diff --git a/flow-state/README.md b/flow-state/README.md index 4955b9d..c98ea97 100644 --- a/flow-state/README.md +++ b/flow-state/README.md @@ -45,7 +45,7 @@ under the playhead; **lock** protects a section from further rerolls. Every parameter the generator chose is exposed under the *scene* tab and can be edited live. -The **click track** button (look tab) mixes an audible click onto the detected beat +The **metronome** button (look tab) mixes an audible click onto the detected beat grid. If the clicks don't sit on the beat, tempo detection is wrong and everything downstream inherits it — check this first when a track looks off. @@ -133,7 +133,7 @@ Scenes that composite over a background rather than being one declare ``` src/ - audio/ decode, STFT analysis, tempo, segmentation, FeatureTrack, click track + audio/ decode, STFT analysis, tempo, segmentation, FeatureTrack, metronome engine/ Timeline, Renderer, Layer, Compositor, passes, seeded rng, flash safety look/ palette (OKLCH), LookGenerator, ArcDriver params/ declarative schema, validation, serialisation diff --git a/flow-state/index.html b/flow-state/index.html index 78d7966..07d9c90 100644 --- a/flow-state/index.html +++ b/flow-state/index.html @@ -80,6 +80,73 @@ + + + + diff --git a/flow-state/src/audio/clicktrack.js b/flow-state/src/audio/metronome.js similarity index 84% rename from flow-state/src/audio/clicktrack.js rename to flow-state/src/audio/metronome.js index 2631e8a..e7e66e6 100644 --- a/flow-state/src/audio/clicktrack.js +++ b/flow-state/src/audio/metronome.js @@ -1,6 +1,17 @@ -// Click track — the Phase 1 gate, and the most useful validation tool in the +// The metronome — the Phase 1 gate, and the most useful validation tool in the // project. // +// NAMED CAREFULLY. This file was `clicktrack.js` until a content blocker ate it: +// `clicktrack` is a click-tracking telemetry pattern that EasyPrivacy and +// friends block by substring, so the request died in the browser, the module +// graph died with it, main.js never ran, and every control in the app went +// inert while looking perfectly fine. The server was serving it with a 200 the +// whole time. +// +// Anything shipped to a browser and named like tracking will be treated as +// tracking. Avoid `click`, `track`, `analytics`, `pixel`, `beacon` and `ad` in +// filenames and URL paths, however honest the code behind them is. +// // Beat detection cannot be judged by watching visuals: a grid that is 20ms late // or at half tempo still "looks kind of right". Mixing an audible click onto the // detected grid makes the answer immediate and unambiguous. Downbeats get a diff --git a/flow-state/src/audio/tempo.js b/flow-state/src/audio/tempo.js index c67046a..e24dc06 100644 --- a/flow-state/src/audio/tempo.js +++ b/flow-state/src/audio/tempo.js @@ -4,7 +4,7 @@ // a causal detector has to converge, and lags for the first several bars of every // section. Here the grid is exact from frame zero, and phase is fitted globally. // -// The click track (audio/clicktrack.js) exists to validate this by ear. If the +// The metronome (audio/metronome.js) exists to validate this by ear. If the // clicks don't sit on the beat, nothing downstream can be trusted — every timing // artefact in the finished video traces back to this file. diff --git a/flow-state/src/main.js b/flow-state/src/main.js index 0569bfb..fb1693a 100644 --- a/flow-state/src/main.js +++ b/flow-state/src/main.js @@ -5,7 +5,7 @@ import { formatTime } from './audio/decode.js'; import { describeLook } from './look/LookGenerator.js'; import { toHex } from './look/palette.js'; import { applyGrainToPost, describeGrain, GRAIN_MASKS, GRAIN_MODES } from './look/grain.js'; -import { renderClickTrack, audioBufferToWavBlob } from './audio/clicktrack.js'; +import { renderClickTrack, audioBufferToWavBlob } from './audio/metronome.js'; import { Exporter, exportSegment, downloadBlob, PRESETS, isSupported } from './export/Exporter.js'; const QUALITY = { @@ -328,10 +328,10 @@ function renderPanel() { ${s.shots ? `${s.shots.length} shots` : ''} ${(s.variants || [s.layers]).map((v) => v[0].module.name).join(' / ')} `).join('')} - +
Mixes clicks onto the detected beat grid. If they don't sit on the beat, tempo detection is wrong and everything downstream inherits it.
`; - document.getElementById('btn-clicktrack').addEventListener('click', downloadClickTrack); + document.getElementById('btn-metronome').addEventListener('click', downloadClickTrack); return; } @@ -562,11 +562,11 @@ document.getElementById('btn-segment').addEventListener('click', () => runExport async function downloadClickTrack() { if (!state.show.ready) return; - const button = document.getElementById('btn-clicktrack'); + const button = document.getElementById('btn-metronome'); button.textContent = 'rendering…'; try { const buffer = await renderClickTrack(state.show.audioBuffer, state.show.track.tempo); - downloadBlob(audioBufferToWavBlob(buffer), `${state.show.fileName}-clicktrack.wav`); + downloadBlob(audioBufferToWavBlob(buffer), `${state.show.fileName}-metronome.wav`); button.textContent = 'download click track'; } catch (err) { button.textContent = `failed: ${err.message}`; @@ -652,3 +652,9 @@ if (import.meta.env && import.meta.env.DEV) { requestAnimationFrame(frame); resize(); + +// Tell the boot guard in index.html that the module graph made it all the way +// through. Without this the guard cannot distinguish "still starting" from +// "never going to start", and the failure it exists to catch is exactly the one +// that produces no error at all in the page. +window.__FLOW_STATE_READY__ = true;