Instruments: the Acid node for 303 bass lines (#421)

- A new section Acid with its knobs, accent from velocity 0.9 and
  slide for notes that touch or overlap.
- The node table and the presets list Acid and Acid Bass.
- Development counts the 3 new checks in e2e 34.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-10-13 11:09:01 +02:00
1 parent b4262e3198
commit 5c215d655d
2 files changed
+68 -2

No files matched your search

+1 -1
@@ -21,7 +21,7 @@ php tests/php/releases.test.php # the release stor
php tests/php/files.test.php # uploaded files in a folder per account (#344), the same way
php tests/php/videos.test.php # the video store, its queue and the worker (#402), with the
# real ffmpeg: the CI image and game-maker-video have it
node tools/seed-e2e.mjs && node tools/e2e.mjs # 2062 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 2065 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
+67 -1
@@ -1,6 +1,7 @@
An **instrument** (**+ → Instrument**, #331) makes a note a sound. It is a node graph like a
[sound FX](Sounds#sound-fx): **Notes** nodes bring the note in, a **Sample** plays a sound for it
or a **Chip** makes one the way old game consoles did (#329), the effects of a sound FX shape it
or a **Chip** makes one the way old game consoles did (#329), an **Acid** plays a bass line as the
Roland TB-303 does (#421), the effects of a sound FX shape it
and the **Output** is what is heard. It is the first piece of the music tools planned in #330: a
[pattern](Patterns) (#333) plays one, and a [song](Songs) (#334) lays patterns out. A behavior
plays one with **Play note**, and the tab plays it from its keys.
@@ -22,6 +23,7 @@ A at 440 Hz, 127 the highest. A note is named with its octave after it and no da
| **Notes** | Where the notes come in. `any` triggers what it is connected to for every note from **From** to **To**. `note` is the number of the note played, `frequency` its pitch in Hz (`440 * 2^((note - 69) / 12)`), `velocity` how hard it is struck, 0 to 1. With **Single notes** there is one trigger output for every note in the range as well, and each can have a **name** of its own (*Name of C2* in the inspector): the output shows it, and so do the keys of the [piano roll](Patterns#the-pattern-tab). |
| **Sample** | Plays a sound asset when its `trigger` is on. With **Follow note** (on by default) it plays higher or lower by the distance from **Root**, the note it sounds as it is (C4 by default), and **keeps its length** — it is played by the same worklet as a Pitch (#317). Off, every note plays it as it is, as for a drum. **Play** says how it is played while the note is held and once it is let go (table below). **Loop start** and **Loop end** are in seconds, 0 at the end being the end of the sound, counted in the stretch of the [clip](Sounds#clips) when it plays one. The volume of the sound asset counts. |
| **Chip** | Makes a raw wave for every note that triggers it, the way sfxr makes its sounds: square, triangle, saw, sine or noise, with a slide, vibrato, an arpeggio and repeat. It needs no sound file. See [Chips](#chips). |
| **Acid** | A bass line in the way of the Roland TB-303: a saw or a square through a resonant low-pass that saturates, swept open on every note, with accent and slide. It needs no sound file. See [Acid](#acid). |
| **Parameters** | The instrument's own parameters, each a number output with a default — like the parameters of a sound FX's Input. One per instrument. |
| **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). |
@@ -260,6 +262,69 @@ from 0 to 1, the Chip counts in Hz, semitones and seconds.
a note of 0.2 s through an Envelope with a decay of 0.1 s and sustain 0 was gone 0.26 s after it
started, read every 150 ms.
## Acid
An **Acid** (#421) plays a bass line the way the Roland TB-303 does: one oscillator, a resonant
low-pass that squeals and saturates, a sweep that opens it on every note, accent and slide. Notes
`any` into its `trigger`, its `out` to the Output. It always plays the pitch of the note.
| Input | Default | What it does |
| --- | --- | --- |
| **Wave** (setting) | Saw | *Saw* or *Square*, band-limited (PolyBLEP), so a high note does not alias. |
| **Tuning (st)** | 0 | Semitones off the note, -12 to 12. |
| **Cutoff (Hz)** | 400 | Where the filter rests, 30 Hz to 12 kHz. |
| **Resonance** | 0.7 | How sharply it rings at the cutoff, 0 to 1. At 1 it is just short of whistling by itself. It takes some of the bass, as the 303's does. |
| **Env mod** | 0.5 | How far every note opens the filter: up to four octaves above the cutoff at 1. |
| **Decay (s)** | 0.5 | How long the filter takes to fall back, 0.2 to 2 s (to 1 % of the sweep). Grey while *Env mod* is 0. |
| **Accent** | 0.6 | How much an accented note stands out, 0 to 1. |
| **Slide time (s)** | 0.06 | How long a slide takes to all but reach its note (95 %). The 303's is about 60 ms. |
- **Accent comes from the velocity:** a note at **0.9 or more** is accented. The piano roll strikes
at 0.8 unless told otherwise, so a plain note is not. An accented note is up to twice as loud
(+6 dB), its sweep falls in 0.2 s whatever *Decay* says, and the filter opens further, up to two
octaves more with *Accent* and *Resonance* at full. That extra goes through a "capacitor" that
charges with a time constant of 20 ms and empties with one of 0.25 s, as the 303's accent
circuit does: accents a sixteenth apart start each one from what the last left, so a run of
them climbs.
- **Slide:** a note that starts while the note before it on the same Acid still plays, or right
where it ends (within 2 ms), **slides**: the pitch glides there in *Slide time* and nothing is
struck anew, so the sweep and the volume run on. The note before is silent from the first sample
of the new one. Notes that start at the same moment do not slide, they all play. In the piano
roll a slide is two notes that touch or overlap. On the keys it is a key struck while the one
before is still held.
- **Its own volume:** the sound rises in 3 ms and dies away slowly (a time constant of 1.6 s) while
the note is held, and ends with the note, faded out in 10 ms. An **Envelope** behind it starts over
with every note, a slide too, so it is better left out.
- **On the audio thread** (AudioParams): *Cutoff*, *Resonance*, *Env mod*, *Decay* and *Accent*. An
Envelope can move *Cutoff*, `Set sound FX parameters` reaches any of them fed by a parameter. A
**Pitch** behind it moves its frequency. *Wave*, *Tuning* and *Slide time* are fixed when the note
starts.
- **How it is made:** a worklet per note, in the same module as the Chip. The oscillator and the
filter run at twice the rate and are averaged down. The filter is four one-pole low-passes in a
row with feedback, solved without a delay in the loop (Zavalishin's topology-preserving
transform), the signal into it saturated by `tanh`. The notes of one Acid that slide into each
other share one synth on the audio thread: it runs once per block, and each note hears the part of
the block that is its own, so it makes no difference which note the audio thread runs first.
- **The node is a device** (#421), 520x280, in the brushed silver of the 303. The inputs are on the
left, `out` on the right:
- a **button for each wave** at the top, the one playing lit in orange;
- a dark screen, **Filter over a note**: the filter where it rests in dull green, at the top of a
plain note's sweep in acid green, the room between them lit faintly, 20 Hz to 20 kHz, +24 to
-48 dB. It says where it rests and how far a note sweeps it; the bezel says how far a full
accent takes it;
- a **chrome knob** for every number, grouped *Filter* (cutoff, resonance, env mod, decay) and
*Note* (tuning, accent, slide). A drag is one undo step, a double-click goes back to the
default. The tuning has 0 straight up.
The screen is drawn from the settings, not listened to. The
[presets](#presets-randomize-and-mutate) give an Acid three columns of room; Randomize and
Mutate leave it as it is.
- **Measured** (2026-10-13, e2e 34, Chrome offline on the Windows runner): two notes on one line,
the second sliding in at 0.1 s. The first was heard from sample 0 to 4799, the second from 4800
to the end. Where it took over, the sound of both together moved by 0.0012 from one sample to
the next, the largest step anywhere else was 0.10. Live, of three notes at 120 BPM the second,
struck where the first ended, slid, the third after half a second's gap did not.
## The Instrument tab
The graph, and below it **the keys**: all 128 notes, the ones that sound in white and black, the
@@ -333,6 +398,7 @@ The instruments, all on every note unless the table says otherwise:
| *Pluck* | Square at duty 0.3 → Envelope → Low-pass that snaps shut from 6 kHz to 600 Hz | dies away |
| *Triangle Bass* | The triangle of the NES → Envelope at full sustain | held |
| *Pluck Bass* | Saw → Envelope → resonant Low-pass that snaps open to 2.4 kHz and falls back towards 180 Hz | held |
| *Acid Bass* | [Acid](#acid), a saw at cutoff 300 Hz, resonance 0.8, env mod 0.6, decay 0.4 s, accent 0.7 → Delay, a dotted eighth, a little of it | dies away slowly, slides |
| *Drum Kit* | *Single notes* 36 to 46: Kick on C2, Snare on D2, Closed hat on F#2, Open hat on A#2, as in General MIDI | each ends by itself |
The kick is a sine that drops from 160 Hz until it is below 30 Hz, the snare a falling triangle and