diff --git a/Development.md b/Development.md index c5dba75..9a15c53 100644 --- a/Development.md +++ b/Development.md @@ -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) 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 -node tools/seed-e2e.mjs && node tools/e2e.mjs # 1840 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 1841 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 41df68f..37bf429 100644 --- a/Instruments.md +++ b/Instruments.md @@ -24,7 +24,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 | The effects of a [sound FX](Sounds#sound-fx), the same nodes. | +| Gain, Pitch, 5-Band-EQ, Filter, Mix, Delay, Bitcrusher | The effects of a [sound FX](Sounds#sound-fx), the same nodes. | **Play** of a Sample: @@ -117,9 +117,9 @@ sound as it is. 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 - the 5-Band-EQ and `mix` of a Mix. Not the time of a Delay, not the times of an Envelope, not a - parameter of a Sound FX node. A cutoff and a Gain move in equal ratios (octaves, decibels), - everything else evenly. Both ends are held to the range of the number it moves. + the 5-Band-EQ, `mix` of a Mix, and `bits`, `rate` and `mix` of a Bitcrusher. Not the time of a + Delay, not the times of an Envelope, not a parameter of a Sound FX node. A cutoff, a Gain and the + 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. - **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 own; one Envelope can feed several numbers. diff --git a/Sounds.md b/Sounds.md index 4fb3d37..4f3c7cd 100644 --- a/Sounds.md +++ b/Sounds.md @@ -148,12 +148,14 @@ Input and the Output once each; their pins carry sound and fit nothing but each | **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). | | **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). | 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 -`time` 0.01 to 8 and never more than 2 s, `feedback` 0 to 0.95, `damping` and `mix` 0 to 1), -whichever way the value arrives. +`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), whichever way the value +arrives. **How Pitch keeps the length** (#317). A way with a Pitch is played by an `AudioWorklet` on the audio thread, a **phase vocoder**: the sound is cut into frames of about 43 ms (2048 samples at @@ -267,6 +269,27 @@ 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. +### Bitcrusher + +A **Bitcrusher** (#322) runs on the same worklet as Pitch, sample by sample: + +- **`rate`** is sample-and-hold: a sample is taken `rate` times a second and held until the next. + 48000 Hz leaves the sound as it is, 8000 Hz is dull and metallic, a few hundred Hz a buzz of its + own. It never takes samples more often than the audio device makes them. +- **`bits`** rounds what is held to `2^(bits-1)` steps either side of 0: 16 is hardly heard, 8 + crackles, 4 and below hiss and buzz, 1 leaves only up, middle and down. Bits need not be whole, + 3.5 lies between 3 and 4, so a ramp on them never jumps. +- **`mix`** blends: at 1 only the crushed sound is heard, at 0.5 half of each. A `mix` typed in at + 0 leaves the Bitcrusher out. Fed from a parameter it stays, at whatever value. +- All three are numbers of the worklet, so *Set sound FX parameters* moves them while the sound + plays (`rate` in octaves, the other two in what they hold), and in an + [instrument](Instruments#envelopes) an Envelope can move them while a note plays. +- Without `AudioWorklet` (or if it cannot be loaded) the sound goes through it as it is, and the + console says why. The worklet is loaded with the first play that needs it. +- **Cost**: in e2e 34 headless Chrome on the CI's Windows machine rendered 64 stereo voices + through a Bitcrusher for 2 s in 1.9 s, so about **68 of them fill the audio thread** + (2026-10-06). There is no limit on how many play at once. + **Listening in the tab.** The header picks one of the project's sounds and plays it through the graph as it stands — the same library as a running game, so it sounds the way the game will. `Stop` there ends only what the tab started. Play is offered only while the graph compiles.