diff --git a/Architecture.md b/Architecture.md index 4eb7dec..18d253f 100644 --- a/Architecture.md +++ b/Architecture.md @@ -174,8 +174,8 @@ src/core/music/ time (ticks, 480 to the quarter, tempo and meter, a posit 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, also read as - the bytes come in, #349), streams (#349: which names stream from which copy, what - copies are missing, how a played sound walks its copy) + streamQueue (#349: the + the bytes come in, #349), streams (#349: which names stream from which copy, which + clips decode from their own, what copies are missing, how a played sound walks its copy) + streamQueue (#349: the samples a streamed sound plays from on the worklet, read at the rate of the context) 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 @@ -412,7 +412,7 @@ src/editor/song/ SongEditor (header: Play, Grid, Bake; plays through the s src/editor/sound/ SoundEditor (#343: header with Play, the clip's direction and fades), WaveCanvas (the waveform, the clips as bands, the cut) + waveView (its time axis), SoundPreview (play and length in the inspector), SoundPicker (the "+ → Sound" rows with their play mark), - StreamKeeper (#349: the copies streamed sounds and their clips play from, made in the + StreamKeeper (#349: the copies streamed sounds and every clip play from, made in the browser and let go once no longer wanted) src/editor/soundfx/ SoundFxEditor (#311: the graph, a sound to try it on, Play and Stop), PreviewParams (the header's fields for the parameters a tab plays with, shared with the Instrument tab), diff --git a/Development.md b/Development.md index 98ca621..751d25a 100644 --- a/Development.md +++ b/Development.md @@ -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 # 1935 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1937 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 diff --git a/Sounds.md b/Sounds.md index 9b77eda..60fe9fb 100644 --- a/Sounds.md +++ b/Sounds.md @@ -127,6 +127,23 @@ library lets the oldest go once they hold more than 64 Mi samples together — a in its tab leaves no pile of copies behind. The sound itself, and a clip over the whole file, is no cut at all and plays the file as it is. +**Each clip has a file of its own** (#349). A moment after a clip was made or last changed, the +editor cuts it from the file, fades and Reverse worked in, encodes that as Opus in Ogg (as a +[streamed](#streaming-long-sounds) sound is) and uploads it as *[stream] Sound - Clip*. The sound +picker leaves it out. This happens for every clip of every sound, with Stream on or off. + +- **A game plays the clip from that file.** A sound that is only played through its clips is never + decoded whole: a long recording with a few short clips in it costs the clips, not the recording. +- **The whole file is still decoded** where something may play the sound itself: a sound field, + a song item or the Sample of an instrument that names the sound and not one of its clips, or a + clip whose own file is not there yet. Then the clips are cut from the whole file as before. +- Something that plays the sound itself only while the game runs, by an id the project does not + hold, starts late once: the whole file is fetched and decoded then. +- **The copy is lossy** and at 48 kHz, as Opus always is: 160 kbit/s in stereo, 96 kbit/s in mono. + The Sound tab plays and draws the original. +- A changed clip plays from the whole file until its new copy is there; the old one is deleted + once the project is saved without it. + **Song items** keep their place and their length when their clip is cut anew: they play from the new start, and one longer than the new stretch plays it round and round. @@ -205,7 +222,8 @@ them at once. **It is loaded ahead.** Every sound of the project is fetched and decoded as soon as the project is open, the way images and fonts are, so the first play starts in the frame its node ran. A sound -that [streams](#streaming-long-sounds) has only the start of its copy fetched. Decoding +that [streams](#streaming-long-sounds) has only the start of its copy fetched, and one only played +through its clips has [the clips' own files](#clips) decoded instead of its own. Decoding ahead opens no audio device: that only happens on the first play. A sound that is still loading when it is played — a run started the moment the project opened — starts a moment late instead. `Play sound` never waits on the network, and a `Stop sound` that happens while the bytes are still @@ -245,9 +263,9 @@ as the page is open. **Stream** (#349) plays such a sound from a copy, a few sec in stereo, 96 kbit/s in mono, as a [bake](Songs#baking) is) and uploads it as *[stream] name*. The sound picker leaves it out. The original stays as it is: the Sound tab shows and cuts that one. Switched off, the copy is taken off the sound and deleted once the project is saved without it. -- **Each clip gets a copy of its own cut**, its fades and Reverse in it, so a clip never needs the - whole file. A changed clip gets a new copy a moment after the last change. A clip that is the whole - file plays the sound's copy. +- **Each clip streams from the copy of its own cut** that every clip has + ([with Stream or without](#clips)), so a clip never needs the whole file. A clip that is the + whole file plays the sound's copy. - **Until a copy is there**, the sound or clip plays decoded, as without Stream. A sound with Stream on is never decoded ahead when the project opens, though: only once it is played without a copy. - **What a game holds** is the compressed copy as it is fetched, and its first two seconds decoded, @@ -273,7 +291,10 @@ Measured (2026-10-09, e2e 34 on the CI's Windows machine): five minutes of a sin with Stream off the same sound decoded grows it by 69.6 MB. Streamed offline at 48 kHz, every sample is the one the copy decoded whole has there, by the feed's decoder and by the browser's `decodeAudioData` alike: from a start at 0.25 s two seconds in, round the seam of its loop, and for -a reversed clip of it round its loop (largest difference 0, no sample late). +a reversed clip of it round its loop (largest difference 0, no sample late). With Stream off, a +game that plays the sound only through that clip decodes the clip's own copy and not the file: its +ten seconds are the cut made from the original to within 0.0091 a sample, and to 0.013 dB in any +20 ms. ## Buses and the master