Skip to content
This repository was archived by the owner on Oct 31, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a381554
Ceiling kit: a class can say which settings clip by design
bdbarnett Oct 27, 2026
d009a12
Ceiling kit: an effect's test can hold its stated level
bdbarnett Oct 27, 2026
9c145f5
Seventeen effects state the input level they are clean at
bdbarnett Oct 27, 2026
56589f0
AnalogDelay: says the input level it is clean at
bdbarnett Oct 27, 2026
7980e2e
Bitcrusher: says the input level it is clean at
bdbarnett Oct 27, 2026
ea02a46
CombFilter: says the input level it is clean at
bdbarnett Oct 27, 2026
ddfacbf
DigitalDelay: says the input level it is clean at
bdbarnett Oct 27, 2026
9d62c9d
HighPass: says the input level it is clean at
bdbarnett Oct 27, 2026
8e8792a
LadderFilter: says the input level it is clean at
bdbarnett Oct 27, 2026
dc0e877
LowPass: says the input level it is clean at
bdbarnett Oct 27, 2026
93b2109
MultiTapDelay: says the input level it is clean at
bdbarnett Oct 27, 2026
9053b3a
MultibandCompressor: says the input level it is clean at
bdbarnett Oct 27, 2026
04525c2
Notch: says the input level it is clean at
bdbarnett Oct 27, 2026
b3737ef
ParametricEQ: says the input level it is clean at
bdbarnett Oct 27, 2026
c154924
Phaser: says the input level it is clean at
bdbarnett Oct 27, 2026
f18015c
PingPongDelay: says the input level it is clean at
bdbarnett Oct 27, 2026
7f91be6
Reverb: says the input level it is clean at
bdbarnett Oct 27, 2026
a5fca33
SlapbackDelay: says the input level it is clean at
bdbarnett Oct 27, 2026
6f64bb8
TapeDelay: says the input level it is clean at
bdbarnett Oct 27, 2026
f9d556f
TransientShaper: says the input level it is clean at
bdbarnett Oct 27, 2026
5b4a79a
Ten effects name the knobs that clip at their top
bdbarnett Oct 27, 2026
6e60fb6
One stated level: the older plucked-phrase levels go
bdbarnett Oct 27, 2026
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
7 changes: 7 additions & 0 deletions lib/audioeffects/analogdelay.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ class AnalogDelay(_component.Component):

**Level and tail.** Below Mix 1, an input peaking at or below
floor(32767 (1 - Mix)) - 1 does not reach the rail.
Keep the input under -6 dBFS and the defaults and patches stay off the
rails.
`tail_samples` is an upper bound on how long the repeats take to reach
exact zero after your input stops.
`reset()` empties the line and returns to patch 0.
Expand Down Expand Up @@ -221,6 +223,11 @@ class AnalogDelay(_component.Component):
LATENCY_SAMPLES = 0
TAIL_SAMPLES = None

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -3 dBFS, the defaults rail on
#: a 100 Hz square.
INPUT_CEILING_DBFS = -6

MACRO_LABELS = ("Time", "Feedback", "Mix", "Modulation", "Mod Rate",
"Spread", "Sync", "Division")
MACRO_MODES = {
Expand Down
10 changes: 8 additions & 2 deletions lib/audioeffects/bitcrusher.py
Original file line number Diff line number Diff line change
Expand Up @@ -1375,8 +1375,9 @@ class Bitcrusher(_component.Component):

**Mix and Output.** Mix 0 is a wire at the source's own level. Output
trims the whole blend: -6 dB is 6 dB down within 0.1 dB, at Mix 1 and
at a blend. The Effect controls page lists what the knobs span and the
patches.
at a blend. Keep the input under -3 dBFS and the defaults and patches
stay off the rails. The Effect controls page lists what the knobs span
and the patches.



Expand All @@ -1400,6 +1401,11 @@ class Bitcrusher(_component.Component):
LATENCY_SAMPLES = 0
TAIL_SAMPLES = 0

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at full scale, patch 1 rails on
#: a 50 Hz sine.
INPUT_CEILING_DBFS = -3

MACRO_LABELS = ("Bits", "Rate", "Dither", "Band Limit", "Mix", "Output")
MACRO_MODES = {
0: "UNIPOLAR", 1: "UNIPOLAR", 2: "UNIPOLAR", 3: "TOGGLE",
Expand Down
13 changes: 10 additions & 3 deletions lib/audioeffects/combfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,9 @@ class CombFilter(_component.Component):
**Glide.** Glide slides the tuning, so turning Frequency bends the note
rather than clicking.

**Level.** A comb gains, so a loud input can reach the rail by design.
A plucked phrase is clean on every patch up to -9 dBFS; at -3 dBFS
Ringing Pitch, Dark Resonator, Single Slap and Filter Only rail.
**Level.** A comb gains, so a loud input can reach the rail by design. Keep
the input under -24 dBFS and the defaults and patches stay off the rails.
Feedback or Trim at its top can still push the output onto the rails.
`reset()` empties the line.

**Latency and tail.** It adds no latency at any setting. The tail
Expand Down Expand Up @@ -294,6 +294,13 @@ class CombFilter(_component.Component):
#: `tail_samples`, the last of its decay.
TAIL_SAMPLES = None

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -18 dBFS, patch 1 rails on
#: a DC step.
INPUT_CEILING_DBFS = -24
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (1, 4)

MACRO_LABELS = ("Frequency", "Feedback", "Mix", "Tone", "Trim", "Glide")
MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "UNIPOLAR",
3: "UNIPOLAR", 4: "BIPOLAR", 5: "UNIPOLAR"}
Expand Down
8 changes: 7 additions & 1 deletion lib/audioeffects/digitaldelay.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
below Nyquist and sound the same. The dry sits at unity and the repeats add
to it, so a hot input can reach the int16 rail. With Repeat Cut out and Mix
below 1, an input that peaks at or below floor(32767 (1 - Mix)) - 1 cannot
reach the rail, at any Time or Feedback.
reach the rail, at any Time or Feedback. Keep the input under -6 dBFS and
the defaults and patches stay off the rails.

**Limits shared by the family.** A control that jumps makes the output step:
move it in small steps from the host if you need it smooth. When your source
Expand Down Expand Up @@ -324,6 +325,11 @@ class DigitalDelay(_component.Component):
LATENCY_SAMPLES = 0
TAIL_SAMPLES = None

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -3 dBFS, patch 3 rails on
#: a 100 Hz square.
INPUT_CEILING_DBFS = -6

MACRO_LABELS = ("Time", "Feedback", "Mix", "Glide", "Sync", "Division",
"Repeat Tone", "Repeat Cut")
MACRO_MODES = {
Expand Down
6 changes: 5 additions & 1 deletion lib/audioeffects/graphiceq.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ class GraphicEQ(_component.Component):
**Level.** Bands overlap and add: three adjacent sliders at +6 dB lift
the middle one's frequency by more than 7 dB. Every section hands the
next an int16, so leave headroom, and pull Volume down when you push
Gain up.
Gain up. A band, Gain or Volume at its top can push a full-scale input
onto the rails.

**Patches.** Scooped Mids, Pushed Mids and Full Boost pull Volume down,
so stepping through them you hear the curve rather than the level. A
Expand Down Expand Up @@ -250,6 +251,9 @@ class GraphicEQ(_component.Component):
LATENCY_SAMPLES = 0
TAIL_SAMPLES = _DESIGN_TAIL_SAMPLES

#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)

MACRO_LABELS = ("31", "63", "125", "250", "500", "1k", "2k", "4k", "8k",
"16k Shelf", "Gain", "Volume", "Band Q", "Constant Q")
MACRO_MODES = {0: "BIPOLAR", 1: "BIPOLAR", 2: "BIPOLAR", 3: "BIPOLAR",
Expand Down
21 changes: 15 additions & 6 deletions lib/audioeffects/highpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
**What it sounds like.** One knob decides where the bottom stops, and above
that corner nothing is touched: rumble, handling noise, a subsonic DC offset
and the mud under a vocal go, and the voice does not. The second knob is how
loud the corner itself stands - at Resonance 0.707 it is 3 dB down and the
cut is as gentle as a cut gets, at 2 it is 6 dB up and you can hear where you
put it, at 16 it is 24 dB up and the filter whistles at its own corner. The
Slope switch is a desk's: 12 dB/oct for a low-cut you do not want to notice,
24 dB/oct when the rumble has to actually leave. There is no drive and no
growl anywhere in it - a high-pass is linear.
loud the corner itself stands - at Resonance 0.707 it is 3 dB down and the cut
is as gentle as a cut gets, at 2 it is 6 dB up and you can hear where you put
it, at 16 it is 24 dB up and the filter whistles at its own corner. The Slope
switch is a desk's: 12 dB/oct for a low-cut you do not want to notice, 24
dB/oct when the rumble has to actually leave. There is no drive and no growl
anywhere in it - a high-pass is linear. Keep the input under -9 dBFS and the
defaults and patches stay off the rails. Trim at its top can still push the
output onto the rails.

**Three things the knobs do not do exactly, found by the Phase 2 gate audit
and its patch sweep, and
Expand Down Expand Up @@ -197,6 +199,13 @@ class HighPass(_component.Component):
#: the drive stops.
TAIL_SAMPLES = 632832

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -6 dBFS, patch 1 rails on
#: a 100 Hz square.
INPUT_CEILING_DBFS = -9
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (4,)

MACRO_LABELS = ("Frequency", "Resonance", "Slope", "Mix", "Trim")
MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "TOGGLE",
3: "UNIPOLAR", 4: "BIPOLAR"}
Expand Down
13 changes: 10 additions & 3 deletions lib/audioeffects/ladderfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ class LadderFilter(_component.Component):
no tail bound, and `reset()` stops it at any setting.

**Levels.** The resonance peak and the drive both add gain, so no input
level is clean on every setting. A plucked phrase stays off the rails on
every patch to -10 dBFS; at -3 dBFS Growl With Drive rails. Turn Drive,
Resonance or your input down.
level is clean on every setting. Keep the input under -15 dBFS and the
defaults and patches stay off the rails. Drive at its top can still push
the output onto the rails. Turn Drive, Resonance or your input down.

**On a board.** The class needs audiodsp's `audioladder`, and on a board
without it construction raises `ImportError`.
Expand All @@ -107,6 +107,13 @@ class LadderFilter(_component.Component):
#: bounds the decay; at and above it the filter sustains by design.
TAIL_SAMPLES = None

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -12 dBFS, patch 4 rails on
#: a 100 Hz square.
INPUT_CEILING_DBFS = -15
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (2,)

MACRO_LABELS = ("Cutoff", "Resonance", "Drive", "Passband Comp",
"Poles", "Oversample", "Mix")
MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "UNIPOLAR",
Expand Down
20 changes: 14 additions & 6 deletions lib/audioeffects/lowpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@

**What it sounds like.** One knob slides the whole curve along the frequency
axis without changing its shape, and one knob decides how loud the corner
itself stands: at Resonance 0.707 the corner is 3 dB down and the curve is
the gentlest roll-off there is, at 2 it is 6 dB up and the filter starts to
whistle where you put it, at 16 it is 24 dB up and a click at the input rings
for seconds. There is no drive, no saturation and no growl anywhere in it -
a low-pass is linear, and the thing that makes a Moog a Moog belongs to
itself stands: at Resonance 0.707 the corner is 3 dB down and the curve is the
gentlest roll-off there is, at 2 it is 6 dB up and the filter starts to whistle
where you put it, at 16 it is 24 dB up and a click at the input rings for
seconds. There is no drive, no saturation and no growl anywhere in it - a
low-pass is linear, and the thing that makes a Moog a Moog belongs to
`LadderFilter`. The Slope switch is a console's: 12 dB/oct is the analog
prototype, 24 dB/oct is a second section behind it for when a rumble has to
actually go away.
actually go away. Keep the input under -3 dBFS and the defaults and patches
stay off the rails. Trim at its top can still push the output onto the rails.

**Where those two sentences stop being exact.** The Phase 2 gate audit
(2026-09-07) disconfirmed four of the five traits off the point they were
Expand Down Expand Up @@ -172,6 +173,13 @@ class LowPass(_component.Component):
#: input stopped outlived (279 149 at patch 4).
TAIL_SAMPLES = 528261

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at full scale, patch 5 rails on
#: a 50 Hz sine.
INPUT_CEILING_DBFS = -3
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (4,)

MACRO_LABELS = ("Frequency", "Resonance", "Slope", "Mix", "Trim")
MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "TOGGLE",
3: "UNIPOLAR", 4: "BIPOLAR"}
Expand Down
15 changes: 12 additions & 3 deletions lib/audioeffects/multibandcompressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,11 @@ class MultibandCompressor(_component.Component):

**Mix.** Mix crossfades the input with the summed bands, so between its
stops it is parallel compression. Mix 0 is the source byte for byte, at
22.05 kHz, 44.1 kHz and 48 kHz, mono and stereo, and moving off it does
not click. Brought back up from 0, it starts as it was built rather
than playing what the bands held.
22.05 kHz, 44.1 kHz and 48 kHz, mono and stereo, and moving off it does not
click. Brought back up from 0, it starts as it was built rather than
playing what the bands held. Keep the input under -9 dBFS and the defaults
and patches stay off the rails. A band's Gain at its top can still push the
output onto the rails.

**Latency and tail.** It adds no latency at any setting. The tail
reaches exact zero, and `tail_samples` says when.
Expand Down Expand Up @@ -304,6 +306,13 @@ class MultibandCompressor(_component.Component):
#: coupling state" in docs/reference/component-api.md.
TAIL_SAMPLES = 0

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -6 dBFS, patch 3 rails on
#: a 147 Hz square.
INPUT_CEILING_DBFS = -9
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (8, 9, 10)

MACRO_LABELS = (
"Crossover Low", "Crossover High",
"Low Threshold", "Mid Threshold", "High Threshold",
Expand Down
7 changes: 7 additions & 0 deletions lib/audioeffects/multitapdelay.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ class MultiTapDelay(_component.Component):
Up to Mix 1 the dry passes at unity, and at Mix 2 the echoes play alone.
At unity the dry is exact to the sample, mono and stereo, right up to
the rails, on every interpreter.
Keep the input under -6 dBFS and the defaults and patches stay off the
rails.
Mix 0 is a wire.
Sync locks Time to Division of the host's beat, clamped to Time's span.
With no host tempo, Time stays on the knob.
Expand Down Expand Up @@ -273,6 +275,11 @@ class MultiTapDelay(_component.Component):
LATENCY_SAMPLES = 0
TAIL_SAMPLES = None

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -3 dBFS, the defaults rail on
#: a 100 Hz square.
INPUT_CEILING_DBFS = -6

MACRO_LABELS = ("Time", "Pattern", "Heads", "Feedback", "Mix", "Tilt",
"Repeat Tone", "Sync", "Division")
MACRO_MODES = {
Expand Down
28 changes: 18 additions & 10 deletions lib/audioeffects/notch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
its dossier. The old `eq.py:Notch` is not consulted except for
the seven defects the dossier's section 7 names.

**What it sounds like.** One knob says where, one says how wide, and what
comes out is the input with a slice taken out of it and nothing else
touched - a whistle, a feedback tone or a mains hum goes and the rest of the
mix does not move, because a notch is exactly unity at DC and at Nyquist by
construction. Width is a bandwidth, not a depth: at Q 0.7 the notch is
wider than the note it sits on and reads as a scoop; at Q 30 it is a few
hertz across and you hear the tone leave without hearing the music change.
Harmonics adds a second notch an octave up, half as wide in hertz, because
mains hum is a series and one notch at 50 or 60 Hz leaves the buzz. Depth is
a blend, not a filter control - at 0 the class is a wire.
**What it sounds like.** One knob says where, one says how wide, and what comes
out is the input with a slice taken out of it and nothing else touched - a
whistle, a feedback tone or a mains hum goes and the rest of the mix does not
move, because a notch is exactly unity at DC and at Nyquist by construction.
Width is a bandwidth, not a depth: at Q 0.7 the notch is wider than the note it
sits on and reads as a scoop; at Q 30 it is a few hertz across and you hear the
tone leave without hearing the music change. Harmonics adds a second notch an
octave up, half as wide in hertz, because mains hum is a series and one notch
at 50 or 60 Hz leaves the buzz. Depth is a blend, not a filter control - at 0
the class is a wire. Keep the input under -6 dBFS and the defaults and patches
stay off the rails. Trim at its top can still push the output onto the rails.

**Portability tier: audiodsp** (`REQUIRES = ("audiobiquad",)`). All three
sections are `audiobiquad.Biquad`: float state, and a tail that reaches
Expand Down Expand Up @@ -134,6 +135,13 @@ class Notch(_component.Component):
#: input stopped outlived by up to twice.
TAIL_SAMPLES = 965439

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -3 dBFS, patch 1 rails on
#: a 100 Hz square.
INPUT_CEILING_DBFS = -6
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (4,)

MACRO_LABELS = ("Frequency", "Width", "Harmonics", "Depth", "Trim")
MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "TOGGLE",
3: "UNIPOLAR", 4: "BIPOLAR"}
Expand Down
15 changes: 12 additions & 3 deletions lib/audioeffects/parametriceq.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@

All of it is the clip, not the filter: the coefficients are reciprocal and
proportional at every level, which is what
`tests/test_cpython_effects_parametriceq.py` reads. Drive this EQ from a
signal with headroom for the boost you dial, or trim into it, and the curves
are the ones the traits describe.
`tests/test_cpython_effects_parametriceq.py` reads. Drive this EQ from a signal
with headroom for the boost you dial, or trim into it, and the curves are the
ones the traits describe. Keep the input under -12 dBFS and the defaults and
patches stay off the rails. A boost or Output at its top can still push the
output onto the rails.

**Cost.** Eight sections, always built, and no patch changes that. Measured
2026-09-07 on both boards at construction defaults - where patch 0 is a wire,
Expand Down Expand Up @@ -216,6 +218,13 @@ class ParametricEQ(_component.Component):
#: ceiling at every rate. Patch 0 is a wire and its tail is zero.
TAIL_SAMPLES = 37696

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -9 dBFS, patch 3 rails on
#: a DC step.
INPUT_CEILING_DBFS = -12
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (1, 4, 6, 8, 11, 15)

MACRO_LABELS = (
"Low Freq", "Low Boost", "Low Atten",
"Bell 1 Freq", "Bell 1 Gain",
Expand Down
12 changes: 10 additions & 2 deletions lib/audioeffects/phaser.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,9 @@ class Phaser(_component.Component):

**Drive and Feedback.** Drive is 0.3 when you construct the class, and
there the Feedback hump stays under 5 dB. Turn Drive to 0 and Feedback
lifts it more than 5 dB. The Phaser - lean patch is the default with
Drive at 0.
lifts it more than 5 dB. The Phaser - lean patch is the default with Drive
at 0. Keep the input under -9 dBFS and the defaults and patches stay off
the rails. Feedback at its top can still push the output onto the rails.

**Sync.** With Sync on, the sweep locks to the host: Division picks the
length of a sweep in bars, a bar is read from the host's time
Expand Down Expand Up @@ -328,6 +329,13 @@ class reports the worst case, 552960 samples, for stages at their
#: comment; musical settings die much sooner. Ceiling, next 2048.
TAIL_SAMPLES = 552960

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -6 dBFS, patch 1 rails on
#: a 147 Hz square.
INPUT_CEILING_DBFS = -9
#: Settings that clip by design: the rail check skips them.
CLIPS_AT_TOP = (3,)

MACRO_LABELS = ("Rate", "Depth", "Centre", "Feedback", "Mix",
"Stages", "Shape", "Drive", "Sync", "Division")
MACRO_MODES = {
Expand Down
7 changes: 7 additions & 0 deletions lib/audioeffects/pingpongdelay.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class PingPongDelay(_component.Component):
input that peaks at or below floor(32767 (1 - Mix)) - 1 cannot reach the
rail, at any Time, Feedback or Spread. Repeat Cut's high-pass
overshoots, so with it in leave more room.
Keep the input under -6 dBFS and the defaults and patches stay off the
rails.

**Limits shared by the family.** A control that jumps makes the output
step: move it in small steps from the host if you need it smooth. When
Expand Down Expand Up @@ -168,6 +170,11 @@ class PingPongDelay(_component.Component):
LATENCY_SAMPLES = 0
TAIL_SAMPLES = None

#: The defaults and every patch play the ceiling kit's materials
#: clean at this level; at -3 dBFS, patch 3 rails on
#: a DC step.
INPUT_CEILING_DBFS = -6

MACRO_LABELS = ("Time", "Feedback", "Mix", "Spread", "First Side",
"Sync", "Division", "Repeat Tone", "Repeat Cut")
MACRO_MODES = {
Expand Down
Loading
Loading