diff --git a/Concepts.md b/Concepts.md index 8612a7d..075618c 100644 --- a/Concepts.md +++ b/Concepts.md @@ -359,7 +359,8 @@ one kind can be open at once — two textures, three behaviors — each with its - **A tab can float** (#371): right-click it and pick *Float*, and the document leaves the row for a window of its own above the editor. Its title bar moves the window, the lower right corner sizes it, *Dock* puts the document back into the row, showing, and the `×` closes it. Only - documents float, never Scene or Game. + documents float, never Scene or Game. A window is at least 320 × 200 px. A tab that needs more, + like a Compressor's with its knobs (#378), holds its window at that size. - Every kind has one editor, so it has **one window**: with a texture in a window, opening another texture shows it there and closes the one it had. Windows of different kinds can be open side by side, a sound FX beside its Parametric EQ for instance. diff --git a/Development.md b/Development.md index e881009..1efd0af 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 # 1947 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1949 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 16b3128..b266a95 100644 --- a/Sounds.md +++ b/Sounds.md @@ -435,9 +435,9 @@ response of a Reverb, and as what the Sound FX tab tries out. Its row means noth | **Phaser** | Sweeps notches through the sound, the swirl of a phaser pedal. **Stages** (2, 4, 6, 8 or 12, 4 by default) is how many allpass stages it has, every two make one notch. They sit around `frequency` (800 Hz by default), and an LFO moves them `depth` octaves wide (2), `rate` times a second (0.5 Hz). `feedback` (0.4) sharpens the notches, `wet` (1) is how much is heard. See [below](#phaser-and-flanger). | | **Flanger** | Blends the sound with a copy a few ms late: the jet sound of a flanger, and what sfxr calls its phaser. `offset` is how late the copy is (1 ms by default), `sweep` moves that on by so many ms a second from the start of the sound (0), an LFO adds up to `depth` ms (2) `rate` times a second (0.25 Hz). `feedback` (0.5) and `wet` (1) as on the Phaser. See [below](#phaser-and-flanger). | | **Pan** | Moves the sound left or right: `pan` -1 is all left, 1 all right, 0 (the default) leaves it in the middle. A mono sound is spread on both sides and then moved (each side 0.707 at 0). A stereo sound keeps the side it goes to and the other side moves over into it, as Web Audio's `StereoPannerNode` does: at 1 both sides are heard on the right. In an [instrument](Instruments#envelopes) an Envelope can move it. Measured (e2e 34, Chrome offline): a mono sine at 0.5 came out 0.5 / 0 at -1 and 0.354 / 0.354 at 0, a stereo one at 0.5 came out 0.354 / 0.854. | -| **Compressor** | Squeezes the loudness together: what goes above `threshold` (-24 dB by default) comes out only 1/`ratio` (4) as much louder. `knee` (6 dB) eases into it, `attack` (0.003 s) and `release` (0.25 s) say how fast it turns down and lets go. `makeup` (0 dB) raises it afterwards, `auto-gain` (0) blends that with the gain that brings a sound at full scale back to full scale. A double-click on the node opens its tab: a knob for each number, its curve, and the level going in and out. See [below](#compressor-and-limiter). | -| **Limiter** | Keeps every peak below `threshold` (-6 dB): it looks 5 ms ahead, so even a sudden peak never gets through, and comes back up in `release` (0.1 s). `auto-gain` (1) raises the limited sound towards `ceiling` (-1 dB). Nothing ever goes above the ceiling. See [below](#compressor-and-limiter). | -| **Saturation** | Bends the sound, from warmth to a hard distortion. **Type**: *Soft (tanh)* (the default), *Cubic*, *Hard clip*, *Tube*, *Fuzz*, *Foldback*, *Sine fold* or *Rectify*. `drive` (12 dB) is how hard the sound is pushed into it, `tone` (1) a low-pass after it from 1 kHz (0) to wide open (1). `output` (0 dB) and `auto-gain` (1) set the level of the bent sound, `wet` (1) how much of it is heard instead of the sound itself. See [below](#saturation). | +| **Compressor** | Squeezes the loudness together: what goes above `threshold` (-24 dB by default) comes out only 1/`ratio` (4) as much louder. `knee` (6 dB) eases into it, `attack` (0.003 s) and `release` (0.25 s) say how fast it turns down and lets go. `makeup` (0 dB) raises it afterwards. **Auto-gain** (off) gives back what it takes from a sound at -18 dB, with the makeup on top. A double-click on the node opens its tab: a knob for each number, its curve, and the level going in and out. See [below](#compressor-and-limiter). | +| **Limiter** | Keeps every peak below `threshold` (-6 dB): it looks 5 ms ahead, so even a sudden peak never gets through, and comes back up in `release` (0.1 s). **Auto-gain** (on) raises the limited sound to `ceiling` (-1 dB). Nothing ever goes above the ceiling. See [below](#compressor-and-limiter). | +| **Saturation** | Bends the sound, from warmth to a hard distortion. **Type**: *Soft (tanh)* (the default), *Cubic*, *Hard clip*, *Tube*, *Fuzz*, *Foldback*, *Sine fold* or *Rectify*. `drive` (12 dB) is how hard the sound is pushed into it, `tone` (1) a low-pass after it from 1 kHz (0) to wide open (1). **Auto-gain** (on) keeps the bent sound about as loud as it came in, `output` (0 dB) is added on top. `wet` (1) how much of it is heard instead of the sound itself. See [below](#saturation). | | **Tremolo** | Makes the volume swing `rate` times a second (5 Hz by default), from full down by `depth` percent (50) and back. **Shape**: *Sine* (the default), *Triangle*, *Square* or *Saw*. See [below](#tremolo-and-vibrato). | | **Vibrato** | Makes the pitch swing `rate` times a second (5 Hz), `depth` semitones up and as far down (0.5). **Shape** as on the Tremolo; a *Square* jumps between two pitches. See [below](#tremolo-and-vibrato). | | **Reverb** | The sound in a space. **Type**: *Room* (the default), *Hall*, *Cave*, *Plate*, *Custom* or *Sound*. Custom shows the rows `decay` (s, 1.5 by default), `damping` (0.4) and `pre-delay` (s, 0.01); Sound picks a sound asset as the impulse response of a real space, with `pre-delay`. `wet` (0.3) is how much of the reverb is heard instead of the sound itself. See [below](#reverb). | @@ -451,12 +451,15 @@ Bitcrusher `bits` 1 to 16, `rate` 100 to 48000 Hz, Phaser `frequency` 20 to 125 ms/s, `rate` 0 to 10 Hz, `depth` 0 to 10 ms, both with `feedback` 0 to 0.95, Pan -1 to 1, Compressor `threshold` -100 to 0 dB, `ratio` 1 to 20, `knee` 0 to 40 dB, `attack` and `release` 0 to 1 s, `makeup` -24 to +24 dB, Limiter `threshold` -60 to 0 dB, `ceiling` -30 to 0 dB, -`release` 0.001 to 2 s, both with `auto-gain` 0 to 1, Saturation `drive` 0 to 48 dB, `tone` and -`auto-gain` 0 to 1, `output` -24 to +24 dB, Tremolo `rate` 0 to 20 Hz and `depth` 0 to 100 %, +`release` 0.001 to 2 s, Saturation `drive` 0 to 48 dB, `tone` 0 to 1, `output` -24 to +24 dB, Tremolo `rate` 0 to 20 Hz and `depth` 0 to 100 %, Vibrato `rate` 0.5 to 20 Hz and `depth` 0 to 4 semitones, Reverb `decay` 0.1 to 10 s, `damping` 0 to 1, `pre-delay` 0 to 0.5 s, and every `wet` 0 to 1), whichever way the value arrives. +**Auto-gain** of the Compressor, the Limiter and the Saturation is a switch on the node, not an +input (#378). Until then it was a number from 0 to 1 that blended between the node's own gain and +the auto-gain. A project from before reads such a number as on when it is above 0. + ### Bypass and Wet Every effect has a **Bypass** and a **`wet`** (#354), all but the Mix, the Input and the Output. @@ -708,16 +711,18 @@ Both (#326) work on the whole sound passing them: on a voice that is one sound, - **Compressor** is Web Audio's `DynamicsCompressorNode`. Its knee is a curve the browser draws; the game takes Chromium's, so the numbers below are worked out the way Chrome compresses. - The node **adds a gain of its own**: 0.6 of what it takes off a sound at full scale (the spec - says so). The game takes that back out, so with `makeup` 0 and `auto-gain` 0 a compressed sound - really is quieter. After the node the sound gets `(1 - auto-gain) * makeup + auto-gain * A` dB, - where `A` is what the curve takes off at 0 dBFS. At `auto-gain` 1 a sound at full scale comes - back near full scale. + says so). The game takes that back out, so with `makeup` 0 and auto-gain off a compressed sound + really is quieter. After the node the sound gets `makeup + A` dB with auto-gain on, `makeup` + with it off. `A` is what the curve takes off a sound at **-18 dB**, about where a mixed sound + sits on average, so that one comes out as loud as it went in. Below the threshold `A` is 0. + Until #378 `A` was taken at 0 dB. With a low threshold that lifted everything far too much + (+28.1 dB at -40 dB, 4:1, knee 6, against +14.6 dB now). - **Limiter** runs on the FX worklet. It holds the sound back 5 ms, finds the gain every sample in that window needs to stay below the lower of `threshold` and `ceiling`, and averages it over the window, so the gain is down smoothly by the time a peak is heard. Then it comes back up in - `release`. Both sides of a stereo sound are turned down together, by the louder one. `auto-gain` - raises what comes out by its share of `ceiling - threshold` dB: at 1 the peaks sit on the - ceiling, at 0 on the threshold. + `release`. Both sides of a stereo sound are turned down together, by the louder one. Auto-gain + on raises what comes out by `ceiling - threshold` dB, so the peaks sit on the ceiling. Off, they + sit on the threshold. - **Both are late**: the Compressor by the spec's look-ahead of 6 ms, the Limiter by 5 ms (240 samples at 48 kHz, 221 at 44.1 kHz). This is made up exactly, as for a [Pitch on a bus](#a-pitch-on-a-bus): a voice starts as much earlier, the other way of a Mix and @@ -727,28 +732,38 @@ Both (#326) work on the whole sound passing them: on a voice that is one sound, their ratio. - **The Compressor's tab** (#378): a double-click on the node opens it, one tab per node, named and closed like a [Parametric EQ's](#parametric-eq). It has three parts: - - **Knobs**, one for each of the seven numbers. Drag up or down to turn one: 200 px is the + - **Knobs**, one for each of the six numbers. Drag up or down to turn one: 200 px is the whole turn, with Shift a tenth of that. A notch of the wheel turns it by a fiftieth. A double-click sets the default, and a click on the number below lets you type one. Attack, release and ratio give most of the turn to the low end, where they are used. A knob whose input is linked is greyed: the link decides its value. + - **Auto-gain** is a switch at the right of the knobs. Below it stands what it gives, for + example `+14.6 dB`, or `off`. A click is one undo step. - **The curve**, level in across and level out up, both -60 to 0 dB, before the makeup and auto-gain. Two points are dragged: the one where it starts to bend sets `threshold`, the one at its end sets the `ratio` that ends there. A dot on the curve is the level coming in now. + With auto-gain on, a line at -18 dB in leads from the curve up to the diagonal: that is how + far auto-gain lifts the sound. - **The levels** of the last 4 s, newest at the right, +6 to -60 dB: grey what comes in, in the colour of a sound port what goes out. Where the compressor takes more than 0.5 dB away, - the stretch below the level coming in is tinted by as much as it takes. The makeup is left - out of that, so a raised sound still shows what was squeezed. A level is the loudest sample + the stretch below the level coming in is tinted by as much as it takes. The makeup and + auto-gain are left out of that, so a raised sound still shows what was squeezed. Every + reading keeps the gain of its time, so changing the makeup does not repaint the past. A level is the loudest sample of the last 25 ms. The line across is the threshold. Each drag, notch or typed number is one undo step. The levels come from the same taps as the EQ's spectrum: a gain of 1 in front of the node and behind it, fed into an analyser only while the tab is seen. An EQ's tab and a Compressor's can be listened to at once. + + The knobs stay on the bottom line, centred. The levels fill the height above them. The curve + is a square at their right, centred up and down. It grows with the tab from 180 to 360 px, but + takes no more than 40 % of its width. A floated Compressor's window cannot be made smaller than + what shows every knob and the smallest pictures (560 × 414 px in the test browser). - **Measured** (2026-10-07, e2e 34, Chrome offline): an impulse came out of the Compressor 288 - samples late and out of the Limiter 240. A 1 kHz sine at full scale through a hard-knee - Compressor at -12 dB and 4:1 came out at +0.19 dB with full auto-gain and at -8.81 dB with none - (the curve says -9). A sine at 2 through a Limiter at -6 dB peaked at exactly -6.00 dB, with - auto-gain 1 at -1.00 dB. Chrome's own gain far below the threshold was measured at four + samples late and out of the Limiter 240. A 1 kHz sine at -18 dB through a hard-knee + Compressor at -24 dB and 4:1 came out at -17.92 dB with auto-gain on and at -22.42 dB with it + off (the curve says -22.5; measured 2026-10-09, #378). A sine at 2 through a Limiter at -6 dB + peaked at exactly -6.00 dB, with auto-gain on at -1.00 dB. Chrome's own gain far below the threshold was measured at four settings: 1.524 at -24 dB, knee 30, 12:1; 1.862 at -12 dB, knee 0, 4:1; 1.675 at -20 dB, knee 10, 2:1; 1.000 at 0 dB. The curve gives all four to three places (`tests/core/dynamics.test.ts`). @@ -766,8 +781,9 @@ aliasing. types make. - **`drive` is a gain in front of the curve**, not a curve of its own: the curve spans 256 times full scale and the sound is turned down as far before it. So a drive glides like any gain. -- **`output` and `auto-gain`**: the bent sound gets `(1 - auto-gain) * output + auto-gain * A` dB. - `A` is the gain that brings a sine at -12 dBFS, driven and bent, back to the RMS it came in with. +- **`output` and auto-gain**: the bent sound gets `output + A` dB with auto-gain on, `output` + with it off. `A` is the gain that brings a sine at -12 dBFS, driven and bent, back to the RMS it + came in with. - **`tone`** is a low-pass after the curve, from 1 kHz at 0 to 20 kHz at 1, evenly in octaves. - **`wet`** blends the bent sound with the sound itself. The oversampling makes the bent sound **192 samples late** (Chrome, at 48 and at 44.1 kHz alike), so the sound itself is held back as @@ -776,8 +792,8 @@ aliasing. leaves the node out. - **Measured** (2026-10-07, e2e 34, Chrome offline): an impulse came out 192 samples late at 48 and at 44.1 kHz. Half bent and half dry at 1125 Hz, where 192 samples off would cancel, the sine - came out at 0.00 dB. A sine at -12 dBFS driven 24 dB into *Soft* came out at 0.00 dB with full - auto-gain. + came out at 0.00 dB. A sine at -12 dBFS driven 24 dB into *Soft* came out at 0.00 dB with + auto-gain on. ### Tremolo and Vibrato