Play the whole catalog: site examples, and a CLI to render them - #28
Merged
Conversation
The player carried its own copies of gameBoyDmg, gbaDirectSound and basicOsc — a hand-maintained port of the same voices in @spacedevin/deck-synths that stalled at three of thirty-three. Every other id resolved to a stand-in oscillator. Delete the duplicates and import the catalog instead. Where the player's copies were ahead, the improvement moves into the catalog rather than being lost: duty defaults to 50% instead of all-off silence, normalizeDuty is shared, wavetables copy through, and gbaDirectSound keeps the saw alias, explicit square and sine arms, and unknown-waveform-to-pulse. Duty.tish moves to the catalog as the single home for the duty table. The player's Registry now derives from generatorCatalog() and exports nothing the catalog also exports — tish inlines its import graph without tree-shaking, so a name exported by both packages is a duplicate declaration, not a merge. Also register the sync AudioWorklet, which nothing was doing. syncLead, syncChoir, obSync and laserSync build an AudioWorkletNode and fall back to a plain oscillator when the processor is missing, so the symptom was a thin render rather than an error. buildAudioGraph now kicks registration off — enough for live playback, where it lands inside press-to-first-note latency — and renderDeckToBuffer awaits it, because an offline render gets no such gap. ensureSyncWorklet is exported so hosts driving the graph themselves can do the same. Verified against a negative control: awaited, 10 worklet nodes and no fallbacks; unawaited, every sync voice degrades. The test harness gains createDelay, which spc700's echo needs and a three-voice player never did. deck-synths is declared the way deck already was: a peer on the published range, with a file: devDependency for local work. tish inlines the voices into dist, so it is a build-time dependency for anyone compiling from source rather than a runtime install. All 33 voices now play, and nothing is substituted.
Every example on the page was gameBoyDmg, gbaDirectSound or basicOsc, so the site demonstrated three voices out of thirty-three and the other thirty were documented only as a list. Add six playable sections under 'Beyond the chips': hard sync, the analog rack, atmospheric pads, the five non-Game-Boy chip emulations, acoustic models, and a matrixFm operator graph. Parameters come from the factory presets rather than from what a knob range suggests, which is where the earlier hand-written attempts went wrong — acid303's env_mod is a filter envelope in Hz and runs in the thousands, sub808's punch is Hz, arco's voice names an instrument, and syncLead's sweep_amt is tens of semitones because that sweep is the sound. All 22 blocks on the page now parse, build a graph and play with zero substitutions.
There was no way to hear a song without opening the site and pressing play. The voices are Web Audio — oscillators, biquads, wave shapers, a delay, and an AudioWorklet for the sync family — so there is no pure-Node path to a buffer, and writing a second implementation for Node is the duplication this repo just finished removing. Drive a headless Chrome instead: serve packages/player/dist, call the player's own renderDeckToBuffer() in an OfflineAudioContext, and copy the samples back. Nothing is recorded from a sound device, so it is deterministic and runs far faster than real time. Reports peak, rms and any substitutions, so a bad render is visible without opening the file — a clipped render, a near-silent one, and a track playing a stand-in oscillator all look different on the output line. docs/RENDERING.md covers the CLI, its flags, how to read that line, renderDeckToBuffer for hosts doing their own rendering, and the sync-worklet ordering that a host driving buildAudioGraph directly has to get right.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The site demonstrated three voices out of thirty-three. Every example was
gameBoyDmg,gbaDirectSoundorbasicOsc, and every other generator id resolved to a stand-in oscillator —because the player carried its own copies of those three voices, a hand-maintained port of
@spacedevin/deck-synthsthat stalled at three.The player plays the catalog now, rather than re-porting it
The duplicates are deleted and the catalog is imported. Where the player's copies were genuinely
ahead, the improvement moved into the catalog instead of being lost — duty defaulting to 50%
rather than all-off silence, shared
normalizeDuty, wavetable copy-through, and thesawaliasplus explicit
square/sinearms ingbaDirectSound.Duty.tishmoves to the catalog as the single home for the duty table, and the player'sRegistryderives from
generatorCatalog()and exports nothing the catalog also exports. That last part isload-bearing:
tish buildinlines its import graph with no tree-shaking, so a name exported byboth packages is a duplicate declaration, not a merge.
deck-synthsis declared the waydeckalready was — a peer on the published range, with afile:devDependency for local work. tish inlines the voices into
dist, so it is a build-time dependencyfor anyone compiling from source, not a runtime install.
A real bug this surfaced: the sync worklet was never registered
syncLead,syncChoir,obSyncandlaserSyncbuild anAudioWorkletNodeand fall back to aplain oscillator when the processor is missing. Nothing registered it, so the symptom was a thin
render rather than an error — the sync examples would have quietly sounded wrong.
buildAudioGraphnow starts registration (enough for live playback, where it lands insidepress-to-first-note latency) and
renderDeckToBufferawaits it, because an offline render gets nosuch gap.
ensureSyncWorkletis exported for hosts driving the graph themselves.Measured against a negative control:
Examples
Six new playable sections under Beyond the chips: hard sync, the analog rack, atmospheric pads,
the five non-Game-Boy chip emulations, acoustic models, and a
matrixFmoperator graph.Parameters come from the factory presets rather than from what a knob range suggests —
acid303'senv_modis a filter envelope in Hz and runs in the thousands,sub808'spunchis Hz,arco'svoicenames an instrument, andsyncLead'ssweep_amtis tens of semitones because that sweepis the sound.
Rendering to audio from the CLI
The voices are Web Audio, so there is no pure-Node path to a buffer — and writing a second
implementation for Node is the duplication this PR just removed. It drives a headless Chrome,
calls the player's own
renderDeckToBuffer()in anOfflineAudioContext, and copies the samplesback: deterministic, and far faster than real time. It reports peak, rms and any substitutions so a
clipped render, a near-silent one, and a track playing a stand-in all look different on the output
line.
docs/RENDERING.mddocuments the CLI, its flags, how to read that line,renderDeckToBufferforhosts doing their own rendering, and the worklet ordering a host driving
buildAudioGraphhas toget right.
Verification
createDelay, whichspc700's echo needs and athree-voice player never did.
substitutions.
peak -2.0 dBFS,consoles
-3.5, matrixFm-4.7.RENDERING.mdwas run in this repo rather than written from memory.