Describe baking a pattern or song (#340)

- Songs: a section Baking, what is in a bake and what stays live, when
  it holds, the loop, the file and its memory, measured in e2e 64.
- Patterns: Play pattern plays a bake that holds, with a link there.
- Architecture: bake and ogg in core/music, bakeRender, the bake voice
  and the offline library in viewport, BakeButton in editor/music.
- Development: 1928 checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-10-09 19:13:21 +02:00
1 parent bca135494f
commit 26f23debb8
4 files changed
+66 -6

No files matched your search

+12 -5
@@ -170,7 +170,10 @@ src/core/music/ time (ticks, 480 to the quarter, tempo and meter, a posit
plays on, mute and solo as gates), mixer (the master bus and `mix:<id>`), needs (a
sound FX with its parameters, or none), playbacks (what a sequence loads before it
plays), embedded (#355: what a song keeps to itself), ghosts (#339: the notes around a
pattern item), take (#342: a recorded take as 16-bit WAV, cut to where the song started)
pattern item), take (#342: a recorded take as 16-bit WAV, cut to where the song started),
bake (#340: what a bake is made from as a print, where the parts of its file lie, how they
are put together from the playing looped and the one played once, and played back) +
ogg (#340: Opus in Ogg, the pages around an encoder's packets and back)
src/core/soundfx/ the sound FX graph (#311): nodes (every effect and what it adds to a plan), compile (the
graph walked back from the Output to a plan; a parameter stays open until a voice
starts; a tapped node gets a `tap` in front and behind, #374), parametricEq (#367:
@@ -247,7 +250,10 @@ src/viewport/ WorldViewport (the drawing of a world or a holder, the ba
voice of what reaches its Output, each way with its own file, #331; a voice can start and
be released at a second of the audio clock, #332; a clip of a song from a second into its
sound until its end, a bus per track of a song with its level and pan, #334; the buses of
the mix and the Master routing, #325/#351), music (the scheduler on
the mix and the Master routing, #325/#351; a bake as one voice on the master of its
playing, and a library of its own on an offline context to bake with, #340; a channel
probe that says whether a bake is mono), bakeRender (#340: a pattern or song played
offline, what reaches its master taken, encoded as Opus with WebCodecs), music (the scheduler on
the library's clock, its notes played by the project's instruments and its clips by
the library: the player of the Pattern and Song tabs, #333/#334, and `__gm.music`) + musicTimer
(the Web Worker whose interval is the scheduler's beat, #332), takeRecorder + takeWorklet
@@ -383,13 +389,14 @@ src/editor/curve/ CurveEditor (header: Preset, the picked point's type, Pre
graph drawn from curveValue, points and handles edited with the vector art gestures, the
preview dot and its rail) + curveView (the curve laid onto a picture for those gestures and
read back, a point held between its neighbours, a point put down on free space)
src/editor/pattern/ PatternEditor (header: Play, Grid; plays through the scheduler and follows edits, #333),
src/editor/pattern/ PatternEditor (header: Play, Grid, Bake; plays through the scheduler and follows edits, #333),
PianoRoll (keys, ruler, notes and velocity lane in one widget, the gestures and keys) +
rollView (its geometry: rows, hits, bands) + rollEdit (what a gesture does to the notes)
src/editor/music/ timeView (the time axis: ticks to pixels, zoom, the lines of bars, beats and grid) + TimeRuler
(bars, loop, start and playhead) — for the piano roll and the song tab — + transport (one
piece plays at a time: a song or a pattern, #334)
src/editor/song/ SongEditor (header: Play, Grid; plays through the scheduler, sets the tracks' buses and
piece plays at a time: a song or a pattern, #334) + BakeButton (#340: bakes the pattern
or song shown, lit while its bake holds)
src/editor/song/ SongEditor (header: Play, Grid, Bake; plays through the scheduler, sets the tracks' buses and
follows edits, #334), SongTimeline (heads, ruler and lanes in one widget, dropping assets
in, the gestures and keys) + songView (its geometry: tracks, items and their ends, the
parts of a head) + songEdit (what a gesture does to the items) + songActions (deleting
+1 -1
@@ -19,7 +19,7 @@ php tests/php/accounts.test.php # the API's accoun
php tests/php/snapshots.test.php # the snapshot store (#231), the same way
php tests/php/releases.test.php # the release store (#266), the same way
php tests/php/files.test.php # uploaded files in a folder per account (#344), the same way
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1922 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1928 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
+4
@@ -173,6 +173,10 @@ Its notes fade out in 10 ms; with **Let ring** a note of an instrument with an
naming no sound it stops everything, patterns included, and its `voice` takes a `playback`. The end
of the run stops every pattern.
A pattern can be **baked** with the icon in the header of its tab (#340): `Play pattern` then plays
the bake, one voice of a file, in place of its notes, as long as nothing it is made of has changed.
The tab plays the notes. See [Baking](Songs#baking).
To play several patterns and sounds together, lay them out in a [song](Songs) and play that with
[Play song](Songs#playing-one-in-a-game). A pattern has no tempo input in the game yet, and nothing
tells a graph that a pattern played once has ended.
+49
@@ -348,6 +348,55 @@ there, played by `Play song` at 120 BPM: the clip once, the pattern's notes 2 s
left sat inside it at 0.501 and -0.5. The toolbar's pause held the clock, and `Stop song` with a
fade of 1 s ended it, its buses with it.
## Baking
**Bake** (#340), the icon with a wave in a frame in the header of the Song and the
[Pattern](Patterns) tab, works out once what the song or pattern plays and keeps it on the asset as a
file. `Play song` and `Play pattern` in a game then play that file instead of every note: one voice
in place of many, and nothing for the scheduler to plan. The tab itself always plays live, so an
edit is heard at once.
- **What is in it:** everything up to the output of the song or pattern. For a pattern, its notes
through the instrument and the instrument's sound FX. For a song, its tracks, items and clips, the
FX of tracks and items, the routing, mute and solo.
- **What stays live:** everything after that: the sound FX of the node (or of the asset), `fx params`
and `Set sound FX parameters`, `volume`, `fade in`, the bus, the Master routing and a stop with a
fade. A node behaves the same with a bake and without.
- **When it holds:** a bake keeps a print of what it was made from: the notes and clips, tempo,
length and loop, the tracks with their level, pan and FX, how each instrument plays each of its
notes with its sound FX, and the files, cuts and levels of the sounds. A change to any of them
outdates it, also one to an instrument or a sound the pattern only names. Then the icon is no
longer lit, the info of the tab says *bake outdated*, and a game plays the notes again. Renaming
changes nothing. An undo back to what was baked makes it hold again without baking anew. Baking
and removing a bake are no undo steps.
- **Looped:** the file holds the playing straight from the start, then the loop once more as it
sounds the second time round, with what rings on from the first round in it. Played looped it runs
to the loop end and then round that second round, to the sample. Played once it runs to the end
and fades out over its last 10 ms. What would ring on past the end is not in it: to keep a tail,
make the song or pattern longer, by an empty bar for instance.
- **Stopping:** `Stop song` and `Stop pattern` with **Let ring** fade a bake out over 0.3 s, as its
notes cannot ring on one by one.
- **The click:** not baked, it bakes. While it bakes, the toolbar says how far it has got and a
click cancels it. Baked, it offers **Bake again** and **Remove bake**. A change made while it bakes
throws the result away.
- **The file:** Opus in Ogg, 160 kbit/s in stereo, 96 kbit/s where everything reaching the output is
mono, encoded in the browser with WebCodecs. Baking therefore needs Chrome. Where a browser
cannot decode the file, a game plays the notes. It is raised to full scale and played at the
level it had. It goes up into the sounds of your account under *[bake] name*, and the sound picker
leaves it out. The old file of a bake made again or removed is deleted once the project is saved
without it, unless a snapshot or a release still names it.
- **Memory:** a bake that holds is loaded and decoded with the project like a sound, at 48 kHz:
about 23 MB a minute in stereo. An outdated one is not loaded. Streaming it instead is #349.
Measured (2026-10-09, e2e 64 on the CI's Windows machine): a bar of quarters at 150 BPM with one
more note just before its end, baked: 76800 frames for the bar and 76800 for the second round,
38 KB. Played offline from its notes and from its bake for 4 s, looped, the level of every 20 ms in
which it was heard differed by at most 0.06 dB (114 windows, two loop ends among them). The game
played it as one voice with no note planned; a note drawn outdated it, Ctrl+Z made it hold again,
and Remove bake deleted its file once the project was saved. With the pre-skip of the Opus file
left out on purpose, the same comparison was 4.8 dB off. In a probe, Chrome worked out 60 s of
sound in 0.3 s, or in 2.1 s with many pitched voices, and encoded it in 0.6 s.
## Sound FX on tracks, items and whole songs
A [sound FX](Sounds#sound-fx) can sit on a track, on a pattern item, and on a whole `Play pattern` or