Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ GitHub are its section here (`tools/release.sh` copies them), so add to

## Unreleased

- **The SM-24 Mono has a patch panel.** Its new PATCH page holds four
cords. Drag a jack from the dock onto a ringed knob (LPF CUT, HPF CUT,
PW, VCO2 PITCH, LVL, MG FREQ), or pick SRC and DEST on a cord row. The
jacks are FENV, AENV and AENV inverted, MG, MG PUL, S&H, NOISE, KBD, VEL,
PRESS, VCO1, VCO2 and VCA OUT. The cords run at audio rate, so VCO2 can
FM the cutoff and the output can feed back into the filter. A patch
with no cords sounds exactly as before. Five new patches show it off
(`patch-*`), with the demo `songs/ms20_patch_bay.py`.

- **Squelch 33, an acid bassline synth.** A new mono machine built on the
HOT/WET filter the SM-24 Mono used to have: one SAW or SQR oscillator,
CUT, RES, ENV MOD, DECAY, ACCENT and SLIDE. Loud notes (the `!` in a
slabkit seq) are accented: louder, brighter, and a run of them climbs
higher with RES up. Overlapping notes slide. Presets: `classic-squelch`,
`square-bounce`, `wet-scream`, `round-line`; demo `songs/squelch_acid.py`.

- **SM-24 Mono sounds like an MS-20.** Its LPF MODE gains **K35** and
**OTA**, the early and late MS-20 filters. Bass stays full at any PEAK,
the peak sits on CUT, and both the HPF and LPF resonate up to
self-oscillation. K35 is the default: grittier and lopsided when driven.
OTA is cleaner and sings. HOT and WET are still there for the acid
sound. New presets: `k35-scream`, `k35-round-bass`, `ota-singing-lead`.
A new SM-24 starts from a re-voiced init patch; saved songs and presets
keep their mode and sound as before.

- **A harsh effects pack** (docs/43).
- **Octaver** gives one and two octaves down the way analog divider
pedals do, with a GLITCH knob to make it chatter, plus an
Expand Down
5 changes: 5 additions & 0 deletions bench/golden/acid.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# bench goldens for acid: case sha256(f32 L ++ f32 R) [nonharm_db thd_db]
notes 8bc59d45b766ad9007c4db1dbfcdd24b75f1c631ccc33814cc4ea2cc68ca9f61
velocity b52ab388bda9c4e65b9ebf3b22a557d2771ad44628677afb3fd1ac0e190fccfd
held 35a9aa5abc0a2babbc8df5521f5f2bd9614e162a47bd05b034765f7a33802a9c -76.1 5.5
high 0cd64ed21d5ec2f2fb40eb5b4fbcf3d60c2435d8efaf6f3f7a79a694ac46c5b1 -48.3 1.5
8 changes: 4 additions & 4 deletions bench/golden/ms20.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# bench goldens for ms20: case sha256(f32 L ++ f32 R) [nonharm_db thd_db]
notes a07508ad06ee8723008c3675e0ba735e56105fb80a75298e29acc4df1704d188
velocity ad9081885f858b343993a2bae2a8933f8152e0db0c9e95a3136cf678511c3859
held 78fe628e416bc871081d2d1aaaeb60b0df256d812f9314579f7cddebdcfd6ae8 -23.3 -0.8
high df662da24d2dd7cf90588facedc1e490e6250e35e1621b48657b76468bce0c68 -12.2 0.2
notes cb771ada7c0d5dde3546729c14c16efa53ee4191994848fccd503cdbe36c67e3
velocity 8e0cde2f8a794f10a1329f8f17f2ce2742c182977515a8e6ca3dda7acf5fd062
held 90baac0913232f60198a2b81cae395d4d31eb7cb17e4620418b6cd5026087a15 -50.0 -5.2
high 0de9e0cb902d5539ae2eff6c17697f52e0c239fc3122aab1a3a93a5db79f56d3 -30.1 -13.7
130 changes: 130 additions & 0 deletions docs/14-ms20-machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,72 @@ Scope call: **no full semi-modular patch bay.** We implement the MS-20's
*hardwired* routing (MG/EG→pitch, MG→cutoff, EG→cutoff/VCA, PWM, ring) with
fixed intensity controls. A general routing matrix can come later.

## K35 and OTA: the MS-20's own filters (2026-10-21)

HOT and WET sound acid: a TB-303, not an MS-20. Their loop subtracts a
clipped, 18 Hz high-passed copy of the lowpass from the input. That is the
ladder structure (the 303 even has the high-pass in its loop). Measured
with a numpy port of `ms20_lpf.fy` at CUT 500 Hz and a quiet input:

- PEAK costs 16 dB of bass at its old default (0.7), and 22–25 dB at the
acid presets.
- The peak sits 1.5–2.2 octaves above CUT.
- The peak moves about 0.7 octave with input level, which is the rubbery
squelch.
- The slope is −8 to −11 dB/oct.

Stinchcombe's study of the MS-10/MS-20 filters
([MS20_study.pdf](http://www.timstinchcombe.co.uk/synth/MS20_study.pdf))
gives both hardware generations LP = 1/(s² + (a − k)s + 1) and
HP = s²/(same). Here a = 2⅓ for the Korg35 Sallen-Key and 2 for the
LM13600 OTA pair. So the DC gain doesn't depend on PEAK, the cutoff
doesn't move, and the slope is 12 dB/oct.
`kernels/04-filters/ms20_sk.fy` implements it as **MODE K35** and
**MODE OTA**. HOT and WET stay as they were.

```
v = x − (a + g)·s1 − s2
bp-lin = (g·h·v + s1) / (1 − g·h·k) h = 1/(1 + a·g + g²)
fb = vd·tanh(k·bp-lin / vd) feedback diodes
u = vf·(tanh((drive·x + fb)/vf + fo) − tanh fo)
TPT SVF with base damping a → hp, bp, lp
```

- **K35:** the diodes sit in the forward amplifier (vf 0.55), so loud
input distorts every frequency. A small offset (fo 0.12) makes the
resonance lopsided: the self-oscillation's 2nd harmonic is −37 to
−46 dB. Both are Stinchcombe §4–5.
- **OTA:** the feedback diodes set the peak and the forward stage is soft
(vf 1.6), so the passband stays clean at normal DRIVE. The
self-oscillation is symmetric (2nd harmonic below −115 dB).
- **Both filters resonate.** In these modes the HPF is the same core with
its own PEAK. The series HPF → LPF pair runs at 4× between `up4` and
`dec4`, and both cutoffs stop at 15 kHz (Korg's spec).
- **PEAK taper:** k = a·(1 − u²) with u = 1 − (PEAK/2)/0.9, rising
linearly past 90%. This is a design choice, not a measured pot. A linear
pot repeats the 2026-09-26 OTA probe's "resonance invisible until the
top" (+1 dB at noon). This taper gives +6 dB at noon, and
self-oscillation from 1.82 of 2.

Measured through the voice in the bench, with noise in (CUT 500 Hz, HPF
1 kHz):

| | K35 | OTA |
|---|---|---|
| bass at 60 Hz, PEAK 0 → 2, small signal | −0.2 … +0.4 dB | −0.1 … +0.2 dB |
| peak position, PEAK ≥ 1.4 | within 0.02 oct of CUT | within 0.02 oct |
| slope, 2 → 3 octaves past the corner | −10.7 … −11.8 dB/oct | −11.2 … −12.1 dB/oct |
| peak at PEAK 1 (noon) | +6.4 dB | +8.1 dB |
| peak, quiet → loud input, PEAK 1.75 | 23.5 → 8.3 dB | 32.5 → 15.3 dB |
| HPF peak position / slope | within 0.04 oct / +11.5 … +12.6 | within 0.04 oct / +11.3 … +12.7 |
| default notes, non-harmonic energy | −31 … −41 dB (HOT −18 … −22) | −31 … −42 dB |
| cost, ReleaseFast, held note | ~2× HOT (≈ 350 vs 184 ns/smp) | same |

K35 is the default for new instances. The init patch was re-voiced
because CUT now means the corner: 1200 Hz, PEAK 1, ENV 2 octaves,
filter decay 0.3 s, sustain 0.2. The acid pair is kept, both here and in
`ms20_lpf.fy` for a future acid machine.

## LPF v3: the June probe's driven SVF, in fy (2026-09-27)

The reference is `scratch/ms20_saw_filter_sweeps_f-hot.wav`, rendered by
Expand Down Expand Up @@ -285,3 +351,67 @@ hatch) is a good early proof.
7. Panel: declare the strips (docs/15); custom ADSR cell.
8. Global spilling (durable follow-up) to optionally collapse hot stages back
inline; polyphony via a higher-order voice-pool machine (docs/15).

## Squelch 33: the acid filter's own synth (2026-10-22)

HOT/WET stayed in the SM-24 Mono, and the same kernel (`ms20_lpf.fy`,
unchanged) now also drives `machines/acid`, a 303-style mono voice in
`kernels/06-voices/acid_voice.fy`: one VCO (saw, or a square with a
little of the ramp on its tops), the LPF at 4x, a decay-only MEG with
ENV MOD (5 octaves at full), a VCA that decays over 3 s and closes in
8 ms on key-up.

- **Accent** is velocity over 0.8. An accented note runs a second MEG
at the 200 ms minimum decay; that envelope pushes the VCA directly
(x[1 + 1.2 ACC]) and feeds the accent RC (15 ms, 60 ms at full RES),
whose output adds 4 octaves x level to the cutoff. With RES up the RC
does not empty between notes, so accents in a row stack.
- **Slide**: a note that overlaps the last (`ctx.legato`) glides on an
RC covering 3 time constants in SLIDE, and retriggers nothing.

Measured (slabkit renders, A1 lines, 120-128 BPM):

| | target | result |
|---|---|---|
| accent level | +4..6 dB | +4.3..5.3 dB at RES 0.3, +3.3..4.6 at 0.9 |
| accent attack centroid | brighter | +40% at RES 0.3, +20% at 0.9 |
| accent stacking, 3 in a row | climbs at high RES | +15% at RES 0.9, +5% at 0.3 |
| fundamental loss, RES 0 -> 1 | the acid thinning | -13 dB HOT, -13.4 dB WET |
| loudest partial, CUT 400 | 1.5-2 oct up | 654 Hz HOT, 785 Hz WET |
| slide 60 ms | lands in ~60 ms | ~50-60 ms, no retrigger |
| preset loudness on a line | -20 dB RMS +-1 | -20.0 .. -19.6 |
| cost | | 233 ns/sample ReleaseFast |

## The patch panel (2026-10-22)

Four cords, evaluated every sample in the voice (`ms20-bay`,
`ms20-cord-to` in `kernels/06-voices/ms20_voice_probe.fy`), declared to
the panel the way Concoction's matrix is (docs/15 §Modulation): controls
`ms20-c1-src` / `-dst` / `-amt` ... `ms20-c4-*`, `mod-source` jacks in the
dock, `mod-dest` rings, the normalled routes (ENV and MG to LPF CUT, EG
and MG to PITCH, MG to PW) as fixed rows, a PATCH page with the dock,
the CORDS matrix display and a scope.

- **Jacks** are an array in the voice state (`jk`), one per SRC option,
stored each sample and read by a runtime index, which sees the state
as the word found it: every source arrives one sample late. That is
what lets VCA OUT go back into the filter without a zero-delay loop.
- **Destinations** and their range at AMT 1: PITCH and VCO2 +-2
octaves, PW +-0.45, HPF CUT and LPF CUT +-5 octaves (KBD at AMT 0.2
tracks the keyboard 1:1), VCA gain +-1, MG FREQ +-4 octaves, FLT IN the
source as audio at the mixer's level, ahead of both filters.
- **Free when empty.** The bay sits behind `bay-on` (a cord with SRC and
DEST set), the per-sample HPF coefficients behind `bay-hpf`; both are
versioned `ifte`s, and every unpatched path adds exact zeros, so all
goldens stay bit-exact. VCO1's wave became a select so the voice fits
fy's 32 versioned bodies (VCO2 wave 4 x MODE 2 x 2 x 2); past that the
bay if-converted and cost 609 ns/sample unpatched.
- Cost, ReleaseFast: 413 ns/sample unpatched, 470 with a cord.

Measured on a held C3 (K35, CUT 600, PEAK 1, ENV 0): MG -> LPF CUT 0.3
triples the centroid's swing (66 -> 190 Hz); MG -> PITCH 0.05 gives
vibrato; AENV -> VCA -0.5 takes 6 dB off; VCO2 a fifth up -> LPF CUT
0.3 doubles the centroid (418 -> 866 Hz) at audio rate; VCA OUT -> FLT IN
0.8 stays bounded (peak -6.9 dBFS). The `patch-*` presets sit at -21 to
-22 dB RMS on a bassline (the vowel patch, a band-pass, -22 two octaves
up).
60 changes: 53 additions & 7 deletions docs/20-machine-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ project or preset stores (docs/19): real units for knobs, the option

| id | name | kind | voices | prefix |
|---|---|---|---|---|
| [`acid`](#acid) | Squelch 33 | instrument | mono | `—` |
| [`bass`](#bass) | Fretwire | instrument | 4 | `bs-` |
| [`concoction`](#concoction) | Concoction | instrument | 8 | `cn-` |
| [`cream`](#cream) | Mog Passenger | instrument | mono | `cr-` |
Expand Down Expand Up @@ -51,6 +52,31 @@ project or preset stores (docs/19): real units for knobs, the option
| [`verb2`](#verb2) | Verb | effect | — | `verb-` |
| [`wob2`](#wob2) | Wobble | effect | — | `wob-` |

## acid

**Squelch 33**, instrument, mono.

| param | section | label | range | default | notes |
|---|---|---|---|---|---|
| `tune` | VCO | TUNE | -12 … 12 | 0 | linear |
| `wave` | VCO | WAVE | 0 SAW / 1 SQR | 0 | switch |
| `cutoff` | VCF | CUT | 30 … 8000 | 260 | exp |
| `resonance` | VCF | RES | 0 … 1 | 0.6 | linear |
| `env-mod` | VCF | ENV | 0 … 1 | 0.5 | linear |
| `decay` | VCF | DEC | 0.2 … 2 | 0.5 | exp |
| `accent` | PLAY | ACC | 0 … 1 | 0.7 | linear |
| `slide` | PLAY | SLIDE | 0.02 … 0.3 | 0.06 | exp |
| `drive` | DRIVE | DRV | 0.25 … 4 | 1 | exp |
| `mode` | DRIVE | MODE | 0 HOT / 1 WET | 0 | switch |
| `level` | OUT | LVL | 0 … 1 | 0.68 | pow |

Presets:

- `classic-squelch` — Saw, RES high, a medium sweep: accents bark and stack
- `round-line` — Low RES and little sweep: a round bassline under a mix
- `square-bounce` — Square, short decay, moderate RES: a hollow rubbery line
- `wet-scream` — WET mode driven, RES near the top: screaming accents

## bass

**Fretwire**, instrument, 4 voices.
Expand Down Expand Up @@ -964,11 +990,11 @@ Presets:
| `noise-level` | MIX | NOISE | 0 … 1 | 0 | pow |
| `hpf-cutoff` | HPF | CUT | 20 … 15000 | 20 | exp |
| `hpf-resonance` | HPF | PEAK | 0 … 2 | 0 | linear |
| `cutoff` | LPF | CUT | 20 … 18000 | 700 | exp |
| `resonance` | LPF | PEAK | 0 … 2 | 0.7 | linear |
| `cutoff` | LPF | CUT | 20 … 18000 | 1200 | exp |
| `resonance` | LPF | PEAK | 0 … 2 | 1 | linear |
| `drive` | LPF | DRV | 0.25 … 4 | 1 | exp |
| `lpf-mode` | LPF | MODE | 0 HOT / 1 WET | 0 | switch |
| `env-amount` | LPF | ENV | 0 … 8 | 4.8 | linear |
| `lpf-mode` | LPF | MODE | 0 HOT / 1 WET / 2 K35 / 3 OTA | 2 | switch |
| `env-amount` | LPF | ENV | 0 … 8 | 2 | linear |
| `level` | VCA | LVL | 0 … 1 | 0.44 | pow |
| `age` | VCA | AGE | 0 … 1 | 0.4 | linear |
| `amp-hold` | AMP ENV | HOLD | 0 … 20 | 0 | pow |
Expand All @@ -978,8 +1004,8 @@ Presets:
| `amp-release` | AMP ENV | REL | 0.01 … 10 | 0.13 | exp |
| `filter-delay` | FLT ENV | DLY | 0 … 10 | 0 | pow |
| `filter-attack` | FLT ENV | ATK | 0.001 … 10 | 0.014 | exp |
| `filter-decay` | FLT ENV | DEC | 0.01 … 10 | 0.14 | exp |
| `filter-sustain` | FLT ENV | SUS | 0 … 1 | 0.18 | linear |
| `filter-decay` | FLT ENV | DEC | 0.01 … 10 | 0.3 | exp |
| `filter-sustain` | FLT ENV | SUS | 0 … 1 | 0.2 | linear |
| `filter-release` | FLT ENV | REL | 0.01 … 10 | 0.11 | exp |
| `mg-freq` | MG | FREQ | 0.1 … 20 | 3 | exp |
| `mg-wave` | MG | WAVE | 0 … 1 | 0.5 | linear |
Expand All @@ -988,16 +1014,36 @@ Presets:
| `mg-cutoff` | MOD | MG>CUT | 0 … 4 | 0 | pow |
| `eg-pitch` | MOD | EG>PIT | 0 … 1 | 0 | pow |
| `portamento` | MOD | PORTA | 0 … 10 | 0 | pow |
| `ms20-c1-src` | CORD 1 | SRC | 0 OFF / 1 FENV / 2 AENV / 3 AENV- / 4 MG / 5 MG PUL / 6 S&H / 7 NOISE / 8 KBD / 9 VEL / 10 PRESS / 11 VCO1 / 12 VCO2 / 13 VCA OUT | 0 | switch |
| `ms20-c1-dst` | CORD 1 | DEST | 0 OFF / 1 PITCH / 2 VCO2 / 3 PW / 4 HPF CUT / 5 LPF CUT / 6 VCA / 7 MG FREQ / 8 FLT IN | 0 | switch |
| `ms20-c1-amt` | CORD 1 | AMT | -1 … 1 | 0 | linear |
| `ms20-c2-src` | CORD 2 | SRC | 0 OFF / 1 FENV / 2 AENV / 3 AENV- / 4 MG / 5 MG PUL / 6 S&H / 7 NOISE / 8 KBD / 9 VEL / 10 PRESS / 11 VCO1 / 12 VCO2 / 13 VCA OUT | 0 | switch |
| `ms20-c2-dst` | CORD 2 | DEST | 0 OFF / 1 PITCH / 2 VCO2 / 3 PW / 4 HPF CUT / 5 LPF CUT / 6 VCA / 7 MG FREQ / 8 FLT IN | 0 | switch |
| `ms20-c2-amt` | CORD 2 | AMT | -1 … 1 | 0 | linear |
| `ms20-c3-src` | CORD 3 | SRC | 0 OFF / 1 FENV / 2 AENV / 3 AENV- / 4 MG / 5 MG PUL / 6 S&H / 7 NOISE / 8 KBD / 9 VEL / 10 PRESS / 11 VCO1 / 12 VCO2 / 13 VCA OUT | 0 | switch |
| `ms20-c3-dst` | CORD 3 | DEST | 0 OFF / 1 PITCH / 2 VCO2 / 3 PW / 4 HPF CUT / 5 LPF CUT / 6 VCA / 7 MG FREQ / 8 FLT IN | 0 | switch |
| `ms20-c3-amt` | CORD 3 | AMT | -1 … 1 | 0 | linear |
| `ms20-c4-src` | CORD 4 | SRC | 0 OFF / 1 FENV / 2 AENV / 3 AENV- / 4 MG / 5 MG PUL / 6 S&H / 7 NOISE / 8 KBD / 9 VEL / 10 PRESS / 11 VCO1 / 12 VCO2 / 13 VCA OUT | 0 | switch |
| `ms20-c4-dst` | CORD 4 | DEST | 0 OFF / 1 PITCH / 2 VCO2 / 3 PW / 4 HPF CUT / 5 LPF CUT / 6 VCA / 7 MG FREQ / 8 FLT IN | 0 | switch |
| `ms20-c4-amt` | CORD 4 | AMT | -1 … 1 | 0 | linear |

Presets:

- `acid-bass` — Squelchy resonant bass — play low, ride LPF CUT
- `aggressive-fill-lead` — Short aggressive lead / fill
- `buzz-lead` — Bright buzzy lead with a touch of MG wobble
- `k35-round-bass` — K35 bass: full low end, a gentle peak, short EG2 sweep
- `k35-scream` — K35 filters both peaked and driven: the early MS-20 scream
- `mild-acid-bass` — Mild acid, not techno
- `minimal-8th-bass` — Minimal 8th-note spoken-verse bass
- `nasal-sneer-lead` — Nasal sneering mono lead
- `octave-night-bass` — Faster octave night-drive bass
- `ota-singing-lead` — OTA LPF, peak high and clean: the later MS-20, symmetric and singing
- `patch-feedback-growl` — VCA OUT back into FLT IN: the OTA growls and sings as the note swells
- `patch-fm-metal-bass` — VCO2 a fifth up into LPF CUT at audio rate: a metallic, clangy bass
- `patch-sh-bubbles` — S&H on LPF CUT, clocked by the MG: random bubbling under a peaked K35
- `patch-tracking-lead` — KBD on LPF CUT for full tracking, MG on PITCH for a slow vibrato, PRESS opens it
- `patch-vowel-sweep` — FENV on HPF CUT with both filters peaked: a talking band-pass sweep
- `portamento-highway-lead` — Smooth portamento highway lead
- `resonant-funk-bass` — Resonant funky bass with filter bite
- `rising-filter-fx` — Overdriven rising-filter FX sweep
Expand Down Expand Up @@ -1620,7 +1666,7 @@ Presets:

- `parallel-squash` — Parallel compression: the dry signal beside a crushed, saturated copy. AMOUNT brings the copy up under it.
- `harsh/bit-guitar` — Lows cut, crushed to 8 bits at 11 kHz, into a clean amp: aliased, zippy guitar.
- `harsh/cross-bass` — Justice-style bass: a clean sub lane, and a dirty lane cut below 250 Hz, crushed to 12 bits, overdriven, through a Brit 4x12, slammed by a FET and taped.
- `harsh/cross-bass` — Justice-style bass: a clean sub lane, and a dirty lane cut below 250 Hz, crushed to 12 bits, overdriven, through a bright Brit 4x12 and slammed by a FET.
- `harsh/four-slot` — Distructor's layout: distortion, filter, chorus and amp in four slots, each slot's mix on a knob.
- `harsh/gated-fuzz` — A starved fuzz into a fast gate: notes sputter and cut off dead.
- `harsh/kodiak` — Big and gnarly: a clean low-passed sub lane under a lane of octave-down, dynamic folding, RAT and OTT. SIZE is the sub, GNARL the dirt.
Expand Down
Loading
Loading