Describe Tremolo, Vibrato, Phaser and Flanger as devices (#398)

The four nodes are now drawn as devices of a fixed size in the graph.

- Sounds: a sentence in each of the four rows of the effect table.
- Sounds: under Tremolo and Vibrato, and under Phaser and Flanger, a
  part on the plates, what the screens draw and say, the lane, the
  light and the Rate lamp, and what is on the plate.
- Architecture: ModulationPanel, ModulationFace and modulationView.
- Development: 1974 editor checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-10-10 21:02:55 +02:00
1 parent fdb016d88d
commit c257ed91ff
3 files changed
+51 -5

No files matched your search

+4
@@ -446,6 +446,10 @@ src/editor/soundfx/ SoundFxEditor (#311: the graph, a sound to try it on, Pla
are the Reverb's `PickButton`), LimiterPanel + LimiterFace + limiterView (#397: are the Reverb's `PickButton`), LimiterPanel + LimiterFace + limiterView (#397:
the Limiter as a device, what it takes away read from its taps and caught from the Limiter as a device, what it takes away read from its taps and caught from
the start of a sound, held once it ends, worked out apart from the drawing), the start of a sound, held once it ends, worked out apart from the drawing),
ModulationPanel + ModulationFace + modulationView (#398: the Tremolo, the
Vibrato, the Phaser and the Flanger as one kind of device, the volume or the
pitch over time, or the response with a lane of where it goes, worked out
apart from the drawing against the worklets' own numbers),
nodeParamDrag (a knob's drag as one undo step, for the tab and the nodeParamDrag (a knob's drag as one undo step, for the tab and the
node, several settings at once since #389), nodeTabs (which nodes open a tab of node, several settings at once since #389), nodeTabs (which nodes open a tab of
their own) their own)
+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/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/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 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 # 1970 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1974 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 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 start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result ./e2e.sh status # live progress, or the last run's result
+46 -4
@@ -431,14 +431,14 @@ response of a Reverb, and as what the Sound FX tab tries out. Its row means noth
| **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). In the graph the node is drawn as a blue device of 180x200 graph units (#390): a lever for **Curve** on top, down for *Crossfade* and up, with its lamp lit, for *Both at center*, and below it one knob for `Mix` with *A* and *B* engraved at the ends of its turn. A flip of the lever is one undo step. A linked `Mix` greys the knob. | | **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). In the graph the node is drawn as a blue device of 180x200 graph units (#390): a lever for **Curve** on top, down for *Crossfade* and up, with its lamp lit, for *Both at center*, and below it one knob for `Mix` with *A* and *B* engraved at the ends of its turn. A flip of the lever is one undo step. A linked `Mix` greys the knob. |
| **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. `wet` (0.5) is the sound alone at 0, the sound and its echoes in full at 0.5, the echoes alone at 1. **Ping-pong** sends the echoes left and right in turn. The node is drawn as a device with its echoes on a screen (#393). See [below](#delay-and-the-tail-of-a-voice). | | **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. `wet` (0.5) is the sound alone at 0, the sound and its echoes in full at 0.5, the echoes alone at 1. **Ping-pong** sends the echoes left and right in turn. The node is drawn as a device with its echoes on a screen (#393). 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), `wet` is how much of the crushed sound is heard instead of the sound itself (1 by default). See [below](#bitcrusher). | | **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), `wet` 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, `wet` (1) is how much is heard. See [below](#phaser-and-flanger). | | **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. The node is drawn as a mustard device whose screen swirls its notches (#398). 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). | | **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. The node is drawn as a steel blue device whose screen draws its comb (#398). 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. In the graph the node is drawn as the Gain's device in cyan (#392), with one knob for `Pan`: the middle stands straight up, the turn goes evenly to *L* and *R* engraved at its ends. A linked `Pan` greys the knob. | | **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. In the graph the node is drawn as the Gain's device in cyan (#392), with one knob for `Pan`: the middle stands straight up, the turn goes evenly to *L* and *R* engraved at its ends. A linked `Pan` greys the knob. |
| **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. The node is drawn as a device with a meter of what it takes away, and a double-click opens a tab with its curve and the level going in and out. See [below](#compressor-and-limiter). | | **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. The node is drawn as a device with a meter of what it takes away, and a double-click opens a tab with 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. The node is drawn as a brick device whose screen holds how much it took away and when (#397). 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. The node is drawn as a brick device whose screen holds how much it took away and when (#397). 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. The node is drawn as a copper device whose screen glows hotter the harder it is driven (#396). See [below](#saturation). | | **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. The node is drawn as a copper device whose screen glows hotter the harder it is driven (#396). 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). | | **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*. The node is drawn as a cream device with the volume on a screen (#398). 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). | | **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. The node is drawn as a plum device with the pitch on a screen (#398). 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. The node is drawn as a device with a round screen of its tail (#389). See [below](#reverb). | | **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. The node is drawn as a device with a round screen of its tail (#389). See [below](#reverb). |
Every number of these nodes is an **input**: typed in while nothing is connected, or fed from a Every number of these nodes is an **input**: typed in while nothing is connected, or fed from a
@@ -964,6 +964,26 @@ FX. A `depth` typed in at 0 leaves the node out.
- `rate` (in its ratio) and `depth` move with *Set sound FX parameters*. **Turning the depth up or - `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 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. stays at 0, and the lower half of the swing flattens.
- **The devices** (#398): in the graph a Tremolo is a plate of 640x280 graph units in the cream of
an old amp's front, a Vibrato one of the same size in plum, their inputs on the left and their
output on the right.
- **The screen** draws the first seconds of a sound, two swings at least: 1, 2, 4 or 8 s. The
Tremolo's (*Volume over time*) is a wave the volume shapes, the volume as its outline, `wet`
blended in. Above it `DOWN TO 50%`, under it the period (`EVERY 200 ms`) and how deep it dips
(`DIPS -6 dB`). The Vibrato's (*Pitch over time*) is the pitch the sound is read at, in
semitones, cool below and warm above, on a scale of 0.5, 1, 2, 4 or 8 semitones either way.
Under a `wet` below 1 the pitch the sound came at stands beside it, the brighter the less
`wet`. Above it `UP 0.5 st DOWN 0.51 st`, under it the period and how late the voice is
(`LATE 0.93 ms`).
- **A light** plays it in real time, over and over, with a dot where the volume or the pitch is.
The *Rate* lamp beside the name goes from dark at the bottom of the swing to lit at its top.
When nothing swings nothing moves, and the node lets the editor rest.
- Everything is drawn from the settings, nothing is listened to. The pictures use the LFO the
worklet runs: the unit tests hold them against its samples.
- **On the plate**: a button for each shape, `Rate` and `Depth` as big chrome knobs, `Wet` small. A
shape and each turn are one undo step, a wired number locks its knob. `Rate` gives its slow end
more of the turn (5 Hz in the middle of a Tremolo's). At `wet` 0 everything else is drawn faint,
at `depth` 0 everything but the depth.
- **Measured** (2026-10-07, e2e 34, Chrome offline): a Tremolo square at 10 Hz, depth 100 %, on a - **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 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 ms. A Vibrato square at 5 Hz and one semitone on a 1 kHz sine read 1059.5 Hz in the first half of
@@ -1083,6 +1103,28 @@ stays.
- `frequency` and `offset` move in what they hold (the frequency in octaves), and in an - `frequency` and `offset` move in what they hold (the frequency in octaves), and in an
[instrument](Instruments#envelopes) an Envelope can move `frequency`, `offset` and `wet` while a [instrument](Instruments#envelopes) an Envelope can move `frequency`, `offset` and `wet` while a
note plays. The other numbers move with *Set sound FX parameters* as well. note plays. The other numbers move with *Set sound FX parameters* as well.
- **The devices** (#398): in the graph a Phaser is a plate of 760x280 graph units in mustard, a
Flanger one of 680x280 in steel blue, their inputs on the left and their output on the right.
- **The screen** draws what is left of each frequency, 20 Hz to 20 kHz in octaves, +12 to -36
dB: the notches of the Phaser, the comb of the Flanger, and the peaks feedback rings. A comb
finer than the screen fills the columns it runs through instead of aliasing. It uses the
allpasses, the feedback and the delay of the worklet, at 48 kHz: the unit tests find the same
dB as the worklet on a sine, within 0.005 dB.
- Under it a **lane** shows where that goes over the first seconds of a sound: the Phaser's turn
in Hz, the Flanger's delay in ms (up to 2.5, 5, 10 or 20). A light runs over the lane in real
time and the response above is drawn where it is, so the notches swirl and the comb jets as
they will. The span is two swings of the LFO at least, for a Flanger as long as its sweep takes
to reach 20 ms. The range they move through is shaded, a thin line marks the Phaser's turn or
the Flanger's first notch (500 Hz at 1 ms).
- Above the screen `2 NOTCHES` (half the stages) or `DELAY 1 ms TO 3 ms`, under it
`TURNS 400 Hz TO 1.6 kHz` and `EVERY 2 s`, or `LFO EVERY 4 s` and `SWEEP +5 ms/s`. At `wet` 0
it says `DRY`. The *Rate* lamp swings with the light as on the Tremolo.
- **On the plate**: the Phaser has a button for each number of stages and `Freq`, `Depth`, `Rate`,
`Feedback` and `Wet` in a row. The Flanger has `Offset` and `Sweep`, `Rate` and `Depth`,
`Feedback` and `Wet` in three columns of two. `Freq` gives its low end more of the turn (800 Hz
just under the middle), `Sweep` stands straight up at 0. A button and each turn are one undo
step, a wired number locks its knob. At `wet` 0 everything else is drawn faint, without `depth`
the `Rate`.
- Without `AudioWorklet` the sound goes through both as it is, as through the Bitcrusher. - Without `AudioWorklet` the sound goes through both as it is, as through the Bitcrusher.
- **Measured** (2026-10-07, e2e 34, Chrome offline on the Windows runner): a 1 kHz sine at 0.8 - **Measured** (2026-10-07, e2e 34, Chrome offline on the Windows runner): a 1 kHz sine at 0.8
through a Phaser at 1 kHz came out at an RMS of 0.0000 with two stages and 0.5657 with four through a Phaser at 1 kHz came out at an RMS of 0.0000 with two stages and 0.5657 with four