Instruments: rebuild the Acid section after nodeseq (#421)

The Acid node now follows the acid voice of nodeseq.com/studio. The
section describes the new knobs, the shared ADSR, the glide and the
comparison with nodeseq.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-10-13 12:39:04 +02:00
1 parent 5c215d655d
commit 63f5f5a0ae
1 file changed
+58 -53
+58 -53
@@ -1,7 +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), an **Acid** plays a bass line as the
Roland TB-303 does (#421), the effects of a sound FX shape it
or a **Chip** makes one the way old game consoles did (#329), an **Acid** plays an acid bass line as
nodeseq.com 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.
@@ -23,7 +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). |
| **Acid** | An acid bass line as nodeseq.com plays it: a saw or a square through a low-pass with a sharp resonance, one ADSR on volume and filter, a glide from the note before. 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). |
@@ -264,66 +264,71 @@ from 0 to 1, the Chip counts in Hz, semitones and seconds.
## 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.
An **Acid** (#421) plays an acid bass line the way [nodeseq.com](https://nodeseq.com/studio)
plays its *Acid* bass: one oscillator, a low-pass with a sharp resonance, and one ADSR that shapes
the volume and sweeps the filter alike. Its defaults are nodeseq's acid. 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. |
| **Cutoff (Hz)** | 250 | Where the filter rests, 40 Hz to 18 kHz. nodeseq's Cutoff 0 to 100 is 40 Hz × 2^(8.8 × value/100), so its 30 is 250 Hz. |
| **Resonance** | 0.78 | How sharply it rings at the cutoff, 0 to 1: a Q of 0.5 dB to 17.5 dB, as nodeseq's Reso 0 to 100. |
| **Env mod** | 0.62 | How far the envelope opens the filter: up to five octaves above the cutoff at 1, never above 18 kHz. At 0.62 that is 3.1 octaves, from 250 Hz up to 2.14 kHz. |
| **Attack (s)** | 0.003 | How long volume and filter take to climb to the top, in a straight line. |
| **Decay (s)** | 0.16 | How long they take to fall back to where the sustain holds them. Grey at *Sustain* 1 while *Env mod* is 0. |
| **Sustain** | 0.15 | Where the volume stays while the note is held, 0 to 1. The filter stays at 60 % of that up its sweep: at the defaults 420 Hz. |
| **Release (s)** | 0.08 | How long volume and filter take to close after the note ends. |
| **Glide (s)** | 0.05 | How long the pitch takes to glide from the note before. 0 does not glide. |
- **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
- **Why it squelches:** the filter falls back **in Hz, not in octaves**, as a Web Audio
`setTargetAtTime` on the cutoff does. So it drops fast from the top and lingers low. With the
sharp two-pole resonance on top, that makes the "yow". Attack, decay and release work as
nodeseq automates them: the attack is a straight ramp, decay and release are `setTargetAtTime`
with a time constant of a third and a quarter of their time.
- **Every note strikes anew** and glides from the pitch of the note before on the same Acid, also
after a gap of up to 2 s. A note that starts while the one before still sounds **cuts it short**,
in a fall with a time constant of 6 ms, gone after 60 ms. Notes that start at the same moment do
not glide and do not cut, they all play.
- **Its own volume:** it plays out its *Release* when the note ends and stops after 1.6 releases
and 50 ms, as nodeseq stops its oscillator. So it **needs no Envelope behind it.** An Envelope
there would shape it a second time.
- **Velocity** makes it louder or softer. A note at velocity 1 is as loud as nodeseq's bass at its
volume 0.8.
- **On the audio thread** (AudioParams): *Cutoff*, *Resonance* and *Env mod*. 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*, the ADSR and *Glide* are fixed when the note starts. The note's
end and the cut are set on the audio clock, to the sample.
- **How it is made:** a worklet per note, in the same module as the Chip. The filter is a
state-variable filter solved without a delay in the loop (Simper's). It has the same curve as the
cookbook biquad that Chrome's `BiquadFilterNode` runs, and it stays steady however fast the cutoff
moves. Like that node it takes its Q in dB.
- **Grit** comes from behind it: nodeseq runs its whole mix through a `tanh` drive, a compressor and
a limiter. Here that is a **Saturation**, a **Compressor** and a **Limiter** after the Acid or on
the master.
- **The node is a device** (#421), 580x280, 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.
- a dark screen, **Filter over a note**: the filter where it rests in dull green, at the top of
its 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 where the sustain holds it;
- a **chrome knob** for every number, grouped *Filter* (cutoff, resonance, env), *Envelope*
(attack, decay, sustain, release) and *Note* (glide). A drag is one undo step, a double-click
goes back to the default.
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.
- **Measured against nodeseq** (2026-10-13, Chrome offline): five sixteenths at 132 BPM, played by
the worklet and by nodeseq's voice built from Web Audio nodes as its studio builds it. In every
10 ms window the two were no more than 0.14 dB apart in loudness, and 95 to 97 % of the windows
were within a tenth of an octave in brightness (the others are dark windows where only the place
of the saw's jump differs). That held at the defaults, at full resonance and sweep, without
resonance, and with a slow ADSR. e2e 34 checks it.
- **A Chrome quirk found on the way:** an `OscillatorNode` started inside a block of 128 samples,
with its frequency automated, runs its first samples at the frequency it had before it started
(440 Hz unless told otherwise). So in nodeseq every note but those on a block's start begins at
a phase at random. Nobody hears that, and the Acid starts every note at the same phase.
## The Instrument tab
@@ -398,7 +403,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 |
| *Acid Bass* | [Acid](#acid) with nodeseq's acid: a saw at cutoff 250 Hz, resonance 0.78, env mod 0.62, attack 3 ms, decay 0.16 s, sustain 0.15, release 80 ms, glide 50 ms → Delay, a dotted eighth, a little of it | squelches, glides |
| *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