Repository navigation
Music: Ogg and a limiter, instruments and effects, songs of sections, MIDI, music that follows the game - #36
Merged
Merged
Conversation
The value ranges' debug line read process.env, which a compile worker in the browser does not have: every script with a program failed to compile in the editor with "process is not defined". It is read only in Node now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
…for long clips
A clip has one channel or two: pan() places it (a balance, 0 as it was), mono()
folds it, and a mix with a two-sided part is two-sided. limit() keeps a clip
under a ceiling (−1.5 dB, room for Ogg's overshoot) with a 5 ms look-ahead and
a 0.1 s release; normalize() brings its peak there. Resampling is a windowed
sinc now, so a note pitched up no longer folds its top back down.
A clip a program plays that lasts 2 seconds or more goes in the map as Ogg
Vorbis (libvorbis in wasm, from wasm-media-encoders, copied to dist/vorbis.wasm
by the build), encoded by the compile worker only for a build, never for a
check as the user types. Shorter clips, untouched imports, wav() clips and any
clip a trigger() plays stay WAVs.
music.render() makes the notes again at 44 100 Hz and limits the mix; tracks
take { pan, volume }. probes/korobeiniki-ab.ts plays the old format against the
new one in the game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
…es, unclipped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
…r, FM, plucks, presets, drums Clips get filter() (a state-variable filter, swept by points, and shelf and peak biquads), drive(), delay(), reverb() (a Dattorro plate), chorus() and compress(). tone() glides and stacks detuned voices across both sides; fm() and pluck() are new sources. The music library's synth has a filter with its own envelope, unison, drive and glide; fmSynth and strings are new instruments; music.presets and music.drums() are ready-made, written on the public API. Tracks take fx and a reverb send, render a shared reverb and a master chain. A bar is a whole number of frames, a step need not be; play refuses more than 32 voices, render does not. Clips are lazy: their length and recipe are known at once, their samples only when a build writes the map or a script reads peak. A check names each file by its recipe and makes no sample, so a song's mix no longer reruns as the user types; builds get 60 seconds. probes/korobeiniki.ts arranges the folk song on all of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
A song is written as sections that fill named parts (a pattern again until the section is full), with section effects and volumes; chords by name with templates that draw their notes by number, voice leading; transpose, stretch, repeat, beat; @ for how loud a step is; swing; riser, impact, the kit's crash and pump. Step 2's arrangement written again as sections gives the same file to the byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
Import Video… takes a .mid: its tracks are read (formats 0 and 1, the
first tempo, the time signature, names, programs, the drums on channel
10) and kept as a .notes file of the script. Clicking one shows its notes
on a roll and plays them roughly. music.midi(notes, { Piano: keys })
plays each track named with an instrument, a kit (General MIDI drum
numbers) or a part, quantized to the song's steps, velocities as accents,
overlapping notes split into voices.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
Judged by its samples, a limited mix peaked 1.1 to 1.6 dB higher between them, and a dense one decoded at 0.99. Each sample's need now comes from its true peak (windowed sinc, 16 a side, each eighth of a sample); the ceiling stays −1.5 dB; decoded peaks −0.7 dB at the most. ENGINE is sound 3, so maps make their files again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
music.stems cuts a song of sections into a clip a bar a layer, each with its notes' tails, echoes and reverb; a Band made in the program says which section and layers play, and conduct plays them at each bar line, with stingers. together() turns clips that play together down by one gain; Clip.ogg(quality) puts a clip in as Ogg Vorbis at its own quality, however short. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
The game plays an Ogg of one channel 2.4 dB quieter than it was mixed, so one channel is written twice. together() takes clips that may play on top at any of their times and leaves room for them; stems passes its stingers at every bar line, turned down with the bars. Step 4 as played. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
conduct wakes every beat: a stinger plays on the next one. stems takes every (beats a piece), so a change can wait half a bar instead of a whole one, for more files and more sounds at once. The probe cuts in half bars. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
scripts/sound-dump.mts writes the sounds a build makes (or names them); scripts/recording-check.mts checks a recording of a probe against them, piece by piece too; stems.playthrough is the song as the game plays it. The probes' answer sheets beside them, and the plan says how it all goes together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG
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 music library's quality work, steps 1–4, each played in StarCraft: Remastered and checked against a recording. The details, the numbers and how to make it all again are in
docs/v4-plan.md("Sounds: step 1" … "Sounds: how to make the Korobeiniki work again").Also a fix trigscript-4 needs regardless:
compiler/ranges.tsreadprocess.envin the browser's compile worker, where there is noprocess, so every program failed to compile in the editor (1c75089, its own commit).Step 1: format and master
pan,mono; render at 44.1 kHz; sinc resampling.dist/vorbis.wasm) for program clips of 2 s or more, or any clip madeogg(quality). Encoded only when a build writes the map, never during a check. The output is deterministic.Step 2: instruments and effects
Step 3: writing songs
part/section/arrange, andsection.with()for a section played again changed.transpose,stretch,@accents, swing.riser,impact,pump, a crash..midand makes a.notesfile;music.midi()turns it into a song.Step 4: music that follows the game
music.stems(song, { layers, every })cuts a song into a clip per piece per layer, each carrying its tails.together()turns them all down by one shared gain, so the sum never clips.Bandmade in the program, played byconduct:every: 2) are the choice for the showpiece.How to repeat the checks
scripts/sound-dump.mtswrites out the sounds a build puts in the map.scripts/recording-check.mtschecks a recording against them, piece by piece too.probes/answers/.The Korobeiniki probes use the folk melody only, which is in the public domain; everything else in them is ours.
1169 tests pass;
tscis clean; every probe runs clean in the lifted game.🤖 Generated with Claude Code
https://claude.ai/code/session_01VUvpRKSfm2yqit18wN5muG