Describe the Chip node in Instruments (#329)

- Instruments: new section Chips with the waves, inputs and measured
  cost, the Chip in the node table, and sustain 0 ending a note in
  the Envelope.
- Sounds: what a Pitch does on a Chip.
- Development: 1843 checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminandClaude Opus 5.5 committed 2026-10-06 22:55:01 +02:00
1 parent 5ea149cad0
commit 2b7a1b04d0
3 files changed
+62 -8

No files matched your search

+1 -1
@@ -18,7 +18,7 @@ php tests/php/accounts.test.php # the API's accoun
# has and the web server's container has not (SKIP there) # has and the web server's container has not (SKIP there)
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
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1841 editor checks, screenshots in /tmp/gm-e2e-*.png node tools/seed-e2e.mjs && node tools/e2e.mjs # 1843 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
+60 -6
@@ -1,6 +1,7 @@
An **instrument** (**+ → Instrument**, #331) makes a note a sound. It is a node graph like a 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, [sound FX](Sounds#sound-fx): **Notes** nodes bring the note in, a **Sample** plays a sound for it
the effects of a sound FX shape it and the **Output** is what is heard. It is the first piece of or a **Chip** makes one the way old game consoles did (#329), 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. 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. A behavior plays one with **Play note**, and the tab plays it from its keys.
@@ -20,6 +21,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). | | **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 the sound is [cut to](Sounds#the-sound-tab). The volume of the sound asset counts. | | **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 the sound is [cut to](Sounds#the-sound-tab). 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). |
| **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. | | **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. | | **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). | | **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). |
@@ -85,7 +87,7 @@ voice 0.
[Envelope](#envelopes) does not fade: it plays on through the release. [Envelope](#envelopes) does not fade: it plays on through the release.
- **Pitch.** A Sample that follows the note is shifted by the phase vocoder of #317, so a note far - **Pitch.** A Sample that follows the note is shifted by the phase vocoder of #317, so a note far
from the root sounds rough, as there. A note at its root is the sound itself, played without the from the root sounds rough, as there. A note at its root is the sound itself, played without the
worklet. worklet. On a Chip a Pitch moves its frequency itself, exactly and at no cost.
- **Sound FX node and a Mix.** The arriving sound is set in wherever the FX's Input was. A Mix in - **Sound FX node and a Mix.** The arriving sound is set in wherever the FX's Input was. A Mix in
the FX therefore plays the sound twice, one source per way, as in the sound FX itself. the FX therefore plays the sound twice, one source per way, as in the sound FX itself.
@@ -113,13 +115,18 @@ sound as it is.
then release* one leaves its loop as well, and the note ends when the release has. A then release* one leaves its loop as well, and the note ends when the release has. A
*One-shot* under an Envelope of its own plays through as it would, the Envelope held at its *One-shot* under an Envelope of its own plays through as it would, the Envelope held at its
sustain. Echoes after it ring on as ever. sustain. Echoes after it ring on as ever.
- **Sustain 0 ends the note** once its decay is over, however long the note is held (#329): every
source under that Envelope ends then, a *One-shot* and a [Chip](#chips) too. A laser or a jump
played by `Play note` with `duration` 0 therefore ends by itself rather than running on silently
until a `Stop sound`.
- **`value`** moves a number with the same curve: at **From** while the note rests, at **To** at - **`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 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 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: `cutoff` and `resonance` of a Filter, `semitones` of a Pitch, `gain` of a Gain, a band of
the 5-Band-EQ, `mix` of a Mix, and `bits`, `rate` and `mix` of a Bitcrusher. Not the time of a the 5-Band-EQ, `mix` of a Mix, `bits`, `rate` and `mix` of a Bitcrusher, and `frequency` and
Delay, not the times of an Envelope, not a parameter of a Sound FX node. A cutoff, a Gain and the `duty` of a Chip. Not the time of a Delay, not the times of an Envelope, not a parameter of a
rate of a Bitcrusher move in equal ratios (octaves, decibels), everything else evenly. Both ends are held to the range of the number it moves. Sound FX node, not the slide or the other numbers of a Chip. A cutoff, a Gain, the rate of a
Bitcrusher and the frequency of 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 - **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 `cutoff`, From 200, To 5000. Each Envelope is a curve of its
own; one Envelope can feed several numbers. own; one Envelope can feed several numbers.
@@ -141,6 +148,53 @@ sound as it is.
A Gain moved from 0.25 to 1 by that curve was 0.5 halfway up. Live, a looped note of 0.2 s A Gain moved from 0.25 to 1 by that curve was 0.5 halfway up. Live, a looped note of 0.2 s
through an Envelope with a release of 1 s was gone 1.32 s after it started, read every 150 ms. through an Envelope with a release of 1 s was gone 1.32 s after it started, read every 150 ms.
## Chips
A **Chip** (#329) makes the sound of a note itself, the way old game consoles did and sfxr does:
a raw wave, and what its frequency does over the note. Notes `any` into its `trigger`, its `out`
on through an Envelope, a Filter or a Bitcrusher like any Sample. Where sfxr counts every slider
from 0 to 1, the Chip counts in Hz, semitones and seconds.
| Input | Default | What it does |
| --- | --- | --- |
| **Wave** (setting) | Square | *Square*, *Triangle*, *Saw*, *Sine*, *Noise* (sfxr's: 32 new values every period, so it gets lower with the frequency) or *Noise (periodic)* (the short noise of the NES, a 15-bit register repeating every 93 steps: metallic, with a pitch). A square and the periodic noise are half as loud as the others, as in sfxr. |
| **Follow note** (setting) | on | Plays at the pitch of the note. Off, it plays at **Frequency**. |
| **Frequency (Hz)** | 440 | The pitch without *Follow note*. A number connected here goes before both: `frequency` of a Notes node, a parameter, or an Envelope's `value` for a sweep in Hz. |
| **Duty** | 0.5 | How much of a square is up: 0.5 is even, 0.25 and 0.125 thinner and more nasal. Held to 0.01 to 0.99. |
| **Duty sweep (/s)** | 0 | Moves the duty by that much per second. |
| **Slide (st/s)** | 0 | Glides the pitch in semitones per second: -100 is a laser falling about eight octaves a second. |
| **Delta slide** | 0 | Changes the slide by that many semitones per second, every second. |
| **Min frequency (Hz)** | 0 | Where a slide falls below it, the note ends. 0 never. |
| **Vibrato depth (st)** | 0 | How far it wobbles up and down, in semitones. |
| **Vibrato speed (Hz)** | 6 | How often it wobbles a second. |
| **Arpeggio (st)** | 0 | Jumps the pitch by that many semitones once, **Arpeggio time** seconds into the note: the "pling" of a pickup. |
| **Arpeggio time (s)** | 0.1 | When it jumps. |
| **Repeat (s)** | 0 | Starts slide, duty sweep and arpeggio over every so many seconds: the trill of a powerup. 0 never. |
- An input that does nothing as things stand is **grey** in the inspector: *Frequency* while it
follows the note, *Duty* and *Duty sweep* for a wave that is no square, *Vibrato speed* at depth
0, *Arpeggio time* without an arpeggio. One that is connected counts as moving, whatever was typed
into it. *Follow note* is left off the node and the inspector while *Frequency* is connected.
- **Frequency and duty run on the audio thread** (AudioParams): an Envelope moves them while the
note plays, and `Set sound FX parameters` reaches one fed by a parameter, as with every other
number. A **Pitch** behind a Chip moves its frequency itself. The rest (slide, vibrato, arpeggio,
repeat, the wave) is fixed when the note starts; a parameter can still set it for every note.
- **It ends with its note**, faded out in 10 ms, or under an [Envelope](#envelopes) through its
release, and with *Sustain* 0 once the decay is over. Below *Min frequency* it ends by itself.
Envelope, Filter and Bitcrusher are nodes of their own; sfxr's punch is a short decay, its phaser
comes with #341.
- **How it is made:** a worklet of its own per note, next to the Pitch and the Bitcrusher in the
same module. Every sample is worked out 8 times and averaged, as sfxr does, which takes the worst
of the aliasing off a raw wave. Without the worklet a Chip is silent and its note ends at once.
The frequency follows sfxr (Tomas Pettersson, MIT licence) as jsfxr (public domain) ports it.
- **Measured** (2026-10-06, e2e 34, Chrome offline on the Windows runner): a square at 220 Hz with
a Pitch of 12 and a duty of 0.25, started at 0.25 s, was silent up to sample 12000 and then made
329 periods in 0.75 s, up 0.249 of each, peak 0.5. Sliding from 440 Hz down two octaves a second
with a lowest frequency of 110 Hz it fell silent at 1 s and said so. 64 voices of noise with a
slide and vibrato rendered 2 s in 2.0 s: about 63 at once, where a Pitch manages about 24. Live,
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.
## The Instrument tab ## The Instrument tab
The graph, and below it **the keys**: all 128 notes, the ones that sound in white and black, the The graph, and below it **the keys**: all 128 notes, the ones that sound in white and black, the
+1 -1
@@ -143,7 +143,7 @@ Input and the Output once each; their pins carry sound and fit nothing but each
| Node | What it does | | Node | What it does |
| --- | --- | | --- | --- |
| **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. | | **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. | | **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. | | **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** | 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. |
| **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). | | **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). |