From 892ae19f34b5ff800e5e3cff1602fb5c893c1d0b Mon Sep 17 00:00:00 2001 From: meleki Date: Wed, 7 Oct 2026 21:30:16 +0200 Subject: [PATCH] Describe the new sound FX nodes (#318 #319 #326 #323 #320 #328) The sound FX got a reworked Filter and six new nodes in one series. The wiki now explains each of them where the other nodes are. - Sounds: a row in the node table for each new node, the Filter with all types, Q, Gain and Slope, and sections for the Filter, Compressor and Limiter, Saturation, Tremolo and Vibrato and the Reverb, each with the values measured in e2e 34. - Instruments: the node table lists the new nodes, and the numbers an Envelope can move name the Filter's frequency, Q and gain and the pan of a Pan instead of cutoff and resonance. - Development: 1878 editor checks instead of 1872. Co-Authored-By: Claude Opus 5.5 --- Development.md | 2 +- Instruments.md | 8 +-- Sounds.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 168 insertions(+), 8 deletions(-) diff --git a/Development.md b/Development.md index 0378236..8b581e2 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 # 1872 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1878 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/Instruments.md b/Instruments.md index bc39407..e99d455 100644 --- a/Instruments.md +++ b/Instruments.md @@ -26,7 +26,7 @@ A at 440 Hz, 127 the highest. A note is named with its octave after it and no da | **Sound FX** | Sends the sound through a sound FX asset, as if it were the sound at the FX's Input. Every parameter of that FX is an input here; one left open is the FX's default. The node is titled after the FX, and a double click opens it. | | **Envelope** | Shapes a note as it is played: **Attack**, **Decay** and **Release** in seconds, **Sustain** from 0 to 1. On the way of the sound it is its volume; its output `value` moves a number with the same curve, from **From** to **To**. See [Envelopes](#envelopes). | | **Output** | What is heard. It takes **one** sound, like every other sound input; a second edge replaces the first. To play several together, put a **Mix** in front of it (several Mixes in a row for more than two). One per instrument. | -| Gain, Pitch, 5-Band-EQ, Filter, Mix, Delay, Bitcrusher, Phaser, Flanger | The effects of a [sound FX](Sounds#sound-fx), the same nodes. | +| Gain, Pitch, 5-Band-EQ, Filter, Mix, Delay, Bitcrusher, Phaser, Flanger, Pan, Compressor, Limiter, Saturation, Tremolo, Vibrato, Reverb | The effects of a [sound FX](Sounds#sound-fx), the same nodes. | **Play** of a Sample: @@ -122,14 +122,14 @@ sound as it is. - **`value`** moves a number with the same curve: at **From** while the note rests, at **To** at the top of the attack, and where the sustain puts it between them while held. Its pin is of a kind of its own, an **envelope**, drawn in coral. It fits only a number the sound itself holds while it - plays: `cutoff` and `resonance` of a Filter, `semitones` of a Pitch, `gain` of a Gain, a band of + plays: `frequency`, `Q` and `gain` of a Filter, `pan` of a Pan, `semitones` of a Pitch, `gain` of a Gain, a band of the 5-Band-EQ, `mix` of a Mix, `bits`, `rate` and `mix` of a Bitcrusher, `frequency` and `mix` of a Phaser, `offset` and `mix` of a Flanger, and `frequency` and `duty` of a Chip. Not the time of a Delay, not the times of an Envelope, not a parameter of a Sound FX node, not the slide or the other numbers of a Chip, not the LFO of a Phaser or Flanger. A cutoff, a Gain, the rate of a Bitcrusher and the frequency of a Phaser or a Chip move in equal ratios (octaves, decibels), everything else evenly. Both ends are held to the range of the number it moves. - **A filter sweep:** Sample → Filter → Envelope → Output, and a second Envelope with its `in` left - open, its `value` into the Filter's `cutoff`, From 200, To 5000. Each Envelope is a curve of its + open, its `value` into the Filter's `frequency`, From 200, To 5000. Each Envelope is a curve of its own; one Envelope can feed several numbers. - **A number an Envelope moves is the Envelope's.** It is no longer reached by a parameter or by `Set sound FX parameters`. A Pitch it moves comes on top of the shift the note already has. @@ -239,7 +239,7 @@ plays the note last played at once (C4 before any), for up to 2 s. Flangers in the graph anew, as sfxr's randomizer does: most of them wild. A Flanger's offset and sweep come from sfxr's phaser, its LFO and feedback and all of a Phaser, which sfxr has not, within bounds of their own (a rate of 0.05 to 5 Hz, a Phaser at 200 Hz to 4 kHz). It leaves the - wave, *Follow note*, the type of a Filter, the *Stages* of a Phaser, every *Mix*, an Envelope's + wave, *Follow note*, the type and Slope of a Filter, the *Stages* of a Phaser, every *Mix*, an Envelope's *From* and *To* and every other node as they are. An Envelope gets *Sustain* 0. An input with an edge into it keeps what it is fed. - **Mutate** moves the numbers of those nodes a little, each one in two: a frequency, cutoff or rate by up to a quarter of an octave, the rest by a step and a tenth of its value. A 0 stays 0, diff --git a/Sounds.md b/Sounds.md index 9d2bea3..689113e 100644 --- a/Sounds.md +++ b/Sounds.md @@ -248,20 +248,33 @@ Input and the Output once each; their pins carry sound and fit nothing but each | **Gain** | Louder or quieter, in dB (`Gain (dB)`): 0 leaves it alone, -6 is about half as loud, +6 twice. Above 0 the sound can clip. | | **Pitch** | Higher or lower, in semitones (12 is an octave, 0.5 a quarter tone). The sound **keeps its length**, also when the pitch changes while it plays. The pitch is made where the sound is read, so an effect before the Pitch acts as if it came after it. On a [Chip](Instruments#chips) of an instrument it moves the frequency of the Chip itself. | | **5-Band-EQ** | Five bands in dB: 80 Hz and 12 kHz as shelves, 250 Hz, 1 kHz and 4 kHz as peaks. A band at 0 does nothing. | -| **Filter** | Takes away what is above `cutoff` (Hz, 1000 by default): **Type** *Low-pass*. Or what is below it (*High-pass*), or both, leaving a band around it (*Band-pass*). `resonance` from 0 to 1 raises what is right at the cutoff: a low- or high-pass has no bump at 0 (Q -3 dB) and rises by 20 dB at 1, a band-pass gets narrower (Q 0.7 to 20). In an [instrument](Instruments#envelopes) an Envelope can move both while a note plays. | +| **Filter** | **Type** *Low-pass* (the default) takes away what is above `frequency` (Hz, 1000 by default), *High-pass* what is below it, *Band-pass* all but a band around it and *Notch* just that band. *Low shelf* and *High shelf* raise or lower everything below or above it by `gain` (dB), *Peaking* a band around it; only these three have the `gain` row. *All-pass* leaves every frequency as loud as it was and only turns its phase. `Q` (0.707 by default) says how sharp it is: at 0.707 a low- or high-pass has no bump, higher it rings at the frequency, and a band, a notch or a peak gets narrower. **Slope** makes a low-pass, high-pass, band-pass or notch steeper: *12 dB/oct* is one filter, *24* two and *48* four in a row, and only the last of them has the Q. In an [instrument](Instruments#envelopes) an Envelope can move all three numbers while a note plays. See [below](#filter). | | **Mix** | Blends the sounds at `a` and `b`: at 0 only `a` is heard, at 1 only `b` (0.5 is the default). **Curve** says what lies between: *Crossfade* (the default) gives half of each at 0.5, *Both at center* keeps each full up to the middle. See [below](#the-curve-of-a-mix). | | **Delay** | An echo: the sound comes back after `time`, again and again, each time `feedback` times as loud. **Sync** says what `time` counts: *Seconds*, or *Beats* in quarter notes at the tempo of the voice. `damping` makes every echo duller than the last, `mix` is how loud the echoes are next to the sound, which stays as it is. **Ping-pong** sends the echoes left and right in turn. See [below](#delay-and-the-tail-of-a-voice). | | **Bitcrusher** | Makes the sound rough, like an old console. `bits` rounds it to fewer steps (8 by default), `rate` takes a sample only so many times a second and holds it until the next (8000 Hz by default), `mix` is how much of the crushed sound is heard instead of the sound itself (1 by default). See [below](#bitcrusher). | | **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, `mix` (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 `mix` (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. 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, `mix` (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`. `mix` (0.3) is how much of the reverb is heard instead of the sound itself. See [below](#reverb). | Every number of these nodes is an **input**: typed in while nothing is connected, or fed from a parameter of the Input. Each is held to its range (Gain -120 to +24 dB, Pitch -64 to +64, the EQ -bands -60 to +24 dB, Filter `cutoff` 20 to 20000 Hz and `resonance` 0 to 1, Mix 0 to 1, Delay +bands -60 to +24 dB, Filter `frequency` 20 to 20000 Hz, `Q` 0.1 to 30 and `gain` -40 to +40 dB, Mix 0 to 1, Delay `time` 0.01 to 8 and never more than 2 s, `feedback` 0 to 0.95, `damping` and `mix` 0 to 1, Bitcrusher `bits` 1 to 16, `rate` 100 to 48000 Hz and `mix` 0 to 1, Phaser `frequency` 20 to 20000 Hz, `rate` 0 to 10 Hz, `depth` 0 to 6 octaves, Flanger `offset` 0 to 10 ms, `sweep` -125 to -125 ms/s, `rate` 0 to 10 Hz, `depth` 0 to 10 ms, both with `feedback` 0 to 0.95 and `mix` 0 to 1), +125 ms/s, `rate` 0 to 10 Hz, `depth` 0 to 10 ms, both with `feedback` 0 to 0.95 and `mix` 0 to 1, +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`, +`auto-gain` and `mix` 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` and +`mix` 0 to 1, `pre-delay` 0 to 0.5 s), whichever way the value arrives. **How Pitch keeps the length** (#317). A way with a Pitch is played by an `AudioWorklet` on the @@ -376,6 +389,153 @@ change ends. The end is timed on the clock of the sound, so a paused run holds a about 0.3 s, so about **400 of them play at once** (2026-10-06). There is no limit on how many voices play, also now that they live longer. +### Filter + +A **Filter** (#335, #318) is Web Audio's `BiquadFilterNode`, every type it has. + +- **`Q` is a factor** for every type, 0.1 to 30. Web Audio takes the Q of a low- or high-pass in + dB, the game hands it on as `20 * log10(Q)`, so 0.707 is a flat -3 dB there. A shelf has no Q in + Web Audio, the row does nothing for it. +- **Slope** puts 2 or 4 filters of the same type in a row. They share one `frequency`, carried by + one source into all of them, so a sweep moves them together. Only the last has `Q`, the others + sit at 0.707, so a resonance is as high at 48 dB/oct as at 12. Shelves, peaks and the all-pass + have no Slope row: in a row they would only add up their gain or their phase. +- `frequency` moves in octaves, `Q` in its ratio (in dB for a low- or high-pass), `gain` in dB. + All three move with *Set sound FX parameters*, and an Envelope can move them in an + [instrument](Instruments#envelopes). +- A Filter before #318 had *Resonance* from 0 to 1 instead of `Q`. No project used it. The Chip + Presets keep their sound: their resonance is now the Q it gave before. +- **Measured** (2026-10-07, e2e 34, Chrome offline): a 2 kHz sine through a low-pass at 1 kHz with + Q 0.707 came out 12.37 dB quieter at 12 dB/oct and 49.5 dB quieter at 48 dB/oct. A peak of + +6 dB at 1 kHz raised a 1 kHz sine by 6.00 dB, a notch there took it down by 124 dB. + +### Compressor and Limiter + +Both (#326) work on the whole sound passing them: on a voice that is one sound, on a +[bus](#buses-and-the-master) everything playing through it. + +- **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. +- **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. +- **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 + the other layers of a note are held back as far, and a bus counts it into how late it is. A voice + with a Limiter is let go only once what the Limiter still holds is out. +- Every number moves with *Set sound FX parameters*: dB values evenly, times and the ratio in + their ratio. +- **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 + 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`). + +### Saturation + +A **Saturation** (#323) is a `WaveShaperNode` with a fixed curve per type, oversampled 4x against +aliasing. + +- **The types**: *Soft* is `tanh`, *Cubic* `1.5 * (x - x³/3)` up to full scale, both round the peaks + off. *Hard clip* cuts them at ±1. *Tube* is `tanh` shifted, so one half bends harder than the + other (even harmonics). *Fuzz* is harder still and lopsided. *Foldback* mirrors what goes past + full scale back in, again and again, *Sine fold* does that smoothly. *Rectify* turns the lower half + up, which sounds an octave higher. A 10 Hz high-pass after the curve takes out the DC the lopsided + 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. +- **`tone`** is a low-pass after the curve, from 1 kHz at 0 to 20 kHz at 1, evenly in octaves. +- **`mix`** 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 + far and the two meet without a comb. The node as a whole is late by that much, which a voice, a + Mix and a bus make up for as for a [Compressor](#compressor-and-limiter). A `mix` typed in at 0 + 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. + +### Tremolo and Vibrato + +Both (#320) run on the FX worklet with an LFO that is **at phase 0 on the first sample of its +voice**, so every sound starts its swing the same way. On a bus the LFO starts when the bus gets its +FX. A `depth` typed in at 0 leaves the node out. + +- **The shapes**, over one period: *Sine* rises from 0, *Triangle* too, *Square* is up for the first + half and down for the second, *Saw* rises from 0 to the top, drops to the bottom and rises again. +- **Tremolo**: the volume is `1 - depth * (1 - LFO) / 2`, so full at the top of the LFO and + `1 - depth` at its bottom. A sine starts halfway down. +- **Vibrato** moves the pitch through a short delay whose time swings: a delay getting shorter is + read faster, so higher. That works on a bus too, where there is no source to speed up, and the + sound keeps its length. The delay is the LFO summed up, so its slope is the LFO and the pitch + follows the shape: a *Square* is a trill between two pitches. At the top the sound is read + `2^(depth/12)` times as fast, at the bottom as much slower as that is faster. +- The delay swings around a **center**, as far as it swings out at the `rate` and `depth` the voice + starts with: about 1 ms at 5 Hz and half a semitone, 83 ms for a sine and 130 ms for a square at + 0.5 Hz and 4 semitones. That center is how late the Vibrato is, and a voice, a Mix and a bus make + up for it as for a [Compressor](#compressor-and-limiter). A sound started *now* in a game is heard + that much later. +- `rate` (in its ratio) and `depth` move with *Set sound FX parameters*. **Turning the depth up or + the rate down after the start** asks for a delay below 0 at the low end of the swing: there it + stays at 0, and the lower half of the swing flattens. +- **Measured** (2026-10-07, e2e 34, Chrome offline): a Tremolo square at 10 Hz, depth 100 %, on a + steady 0.5 for a voice starting at 0.1 s was silent before it, then 0.5 for 50 ms and 0 for 50 + ms. A Vibrato square at 5 Hz and one semitone on a 1 kHz sine read 1059.5 Hz in the first half of + a period and 940.5 Hz in the second. + +### Reverb + +A **Reverb** (#328) is a `ConvolverNode`: the sound folded with an impulse response, the way a +space answers a click. It works on a voice and on a bus; a shared one on a bus is far cheaper than +one on every voice (see [Buses and the master](#buses-and-the-master), and #351 for sending only a +share of a sound there). + +- **Type** picks the space: + + | Type | `decay` | `damping` | `pre-delay` | + | --- | --- | --- | --- | + | Room | 0.6 s | 0.5 | 5 ms | + | Hall | 2.5 s | 0.35 | 20 ms | + | Cave | 5 s | 0.15 | 40 ms | + | Plate | 1.6 s | 0.1 | 0 | + + *Custom* shows the three rows to set them yourself, *Sound* the pick of a sound asset and + `pre-delay`. The rows are only there for those types (they are ports of their own, so an edge + into one that goes away is a ghost port). +- **The made impulse response** is noise, its own on either side, that has fallen by 60 dB after + `decay` seconds (up to 10). A low-pass closes over that time, from 20 kHz towards 500 Hz as far as + `damping` says, so the highs of the tail die first. It is made once for its numbers and kept. +- **A sound as the impulse response** is loaded with the voice, or before a bus gets its FX. Web + Audio normalizes it, so a loud recording is not louder than a quiet one. A sound that cannot be + loaded leaves the reverb silent and the sound dry. +- **`mix`**: `(1 - mix)` of the sound itself, `mix` of the reverb. A bus that only carries reverb + wants 1. A `mix` typed in at 0 leaves the node out. +- **Only `mix` moves while the sound plays.** `decay`, `damping` and `pre-delay` are set when a + voice starts, on a bus when it gets its FX: a new impulse response would click. +- **The voice lasts until the reverb has faded**: its pre-delay and its decay, or as long as its + sound. +- **Cost**: every voice with a Reverb folds its sound with the whole impulse response. Many short + sounds at once, each with a long decay, are better sent through one bus. +- **Measured** (2026-10-07, e2e 34, Chrome offline): an impulse through a *Custom* reverb of 1 s with + 50 ms pre-delay was silent up to 50 ms after it, and fell by 35.9 dB from 0.2-0.3 s to 0.8-0.9 s + after it (36 dB to be expected). At `mix` 0.5 the impulse itself came through at half. With a sound + as the impulse response that has one sample at 0.2 s, the impulse came back exactly 0.25 s later and + nowhere else. + ### Bitcrusher A **Bitcrusher** (#322) runs on the same worklet as Pitch, sample by sample: