Clickable previews: exact app deep-links everywhere
Every rendered still links to the app frame that produced it (song + seed + frame + venue/viz pins + probe warm-up, new window), via shared src/ui/preview.js. Supporting work, each measured: app honors ?frame/?venueSel/?viz/?warm= with pre-build pins (Show.applyPins) and primes deep-link loads; shared pinViz/pinVenueShell paths for gallery, filmstrip and app; probes prime and carry their warm-up; bank probes analyse the bank PCM and file decode is fixed-rate with bit-exact WAV parsing so app and probes see identical samples; materials built mid-update compile synchronously (needsCompile). Round trip verified bit-identical gallery-versus-app.
This commit is contained in:
@@ -22,7 +22,7 @@ import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { synthesizeSong } from '../src/audio/synth.js';
|
||||
import { FeatureTrack } from '../src/audio/FeatureTrack.js';
|
||||
import { SONGS, AXES } from '../src/audio/songbank.js';
|
||||
import { SONGS, AXES, bankPcm } from '../src/audio/songbank.js';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -41,7 +41,9 @@ const CHECK_ONLY = has('check');
|
||||
* 16-bit PCM WAV, mono.
|
||||
*
|
||||
* Mono halves the size and loses nothing: the analyser sums to mono before it
|
||||
* does anything, and the synth writes the same signal to both channels anyway.
|
||||
* does anything, the synth writes the same signal to both channels anyway —
|
||||
* and the probes analyse the mono bank PCM, so the files must be mono for
|
||||
* app and probe features to match bit-exactly. See songbank.js bankPcm.
|
||||
*/
|
||||
function wav(samples, sampleRate) {
|
||||
const bytes = samples.length * 2;
|
||||
@@ -63,13 +65,12 @@ function wav(samples, sampleRate) {
|
||||
// Peak-normalise to -1 dBFS. Without it the quiet ambient entries clip down
|
||||
// into 16-bit noise and their measured flatness stops being what the spec
|
||||
// asked for — the bank would then be testing the encoder, not the generator.
|
||||
let peak = 0;
|
||||
for (let i = 0; i < samples.length; i++) peak = Math.max(peak, Math.abs(samples[i]));
|
||||
const gain = peak > 1e-9 ? 0.89 / peak : 1;
|
||||
// Shared with songbank.js bankPcm: the files must hold exactly what the
|
||||
// probes analyse, or app-vs-probe previews can never match.
|
||||
const pcm = bankPcm(samples);
|
||||
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
const v = Math.max(-1, Math.min(1, samples[i] * gain));
|
||||
buffer.writeInt16LE(Math.round(v * 32767), 44 + i * 2);
|
||||
for (let i = 0; i < pcm.length; i++) {
|
||||
buffer.writeInt16LE(Math.round(pcm[i] * 32768), 44 + i * 2);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
@@ -92,7 +93,19 @@ for (const spec of SONGS) {
|
||||
transition: spec.transition ?? 0.5,
|
||||
seed: 1 + SONGS.indexOf(spec),
|
||||
});
|
||||
const track = FeatureTrack.fromAudioBuffer(buffer, { fps: 60 });
|
||||
// Measured from the bank PCM — the same samples the files hold and the
|
||||
// probes analyse (see songbank.js bankPcm) — not the raw synth.
|
||||
const pcm = bankPcm(buffer.getChannelData(0));
|
||||
const track = FeatureTrack.fromAudioBuffer({
|
||||
sampleRate: buffer.sampleRate,
|
||||
length: pcm.length,
|
||||
duration: buffer.duration,
|
||||
numberOfChannels: 1,
|
||||
getChannelData: (i) => {
|
||||
if (i !== 0) throw new Error('bank is mono');
|
||||
return pcm;
|
||||
},
|
||||
}, { fps: 60 });
|
||||
|
||||
if (!CHECK_ONLY) {
|
||||
writeFileSync(resolve(OUT, `${spec.name}.wav`),
|
||||
|
||||
Reference in New Issue
Block a user