From a3815541bb70f53077a301ad5e3c94b98f054b95 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:54:23 -0500 Subject: [PATCH 01/22] Ceiling kit: a class can say which settings clip by design --- tests/support/kit_ceiling.py | 25 +++++++++++++++++++++---- tests/support/kit_planted.py | 20 ++++++++++++++++++++ tests/test_kit.py | 11 +++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/tests/support/kit_ceiling.py b/tests/support/kit_ceiling.py index a024b03b..d95dd92f 100644 --- a/tests/support/kit_ceiling.py +++ b/tests/support/kit_ceiling.py @@ -59,7 +59,11 @@ - `rail`: more than 8 frames in a row on a rail (|y| >= 32767) where the input was on neither `latency_samples` earlier nor in the last min(`latency_samples`, `HOLD`) frames: a hold that latched a rail - plays it for the length of the hold. + plays it for the length of the hold. Not judged where the class says + the setting clips by design: `CLIPS_AT_TOP`, a tuple of macro indexes, + excuses a cell whose knobs put one of them at 127 (Feedback at its top + runs away), and `CLIPS_BY_DESIGN = True` excuses every cell (a fuzz + clips at any level). A wrap is still judged there. - `silent`: the last second exactly zero while the input played through it. Not judged on the DC step, which a high-pass rightly takes to zero, nor where a macro whose label holds Level or Volume is at 0: an output @@ -230,8 +234,20 @@ def turned_down(cls, knobs): return False +def clips_by_design(cls, knobs): + """Whether the class says this cell's knobs clip by design (see `rail` + under **The bars**).""" + if getattr(cls, "CLIPS_BY_DESIGN", False): + return True + top = getattr(cls, "CLIPS_AT_TOP", ()) + for index, value in knobs or (): + if value == 127 and index in top: + return True + return False + + def judge(data, values, channels, rate, lat, which, seconds=SECONDS, - silent_ok=False): + silent_ok=False, rail_ok=False): out = kc.samples(data) reds = [] if which != "noise": @@ -239,7 +255,7 @@ def judge(data, values, channels, rate, lat, which, seconds=SECONDS, if hit is not None: reds.append("wrap at %d ch %d" % hit) run, where = kc.rail_run(out, values, channels, lat, HOLD) - if run > kc.RAIL_RUN: + if run > kc.RAIL_RUN and not rail_ok: reds.append("rail %d frames from %d" % (run, where)) if which != "dc" and not silent_ok: lo = (seconds - 1) * rate * channels @@ -316,7 +332,8 @@ def measure(cls, rate, channels, which, patch, knobs, options=None): e.set_macro(index, value) data = kc.render(e, seconds * rate) reds = judge(data, values, channels, rate, e.latency_samples, which, - seconds, turned_down(cls, knobs)) + seconds, turned_down(cls, knobs), + clips_by_design(cls, knobs)) finally: e.deinit() verdict = "RED(%s)" % "; ".join(reds) if reds else "ok" diff --git a/tests/support/kit_planted.py b/tests/support/kit_planted.py index 46382b4a..c88fef51 100644 --- a/tests/support/kit_planted.py +++ b/tests/support/kit_planted.py @@ -151,6 +151,25 @@ class ClipKit(KitPlain): CURVE = staticmethod(_clip2) +class ClipAtTop(ClipKit): + """Ceiling: ClipKit saying its Gain at the top clips by design, so + only a cell with Gain at 127 is excused the rail.""" + NAME = "ClipAtTop" + CLIPS_AT_TOP = (1,) + + +class ClipsAlways(ClipKit): + """Ceiling: ClipKit saying it clips by design everywhere.""" + NAME = "ClipsAlways" + CLIPS_BY_DESIGN = True + + +class WrapsAlways(WrapKit): + """Ceiling: a wrap is never by design, even in a class that clips.""" + NAME = "WrapsAlways" + CLIPS_BY_DESIGN = True + + class DeadStop(KitPlain): """Ceiling: Gain's bottom stop is silence, as a level that went NaN on a board would be.""" @@ -713,6 +732,7 @@ def _apply_macro(self, index, position): PLANTS = {} for _cls in (KitPlain, Silent, LeakBand, WrapKit, WrapKitQuiet, ClipKit, DeadStop, + ClipAtTop, ClipsAlways, WrapsAlways, CondKit, MidWrap, ComboWrap, AllWrap, LowWrap, ImplKit, EchoTail, ShortTail, EventTail, NoneSilent, NoneDC, ReadbackOff, StopRaises, NanLevel, PatchStale, CtorNan, ClearsFirst, ClearsMacro, FlushKit, FlushOnly, DoubleReset, diff --git a/tests/test_kit.py b/tests/test_kit.py index 745321ed..b1223cf8 100644 --- a/tests/test_kit.py +++ b/tests/test_kit.py @@ -541,6 +541,17 @@ def test_a_rail_is_red(self): # A square at full scale sits on the rail because the input does. self.assertEqual(self.v(kp.KitPlain, "square"), "ok") + def test_a_rail_by_design_is_excused_where_declared(self): + self.assertTrue(self.v(kp.ClipAtTop).startswith("RED(rail")) + self.assertTrue(self.v(kp.ClipAtTop, knob=(0, 127)).startswith( + "RED(rail")) + self.assertEqual(self.v(kp.ClipAtTop, knob=(1, 127)), "ok") + self.assertEqual(self.v(kp.ClipAtTop, knob=[(0, 127), (1, 127)]), + "ok") + self.assertEqual(self.v(kp.ClipsAlways), "ok") + # A wrap is still judged. + self.assertTrue(self.v(kp.WrapsAlways).startswith("RED(wrap")) + def test_silence_at_a_stop_is_red(self): self.assertTrue(self.v(kp.DeadStop, knob=(1, 0)).startswith( "RED(silent")) From d009a12e6df58fa47550426d0e0d6c62090a38ea Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:32:13 -0500 Subject: [PATCH 02/22] Ceiling kit: an effect's test can hold its stated level --- tests/support/kit_ceiling.py | 17 +++++++++++++++++ tests/test_kit.py | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/support/kit_ceiling.py b/tests/support/kit_ceiling.py index d95dd92f..8c48badd 100644 --- a/tests/support/kit_ceiling.py +++ b/tests/support/kit_ceiling.py @@ -369,6 +369,23 @@ def run(cls, emit=print, quick=False, rates=None, channels=None, return cells.summary() +def stated_level_reds(cls, material, db=None, keys=None): + """The red cells on `material`, 48 kHz stereo, for the defaults and + every patch (or just `keys`), played at the class's stated level, or at + `db` to show the level above it is not clean. An effect's own test + holds its stated level with this: clean there, red a step up.""" + if db is not None: + cls = type(cls.__name__, (cls,), {"INPUT_CEILING_DBFS": db, + "__module__": cls.__module__}) + if keys is None: + keys = ["d"] + ["p%d" % p for p in sorted(cls.PATCHES)] + lines = [] + run(cls, emit=lines.append, quick=True, materials=(material,), + only=keys) + return [line for line in lines + if "|V:RED" in line and not line.startswith("SUMMARY")] + + if __name__ == "__main__": import kit kit.main(sys.argv[1:], only=(CHECK,)) diff --git a/tests/test_kit.py b/tests/test_kit.py index b1223cf8..ef9dc57c 100644 --- a/tests/test_kit.py +++ b/tests/test_kit.py @@ -552,6 +552,15 @@ def test_a_rail_by_design_is_excused_where_declared(self): # A wrap is still judged. self.assertTrue(self.v(kp.WrapsAlways).startswith("RED(wrap")) + def test_a_stated_level_holds_and_the_level_above_does_not(self): + self.assertEqual( + kit_ceiling.stated_level_reds(kp.WrapKitQuiet, "sine"), []) + reds = kit_ceiling.stated_level_reds(kp.WrapKitQuiet, "sine", db=0) + self.assertEqual(len(reds), 3) # the defaults, two patches + self.assertIn("RED(wrap", reds[0]) + self.assertEqual(len(kit_ceiling.stated_level_reds( + kp.WrapKitQuiet, "sine", db=0, keys=("p1",))), 1) + def test_silence_at_a_stop_is_red(self): self.assertTrue(self.v(kp.DeadStop, knob=(1, 0)).startswith( "RED(silent")) From 9c145f5473933f78d2d536a4801e63f44cbe18b1 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:32:56 -0500 Subject: [PATCH 03/22] Seventeen effects state the input level they are clean at --- lib/audioeffects/analogdelay.py | 5 +++++ lib/audioeffects/bitcrusher.py | 5 +++++ lib/audioeffects/combfilter.py | 5 +++++ lib/audioeffects/digitaldelay.py | 5 +++++ lib/audioeffects/highpass.py | 5 +++++ lib/audioeffects/ladderfilter.py | 5 +++++ lib/audioeffects/lowpass.py | 5 +++++ lib/audioeffects/multibandcompressor.py | 5 +++++ lib/audioeffects/multitapdelay.py | 5 +++++ lib/audioeffects/notch.py | 5 +++++ lib/audioeffects/parametriceq.py | 5 +++++ lib/audioeffects/phaser.py | 5 +++++ lib/audioeffects/pingpongdelay.py | 5 +++++ lib/audioeffects/reverb.py | 5 +++++ lib/audioeffects/slapbackdelay.py | 5 +++++ lib/audioeffects/tapedelay.py | 5 +++++ lib/audioeffects/transientshaper.py | 5 +++++ 17 files changed, 85 insertions(+) diff --git a/lib/audioeffects/analogdelay.py b/lib/audioeffects/analogdelay.py index 74f35ead..ae48014d 100644 --- a/lib/audioeffects/analogdelay.py +++ b/lib/audioeffects/analogdelay.py @@ -221,6 +221,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 = { diff --git a/lib/audioeffects/bitcrusher.py b/lib/audioeffects/bitcrusher.py index d6f1972f..4f1cb0fc 100644 --- a/lib/audioeffects/bitcrusher.py +++ b/lib/audioeffects/bitcrusher.py @@ -1400,6 +1400,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", diff --git a/lib/audioeffects/combfilter.py b/lib/audioeffects/combfilter.py index e3f16cb8..135c8804 100644 --- a/lib/audioeffects/combfilter.py +++ b/lib/audioeffects/combfilter.py @@ -294,6 +294,11 @@ 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 + MACRO_LABELS = ("Frequency", "Feedback", "Mix", "Tone", "Trim", "Glide") MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "UNIPOLAR", 3: "UNIPOLAR", 4: "BIPOLAR", 5: "UNIPOLAR"} diff --git a/lib/audioeffects/digitaldelay.py b/lib/audioeffects/digitaldelay.py index 27be5cc2..67203fbd 100644 --- a/lib/audioeffects/digitaldelay.py +++ b/lib/audioeffects/digitaldelay.py @@ -324,6 +324,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 = { diff --git a/lib/audioeffects/highpass.py b/lib/audioeffects/highpass.py index 08a91786..7ff4cd55 100644 --- a/lib/audioeffects/highpass.py +++ b/lib/audioeffects/highpass.py @@ -197,6 +197,11 @@ 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 + MACRO_LABELS = ("Frequency", "Resonance", "Slope", "Mix", "Trim") MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "TOGGLE", 3: "UNIPOLAR", 4: "BIPOLAR"} diff --git a/lib/audioeffects/ladderfilter.py b/lib/audioeffects/ladderfilter.py index b90298f0..896690a7 100644 --- a/lib/audioeffects/ladderfilter.py +++ b/lib/audioeffects/ladderfilter.py @@ -107,6 +107,11 @@ 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 + MACRO_LABELS = ("Cutoff", "Resonance", "Drive", "Passband Comp", "Poles", "Oversample", "Mix") MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "UNIPOLAR", diff --git a/lib/audioeffects/lowpass.py b/lib/audioeffects/lowpass.py index 7d5bc5eb..dca56e1f 100644 --- a/lib/audioeffects/lowpass.py +++ b/lib/audioeffects/lowpass.py @@ -172,6 +172,11 @@ 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 + MACRO_LABELS = ("Frequency", "Resonance", "Slope", "Mix", "Trim") MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "TOGGLE", 3: "UNIPOLAR", 4: "BIPOLAR"} diff --git a/lib/audioeffects/multibandcompressor.py b/lib/audioeffects/multibandcompressor.py index 5d68c552..a4670dc8 100644 --- a/lib/audioeffects/multibandcompressor.py +++ b/lib/audioeffects/multibandcompressor.py @@ -304,6 +304,11 @@ 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 + MACRO_LABELS = ( "Crossover Low", "Crossover High", "Low Threshold", "Mid Threshold", "High Threshold", diff --git a/lib/audioeffects/multitapdelay.py b/lib/audioeffects/multitapdelay.py index dd3562f0..3b4c2b6c 100644 --- a/lib/audioeffects/multitapdelay.py +++ b/lib/audioeffects/multitapdelay.py @@ -273,6 +273,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 = { diff --git a/lib/audioeffects/notch.py b/lib/audioeffects/notch.py index 039da4a5..1c9032a2 100644 --- a/lib/audioeffects/notch.py +++ b/lib/audioeffects/notch.py @@ -134,6 +134,11 @@ 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 + MACRO_LABELS = ("Frequency", "Width", "Harmonics", "Depth", "Trim") MACRO_MODES = {0: "UNIPOLAR", 1: "UNIPOLAR", 2: "TOGGLE", 3: "UNIPOLAR", 4: "BIPOLAR"} diff --git a/lib/audioeffects/parametriceq.py b/lib/audioeffects/parametriceq.py index 103ace3f..9f780674 100644 --- a/lib/audioeffects/parametriceq.py +++ b/lib/audioeffects/parametriceq.py @@ -216,6 +216,11 @@ 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 + MACRO_LABELS = ( "Low Freq", "Low Boost", "Low Atten", "Bell 1 Freq", "Bell 1 Gain", diff --git a/lib/audioeffects/phaser.py b/lib/audioeffects/phaser.py index 6a812276..2551df27 100644 --- a/lib/audioeffects/phaser.py +++ b/lib/audioeffects/phaser.py @@ -328,6 +328,11 @@ 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 + MACRO_LABELS = ("Rate", "Depth", "Centre", "Feedback", "Mix", "Stages", "Shape", "Drive", "Sync", "Division") MACRO_MODES = { diff --git a/lib/audioeffects/pingpongdelay.py b/lib/audioeffects/pingpongdelay.py index fff068e1..8c7771bc 100644 --- a/lib/audioeffects/pingpongdelay.py +++ b/lib/audioeffects/pingpongdelay.py @@ -168,6 +168,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 = { diff --git a/lib/audioeffects/reverb.py b/lib/audioeffects/reverb.py index 4ba3f1c1..8a0a1eb4 100644 --- a/lib/audioeffects/reverb.py +++ b/lib/audioeffects/reverb.py @@ -298,6 +298,11 @@ class Reverb(_component.Component): LATENCY_SAMPLES = 0 TAIL_SAMPLES = None + #: The defaults and every patch play the ceiling kit's materials + #: clean at this level; at -9 dBFS, patch 9 rails on + #: a 220 Hz square. + INPUT_CEILING_DBFS = -12 + MACRO_LABELS = ("Character", "Decay", "Size", "Predelay", "Diffusion", "Damping", "Bandwidth", "Low Cut", "Mod Depth", "Mod Rate", "Width", "Tone", "Mix") diff --git a/lib/audioeffects/slapbackdelay.py b/lib/audioeffects/slapbackdelay.py index ee8eafd1..b7fac642 100644 --- a/lib/audioeffects/slapbackdelay.py +++ b/lib/audioeffects/slapbackdelay.py @@ -187,6 +187,11 @@ class SlapbackDelay(_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 2 rails on + #: a 100 Hz square. + INPUT_CEILING_DBFS = -6 + MACRO_LABELS = ("Time", "Level", "Saturation", "Tone", "Wow", "Repeats") MACRO_MODES = { 0: "UNIPOLAR", diff --git a/lib/audioeffects/tapedelay.py b/lib/audioeffects/tapedelay.py index d81780f9..795386d4 100644 --- a/lib/audioeffects/tapedelay.py +++ b/lib/audioeffects/tapedelay.py @@ -349,6 +349,11 @@ class TapeDelay(_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", "Glide", "Wow", "Flutter", "Record Level", "Spacing", "Spread", "Sync", "Division") MACRO_MODES = { diff --git a/lib/audioeffects/transientshaper.py b/lib/audioeffects/transientshaper.py index 138637f0..ffa07401 100644 --- a/lib/audioeffects/transientshaper.py +++ b/lib/audioeffects/transientshaper.py @@ -155,6 +155,11 @@ class TransientShaper(_component.Component): LATENCY_SAMPLES = 0 TAIL_SAMPLES = 0 + #: The defaults and every patch play the ceiling kit's materials + #: clean at this level; at -9 dBFS, patch 1 rails on + #: a 100 Hz square. + INPUT_CEILING_DBFS = -12 + MACRO_LABELS = ("Attack", "Sustain", "Output", "Attack Speed", "Sustain Hold") MACRO_MODES = {0: "BIPOLAR", 1: "BIPOLAR", 2: "BIPOLAR", From 56589f02d5f8c441a76a43e4bf617118d04e57d0 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:54:43 -0500 Subject: [PATCH 04/22] AnalogDelay: says the input level it is clean at --- lib/audioeffects/analogdelay.py | 2 ++ tests/test_cpython_effects_analogdelay.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/analogdelay.py b/lib/audioeffects/analogdelay.py index ae48014d..82a27255 100644 --- a/lib/audioeffects/analogdelay.py +++ b/lib/audioeffects/analogdelay.py @@ -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. diff --git a/tests/test_cpython_effects_analogdelay.py b/tests/test_cpython_effects_analogdelay.py index 2378d64a..927d1634 100644 --- a/tests/test_cpython_effects_analogdelay.py +++ b/tests/test_cpython_effects_analogdelay.py @@ -40,6 +40,7 @@ class built as a wire. T1 is unmeasured and T2b, T4 and T5 are disconfirmed sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import audiocore # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import _component # noqa: E402 @@ -2446,6 +2447,15 @@ def test_another_macro_leaves_a_walk_at_its_rate(self): self.assertEqual(effect._delay._handed["delay_slew"], slew) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: d at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(AnalogDelay, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + AnalogDelay, "square", db=-3, keys=("d",))) + + # -------------------------------------------------------------------------- # The docstring's claims, each tied to the test that asserts it @@ -2453,6 +2463,9 @@ def test_another_macro_leaves_a_walk_at_its_rate(self): #: that asserts it). Every sentence in the docstring that makes a claim is #: here; one that could not be tied to a test was struck. CLAIMS = ( + ("Keep the input under -6 dBFS and the defaults and patches stay " + "off the rails.", + ("test_clean_at_the_stated_input_level",)), ("Time runs from 20 to 600 ms, Feedback from 0 to 0.99 and Mix from 0 " "to 2.", "test_the_knob_spans"), ("Up to Mix 1 the dry passes untouched until the first repeat arrives.", From 7980e2e3ec7a186c186d10958dcd417d356010c3 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:55:11 -0500 Subject: [PATCH 05/22] Bitcrusher: says the input level it is clean at --- lib/audioeffects/bitcrusher.py | 5 +++-- tests/test_cpython_effects_bitcrusher.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/audioeffects/bitcrusher.py b/lib/audioeffects/bitcrusher.py index 4f1cb0fc..8eb13aee 100644 --- a/lib/audioeffects/bitcrusher.py +++ b/lib/audioeffects/bitcrusher.py @@ -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. diff --git a/tests/test_cpython_effects_bitcrusher.py b/tests/test_cpython_effects_bitcrusher.py index f980694e..8490a24f 100644 --- a/tests/test_cpython_effects_bitcrusher.py +++ b/tests/test_cpython_effects_bitcrusher.py @@ -16,6 +16,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import audiocore # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import bitcrusher as rebuilt # noqa: E402 @@ -1252,7 +1253,19 @@ def test_construction_needs_its_four_modules(self): sys.modules[name] = saved +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # sine is where it rails first: p1 at 0 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(rebuilt.Bitcrusher, "sine"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + rebuilt.Bitcrusher, "sine", db=0, keys=("p1",))) + + CLAIMS = ( + ("Keep the input under -3 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("`Bitcrusher` — a linear hold, the SP-1200's two numbers as " "knobs.", ("TheSurface.test_six_macros_and_seven_patches",)), From ea02a462d59339062e9375872cb4daaadda262da Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:55:22 -0500 Subject: [PATCH 06/22] CombFilter: says the input level it is clean at --- lib/audioeffects/combfilter.py | 2 ++ tests/test_cpython_effects_combfilter.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/combfilter.py b/lib/audioeffects/combfilter.py index 135c8804..e7c9726f 100644 --- a/lib/audioeffects/combfilter.py +++ b/lib/audioeffects/combfilter.py @@ -262,6 +262,8 @@ class CombFilter(_component.Component): **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. + Keep the input under -24 dBFS and the defaults and patches stay off the + rails. `reset()` empties the line. **Latency and tail.** It adds no latency at any setting. The tail diff --git a/tests/test_cpython_effects_combfilter.py b/tests/test_cpython_effects_combfilter.py index db262126..18c3d198 100644 --- a/tests/test_cpython_effects_combfilter.py +++ b/tests/test_cpython_effects_combfilter.py @@ -27,6 +27,7 @@ import audiocore sys.path.insert(0, os.path.join(os.path.dirname(__file__), "support")) +import kit_ceiling # noqa: E402 import kit_faults as faults # noqa: E402 from effects_measure import SAMPLE_RATE # noqa: E402 @@ -1235,7 +1236,19 @@ def test_construction_needs_its_two_modules(self): sys.modules[name] = saved +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # dc is where it rails first: p1 at -18 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(combfilter.CombFilter, "dc"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + combfilter.CombFilter, "dc", db=-18, keys=("p1",))) + + CLAIMS = ( + ("Keep the input under -24 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("A tuned comb: a delay line short enough to be a pitch, fed back on " "itself, so noise turns into a note and a chord turns metallic.", ("TheCombTunesFractionally.test_every_request_lands_within_five_cents", From ddfacbf6724b111cc05df0043c443fff532c16a0 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:55:42 -0500 Subject: [PATCH 07/22] DigitalDelay: says the input level it is clean at --- lib/audioeffects/digitaldelay.py | 3 ++- tests/test_cpython_effects_digitaldelay.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/audioeffects/digitaldelay.py b/lib/audioeffects/digitaldelay.py index 67203fbd..d55f8acf 100644 --- a/lib/audioeffects/digitaldelay.py +++ b/lib/audioeffects/digitaldelay.py @@ -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 diff --git a/tests/test_cpython_effects_digitaldelay.py b/tests/test_cpython_effects_digitaldelay.py index fcf332df..218748f8 100644 --- a/tests/test_cpython_effects_digitaldelay.py +++ b/tests/test_cpython_effects_digitaldelay.py @@ -87,6 +87,7 @@ class of the node's walk, and the edge test goes red at the old edge. A sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import audiocore # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 import lifecycle # noqa: E402 @@ -2902,12 +2903,24 @@ def touch(effect, _index=index): effect.deinit() +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: p3 at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(dd.DigitalDelay, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + dd.DigitalDelay, "square", db=-3, keys=("p3",))) + + # -------------------------------------------------------------------------- # The docstring's claims #: Every claim the module docstring makes, word for word, and the tests that #: assert it ("Class.test_name", in this file). CLAIMS = ( + ("Keep the input under -6 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("Time is the delay, from 12.5 to 800 ms, and Feedback is how much of " "each repeat goes round again, up to 0.99.", ("T4TimeLaw.test_the_map_at_the_stops_and_between", From 9d62c9d5c81dacce0e05cebacf21036e237340e6 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:56:06 -0500 Subject: [PATCH 08/22] HighPass: says the input level it is clean at --- lib/audioeffects/highpass.py | 3 ++- tests/test_cpython_effects_highpass.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/audioeffects/highpass.py b/lib/audioeffects/highpass.py index 7ff4cd55..278a0b1c 100644 --- a/lib/audioeffects/highpass.py +++ b/lib/audioeffects/highpass.py @@ -13,7 +13,8 @@ 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. +growl anywhere in it - a high-pass is linear. Keep the input under -9 dBFS +and the defaults and patches stay off the rails. **Three things the knobs do not do exactly, found by the Phase 2 gate audit and its patch sweep, and diff --git a/tests/test_cpython_effects_highpass.py b/tests/test_cpython_effects_highpass.py index 85369d7a..e1a75b14 100644 --- a/tests/test_cpython_effects_highpass.py +++ b/tests/test_cpython_effects_highpass.py @@ -31,6 +31,7 @@ import synthio sys.path.insert(0, os.path.join(os.path.dirname(__file__), "support")) +import kit_ceiling # noqa: E402 from effects_measure import SAMPLE_RATE, source # noqa: E402 from audioeffects import _component # noqa: E402 @@ -602,5 +603,14 @@ def test_deinit_releases_every_node_and_is_idempotent(self): built.output +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: p1 at -6 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(highpass.HighPass, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + highpass.HighPass, "square", db=-6, keys=("p1",))) + + if __name__ == "__main__": unittest.main() From 8e8792a647be3790f40484dd55a3aa6b5b989efa Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:56:26 -0500 Subject: [PATCH 09/22] LadderFilter: says the input level it is clean at --- lib/audioeffects/ladderfilter.py | 7 ++++--- tests/test_cpython_effects_ladderfilter.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/audioeffects/ladderfilter.py b/lib/audioeffects/ladderfilter.py index 896690a7..83ce1ff7 100644 --- a/lib/audioeffects/ladderfilter.py +++ b/lib/audioeffects/ladderfilter.py @@ -79,9 +79,10 @@ 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. 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. **On a board.** The class needs audiodsp's `audioladder`, and on a board without it construction raises `ImportError`. diff --git a/tests/test_cpython_effects_ladderfilter.py b/tests/test_cpython_effects_ladderfilter.py index 2c2b6784..71c11eeb 100644 --- a/tests/test_cpython_effects_ladderfilter.py +++ b/tests/test_cpython_effects_ladderfilter.py @@ -16,6 +16,7 @@ sys.path.insert(0, os.path.join(ROOT, "lib")) sys.path.insert(0, os.path.join(ROOT, "tests", "support")) +import kit_ceiling # noqa: E402 import kit_levels # noqa: E402 import kit_probes as probes # noqa: E402 import numpy as np # noqa: E402 @@ -182,7 +183,19 @@ def test_construction_needs_audioladder(self): self.assertEqual(LadderFilter.REQUIRES, ("audioladder",)) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: p4 at -12 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(LadderFilter, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + LadderFilter, "square", db=-12, keys=("p4",))) + + CLAIMS = ( + ("Keep the input under -15 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("`LadderFilter` - the Moog transistor ladder, rebuilt on " "`audioladder`.", ("TheClaims.test_construction_needs_audioladder",)), From dc0e87713f2dd1737e06ced9ec0c3006dfa73730 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:56:38 -0500 Subject: [PATCH 10/22] LowPass: says the input level it is clean at --- lib/audioeffects/lowpass.py | 2 ++ tests/test_cpython_effects_lowpass.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/audioeffects/lowpass.py b/lib/audioeffects/lowpass.py index dca56e1f..0af3a349 100644 --- a/lib/audioeffects/lowpass.py +++ b/lib/audioeffects/lowpass.py @@ -15,6 +15,8 @@ `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. +Keep the input under -3 dBFS and the defaults and patches stay off 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 diff --git a/tests/test_cpython_effects_lowpass.py b/tests/test_cpython_effects_lowpass.py index ab7c96d7..e3959622 100644 --- a/tests/test_cpython_effects_lowpass.py +++ b/tests/test_cpython_effects_lowpass.py @@ -28,6 +28,7 @@ class clamps now, and the dossier's section 7 names the raise as the defect) import audioeffects sys.path.insert(0, os.path.join(os.path.dirname(__file__), "support")) +import kit_ceiling # noqa: E402 from effects_measure import SAMPLE_RATE, source # noqa: E402 import kit_faults as faults # noqa: E402 @@ -671,6 +672,15 @@ def build_twelve(cls): lambda effect: float(effect._pole_two.mix), build_twelve) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # sine is where it rails first: p5 at 0 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(lowpass.LowPass, "sine"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + lowpass.LowPass, "sine", db=0, keys=("p5",))) + + if __name__ == "__main__": unittest.main() From 93b21091b05dc8584a25888c9d646a062938b4c5 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:57:22 -0500 Subject: [PATCH 11/22] MultiTapDelay: says the input level it is clean at --- lib/audioeffects/multitapdelay.py | 2 ++ tests/test_cpython_effects_multitapdelay.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/multitapdelay.py b/lib/audioeffects/multitapdelay.py index 3b4c2b6c..fb6d4499 100644 --- a/lib/audioeffects/multitapdelay.py +++ b/lib/audioeffects/multitapdelay.py @@ -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. diff --git a/tests/test_cpython_effects_multitapdelay.py b/tests/test_cpython_effects_multitapdelay.py index 4fb9c67f..74260393 100644 --- a/tests/test_cpython_effects_multitapdelay.py +++ b/tests/test_cpython_effects_multitapdelay.py @@ -60,6 +60,7 @@ class built as a wire. The full spans, the three interpreters and the rates import audiomixer # noqa: E402 import audioroute # noqa: E402 import synthio # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 import audioeffects # noqa: E402 @@ -2953,6 +2954,15 @@ def test_the_tail_reading_is_red_on_a_bound_one_lap_short(self): self.assertGreater(planted, short, (rate, channels)) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: d at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(mtd.MultiTapDelay, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + mtd.MultiTapDelay, "square", db=-3, keys=("d",))) + + # -------------------------------------------------------------------------- # The docstring's claims, each tied to the test that asserts it @@ -2961,6 +2971,9 @@ def test_the_tail_reading_is_red_on_a_bound_one_lap_short(self): #: claim is here; one that could not be tied to a test was struck (the #: trial fixer's dated note in the dossier lists them). CLAIMS = ( + ("Keep the input under -6 dBFS and the defaults and patches stay " + "off the rails.", + ("test_clean_at_the_stated_input_level",)), ("Measured at v0.6.3, patch 4 costs 5.567 ms a block on the " "ESP32-S3, where a block lasts 5.333 ms, so it needs a P4-class " "board: the ESP32-P4 runs it in 3.865 ms.", From 9053b3ab4a5b6d3fe3705ac3ea586046a1c8f98d Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:58:03 -0500 Subject: [PATCH 12/22] MultibandCompressor: says the input level it is clean at --- lib/audioeffects/multibandcompressor.py | 2 ++ tests/test_cpython_effects_multiband.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/multibandcompressor.py b/lib/audioeffects/multibandcompressor.py index a4670dc8..d7e5269e 100644 --- a/lib/audioeffects/multibandcompressor.py +++ b/lib/audioeffects/multibandcompressor.py @@ -263,6 +263,8 @@ class MultibandCompressor(_component.Component): 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. **Latency and tail.** It adds no latency at any setting. The tail reaches exact zero, and `tail_samples` says when. diff --git a/tests/test_cpython_effects_multiband.py b/tests/test_cpython_effects_multiband.py index 811182fb..7ededd7a 100644 --- a/tests/test_cpython_effects_multiband.py +++ b/tests/test_cpython_effects_multiband.py @@ -32,6 +32,7 @@ #: `audioeffects/multibandcompressor.py`; `rebuilt.ADOPTED` no longer lists #: it. These tests still import the home module so a planted-fault subclass #: is measured against this file, not only `create()`. +import kit_ceiling # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import _component # noqa: E402 from tools import effect_measurements as kit # noqa: E402 @@ -990,7 +991,19 @@ def test_construction_needs_its_three_modules(self): sys.modules[name] = saved +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # sq147 is where it rails first: p3 at -6 dBFS. + cls = multibandcompressor.MultibandCompressor + self.assertEqual(kit_ceiling.stated_level_reds(cls, "sq147"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + cls, "sq147", db=-6, keys=("p3",))) + + CLAIMS = ( + ("Keep the input under -9 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("Split-band compression: the mix split into two or three bands, each " "with its own compressor, then summed back flat.", ("Traits.test_m1_the_bands_sum_flat_at_three_bands", From 04525c2656296f682794ad14cbc2916812b437db Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:58:25 -0500 Subject: [PATCH 13/22] Notch: says the input level it is clean at --- lib/audioeffects/notch.py | 3 ++- tests/test_cpython_effects_notch.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/audioeffects/notch.py b/lib/audioeffects/notch.py index 1c9032a2..7d07b048 100644 --- a/lib/audioeffects/notch.py +++ b/lib/audioeffects/notch.py @@ -13,7 +13,8 @@ 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. +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. **Portability tier: audiodsp** (`REQUIRES = ("audiobiquad",)`). All three sections are `audiobiquad.Biquad`: float state, and a tail that reaches diff --git a/tests/test_cpython_effects_notch.py b/tests/test_cpython_effects_notch.py index 0cca27bc..7c7510ec 100644 --- a/tests/test_cpython_effects_notch.py +++ b/tests/test_cpython_effects_notch.py @@ -31,6 +31,7 @@ class actually built and not a re-derivation of RBJ standing beside it. The import synthio sys.path.insert(0, os.path.join(os.path.dirname(__file__), "support")) +import kit_ceiling # noqa: E402 import kit_faults as faults # noqa: E402 from effects_measure import SAMPLE_RATE # noqa: E402 @@ -573,6 +574,15 @@ def test_deinit_releases_every_node_the_class_built(self): self.assertTrue(getattr(node, "_deinited", True)) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: p1 at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(notch_module.Notch, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + notch_module.Notch, "square", db=-3, keys=("p1",))) + + if __name__ == "__main__": unittest.main() From b3737efe292c91dad5fec25199b9c2209b646969 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:58:25 -0500 Subject: [PATCH 14/22] ParametricEQ: says the input level it is clean at --- lib/audioeffects/parametriceq.py | 3 ++- tests/test_cpython_effects_parametriceq.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/audioeffects/parametriceq.py b/lib/audioeffects/parametriceq.py index 9f780674..acb60094 100644 --- a/lib/audioeffects/parametriceq.py +++ b/lib/audioeffects/parametriceq.py @@ -50,7 +50,8 @@ 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. +are the ones the traits describe. Keep the input under -12 dBFS and the +defaults and patches stay off 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, diff --git a/tests/test_cpython_effects_parametriceq.py b/tests/test_cpython_effects_parametriceq.py index d6150fa7..b1c88c1d 100644 --- a/tests/test_cpython_effects_parametriceq.py +++ b/tests/test_cpython_effects_parametriceq.py @@ -27,6 +27,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "support")) import effects_measure # noqa: E402 +import kit_ceiling # noqa: E402 from effects_measure import SAMPLE_RATE, source # noqa: E402 from audioeffects import _component # noqa: E402 @@ -456,5 +457,14 @@ def __init__(self, sample): self.output = sample +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # dc is where it rails first: p3 at -9 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(parametriceq.ParametricEQ, "dc"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + parametriceq.ParametricEQ, "dc", db=-9, keys=("p3",))) + + if __name__ == "__main__": unittest.main() From c15492458ef68b3913b09e24b6d9bd4ca31e0c2f Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:58:53 -0500 Subject: [PATCH 15/22] Phaser: says the input level it is clean at --- lib/audioeffects/phaser.py | 2 ++ tests/test_cpython_effects_phaser.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/phaser.py b/lib/audioeffects/phaser.py index 2551df27..272eb695 100644 --- a/lib/audioeffects/phaser.py +++ b/lib/audioeffects/phaser.py @@ -297,6 +297,8 @@ class Phaser(_component.Component): 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. + Keep the input under -9 dBFS and the defaults and patches stay off 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 diff --git a/tests/test_cpython_effects_phaser.py b/tests/test_cpython_effects_phaser.py index fa902f56..f02910aa 100644 --- a/tests/test_cpython_effects_phaser.py +++ b/tests/test_cpython_effects_phaser.py @@ -25,6 +25,7 @@ from audioeffects import _component # noqa: E402 from audioeffects import phaser as phaser_module # noqa: E402 from tools import effect_measurements as kit # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults as faults # noqa: E402 VENDOR = "PyDevices" @@ -911,7 +912,19 @@ def test_construction_needs_audiobiquad_and_audioshaper(self): sys.modules[name] = saved +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # sq147 is where it rails first: p1 at -6 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(Phaser, "sq147"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + Phaser, "sq147", db=-6, keys=("p1",))) + + CLAIMS = ( + ("Keep the input under -9 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("`Phaser` - four first-order all-pass stages swept the way a Phase 90 " "is.", ("TestPhaserTraits.test_p1_two_notches_at_defaults_six_stages_is_" From f18015c845cd9d827efacabcbc4f6967c235f464 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:59:14 -0500 Subject: [PATCH 16/22] PingPongDelay: says the input level it is clean at --- lib/audioeffects/pingpongdelay.py | 2 ++ tests/test_cpython_effects_pingpongdelay.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/pingpongdelay.py b/lib/audioeffects/pingpongdelay.py index 8c7771bc..15de2dee 100644 --- a/lib/audioeffects/pingpongdelay.py +++ b/lib/audioeffects/pingpongdelay.py @@ -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 diff --git a/tests/test_cpython_effects_pingpongdelay.py b/tests/test_cpython_effects_pingpongdelay.py index aae72360..fc864a58 100644 --- a/tests/test_cpython_effects_pingpongdelay.py +++ b/tests/test_cpython_effects_pingpongdelay.py @@ -30,6 +30,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import audiocore # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import _component # noqa: E402 @@ -2427,12 +2428,24 @@ def test_construction_needs_audioecho(self): sys.modules["audioecho"] = saved +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # dc is where it rails first: p3 at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(PingPongDelay, "dc"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + PingPongDelay, "dc", db=-3, keys=("p3",))) + + # -------------------------------------------------------------------------- # The docstring's claims #: Every claim the class docstring makes, word for word, and the tests that #: assert it ("Class.test_name", in this file). CLAIMS = ( + ("Keep the input under -6 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("Two delay lines crossed into each other: the repeats bounce between " "the speakers.", ("T1RepeatsAlternate.test_the_three_feedbacks_at_three_rates",)), From 7f91be6d6686c5e5d14f17782900d8b80b19b44f Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:00:00 -0500 Subject: [PATCH 17/22] Reverb: says the input level it is clean at --- lib/audioeffects/reverb.py | 2 ++ tests/test_cpython_effects_reverb.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/reverb.py b/lib/audioeffects/reverb.py index 8a0a1eb4..e1eea48f 100644 --- a/lib/audioeffects/reverb.py +++ b/lib/audioeffects/reverb.py @@ -48,6 +48,8 @@ at 48, 44.1 or 22.05 kHz; Bright Chamber at 44.1 kHz is not claimed. A plucked phrase stays off the rails on every patch to -4 dBFS; at -3 dBFS Slow Bloom rails. +Keep the input under -12 dBFS and the defaults and patches stay off the +rails. `tail_samples` bounds the frames until the output is exactly zero once your input stops: 222 868 frames at the defaults at 48 kHz. One int16 diff --git a/tests/test_cpython_effects_reverb.py b/tests/test_cpython_effects_reverb.py index 64bf8c51..13ccf998 100644 --- a/tests/test_cpython_effects_reverb.py +++ b/tests/test_cpython_effects_reverb.py @@ -24,6 +24,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import audiocore # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import _component # noqa: E402 @@ -1838,12 +1839,24 @@ def test_the_tanks_lines_compress_from_8000_lsb_rms(self): self.assertLess(g8 - g4, -1.0, (g4, g8)) +class TheInputCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # sq220 is where it rails first: p9 at -9 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(Reverb, "sq220"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + Reverb, "sq220", db=-9, keys=("p9",))) + + # -- the claims --------------------------------------------------------------- #: Every sentence of the docstrings that makes a claim, word for word, and #: the tests that assert it (the trial brief). A sentence that is not here #: may hold no number. CLAIMS = ( + ("Keep the input under -12 dBFS and the defaults and patches stay " + "off the rails.", + ("TheInputCeiling.test_clean_at_the_stated_input_level",)), ("Your dry signal passes untouched and a reverb tail rises behind it.", ("T7LowCut.test_the_dry_is_flat",)), ("Character picks the machine: `plate` is dense from the first " From a5fca336d6a162f5c0dbda1da883e2a87b9692e3 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:00:23 -0500 Subject: [PATCH 18/22] SlapbackDelay: says the input level it is clean at --- lib/audioeffects/slapbackdelay.py | 2 ++ tests/test_cpython_effects_slapbackdelay.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/audioeffects/slapbackdelay.py b/lib/audioeffects/slapbackdelay.py index b7fac642..b8980228 100644 --- a/lib/audioeffects/slapbackdelay.py +++ b/lib/audioeffects/slapbackdelay.py @@ -135,6 +135,8 @@ class SlapbackDelay(_component.Component): Up to Level 1 the dry passes untouched until the repeat arrives, however hard Saturation drives the repeat. A hot input can reach the rail, since the repeat adds to a dry at unity. + Keep the input under -6 dBFS and the defaults and patches stay off the + rails. At Repeats 0 there is one repeat and no second. Repeats above 0 sends the repeat round for more. Wow swings the repeat's pitch by the cents the knob reads, at a slow diff --git a/tests/test_cpython_effects_slapbackdelay.py b/tests/test_cpython_effects_slapbackdelay.py index bbd791d5..4a9c88aa 100644 --- a/tests/test_cpython_effects_slapbackdelay.py +++ b/tests/test_cpython_effects_slapbackdelay.py @@ -39,6 +39,7 @@ import audiocore # noqa: E402 import audiomixer # noqa: E402 import audioroute # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import _component # noqa: E402 @@ -1900,6 +1901,15 @@ def test_the_stated_ceiling_is_clean_and_just_over_is_not(self): **options), 0, options) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: p2 at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(SlapbackDelay, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + SlapbackDelay, "square", db=-3, keys=("p2",))) + + # -------------------------------------------------------------------------- # The docstring's claims, each tied to the test that asserts it (the trial # of the second process, 2026-09-29) @@ -1908,6 +1918,9 @@ def test_the_stated_ceiling_is_clean_and_just_over_is_not(self): #: that asserts it). Every sentence in the docstring that makes a claim is #: here; one that could not be tied to a test was struck. CLAIMS = ( + ("Keep the input under -6 dBFS and the defaults and patches stay " + "off the rails.", + ("test_clean_at_the_stated_input_level",)), ("By default the repeat comes 134.4 ms after the dry, once.", "test_defaults_three_rates_stereo_and_mono"), ("Time runs from 40 to 250 ms, Level from 0 to 2, Saturation from 0 to " From 6f64bb886444d6c7ac5a263f26b6217b24283b44 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:00:47 -0500 Subject: [PATCH 19/22] TapeDelay: says the input level it is clean at --- lib/audioeffects/tapedelay.py | 3 ++- tests/test_cpython_effects_tapedelay.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/audioeffects/tapedelay.py b/lib/audioeffects/tapedelay.py index 795386d4..5abc7d10 100644 --- a/lib/audioeffects/tapedelay.py +++ b/lib/audioeffects/tapedelay.py @@ -35,7 +35,8 @@ shape. The darkening follows the tape's loss law only up to a band top that rises with the tape's speed. Record Level has no memory: tape hysteresis is not modelled. The RE-201's -Bass and Treble are not here. +Bass and Treble are not here. 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 diff --git a/tests/test_cpython_effects_tapedelay.py b/tests/test_cpython_effects_tapedelay.py index 13553998..6fe4c48b 100644 --- a/tests/test_cpython_effects_tapedelay.py +++ b/tests/test_cpython_effects_tapedelay.py @@ -34,6 +34,7 @@ import audiocore # noqa: E402 import lifecycle # noqa: E402 +import kit_ceiling # noqa: E402 import kit_faults # noqa: E402 import kit_probes as probes # noqa: E402 from audioeffects import _component # noqa: E402 @@ -2954,11 +2955,23 @@ def test_every_demonstrated_row_is_red_on_a_wire(self): or null["drift_ok"], null) +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: d at -3 dBFS. + self.assertEqual( + kit_ceiling.stated_level_reds(tape.TapeDelay, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + tape.TapeDelay, "square", db=-3, keys=("d",))) + + # -- the docstring's claims ----------------------------------------------- #: Every claim the module docstring makes, word for word, and the tests that #: assert it ("Class.test_name", in this file). CLAIMS = ( + ("Keep the input under -6 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("`TapeDelay` - a tape echo with two transports: the Roland RE-201's " "motor and the Maestro EP-3's sliding head.", ("TheSurface.test_macros_characters_tier_latency", From f9d556f2aa1ab1af20f6f007cb80ed264c4c492a Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:01:08 -0500 Subject: [PATCH 20/22] TransientShaper: says the input level it is clean at --- lib/audioeffects/transientshaper.py | 5 +++-- tests/test_cpython_effects_transientshaper.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/audioeffects/transientshaper.py b/lib/audioeffects/transientshaper.py index ffa07401..4ae63a0f 100644 --- a/lib/audioeffects/transientshaper.py +++ b/lib/audioeffects/transientshaper.py @@ -131,8 +131,9 @@ class TransientShaper(_component.Component): **Levels.** At its defaults the class is a wire. Attack and Sustain add gain where they are turned up, so a hit that is already loud reaches - the rails. A plucked phrase stays off the rails on every patch to - -10 dBFS; at -3 dBFS Snap and Kick Punch rail. Turn Output down, or + the rails. Keep the input under -12 dBFS and the defaults and patches + stay off the rails. A plucked phrase stays off the rails on every patch + to -10 dBFS; at -3 dBFS Snap and Kick Punch rail. Turn Output down, or your input. **Timing.** Latency is zero at every setting, and so is the tail: diff --git a/tests/test_cpython_effects_transientshaper.py b/tests/test_cpython_effects_transientshaper.py index 29eda36a..6e5813b9 100644 --- a/tests/test_cpython_effects_transientshaper.py +++ b/tests/test_cpython_effects_transientshaper.py @@ -31,6 +31,7 @@ #: These tests still import the home module so a planted-fault subclass is #: measured against this file, not only `create()`. import effect_measurements as kit # noqa: E402 +from support import kit_ceiling # noqa: E402 from support import kit_probes as probes # noqa: E402 VENDOR = "PyDevices" @@ -321,7 +322,19 @@ def test_construction_needs_audiodynamics(self): sys.modules["audiodynamics"] = saved +class TheCeiling(unittest.TestCase): + def test_clean_at_the_stated_input_level(self): + # square is where it rails first: p1 at -9 dBFS. + cls = transientshaper.TransientShaper + self.assertEqual(kit_ceiling.stated_level_reds(cls, "square"), []) + self.assertTrue(kit_ceiling.stated_level_reds( + cls, "square", db=-9, keys=("p1",))) + + CLAIMS = ( + ("Keep the input under -12 dBFS and the defaults and patches stay " + "off the rails.", + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("There is no threshold and no ratio: the gain follows the difference " "between two envelopes of your own signal, so between -20 and -40 dBFS " "a quiet hit is shaped as hard as a loud one, to within 0.5 dB.", From 5b4a79aab35e4e7bf82dd83690cabaae76e91f98 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:28:58 -0500 Subject: [PATCH 21/22] Ten effects name the knobs that clip at their top --- lib/audioeffects/combfilter.py | 14 ++++++++------ lib/audioeffects/graphiceq.py | 6 +++++- lib/audioeffects/highpass.py | 17 ++++++++++------- lib/audioeffects/ladderfilter.py | 9 ++++++--- lib/audioeffects/lowpass.py | 17 +++++++++-------- lib/audioeffects/multibandcompressor.py | 12 +++++++----- lib/audioeffects/notch.py | 24 +++++++++++++----------- lib/audioeffects/parametriceq.py | 11 +++++++---- lib/audioeffects/phaser.py | 9 +++++---- lib/audioeffects/transientshaper.py | 12 +++++++----- 10 files changed, 77 insertions(+), 54 deletions(-) diff --git a/lib/audioeffects/combfilter.py b/lib/audioeffects/combfilter.py index e7c9726f..d99ffc15 100644 --- a/lib/audioeffects/combfilter.py +++ b/lib/audioeffects/combfilter.py @@ -259,12 +259,12 @@ 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. - Keep the input under -24 dBFS and the defaults and patches stay off the - rails. - `reset()` empties the line. + **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. 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 always reaches exact zero, and `tail_samples` says when, for the @@ -300,6 +300,8 @@ class CombFilter(_component.Component): #: 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", diff --git a/lib/audioeffects/graphiceq.py b/lib/audioeffects/graphiceq.py index 913c19bc..3a5d3361 100644 --- a/lib/audioeffects/graphiceq.py +++ b/lib/audioeffects/graphiceq.py @@ -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 @@ -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", diff --git a/lib/audioeffects/highpass.py b/lib/audioeffects/highpass.py index 278a0b1c..443d5ab0 100644 --- a/lib/audioeffects/highpass.py +++ b/lib/audioeffects/highpass.py @@ -8,13 +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. Keep the input under -9 dBFS -and the defaults and patches stay off the rails. +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 @@ -202,6 +203,8 @@ class HighPass(_component.Component): #: 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", diff --git a/lib/audioeffects/ladderfilter.py b/lib/audioeffects/ladderfilter.py index 83ce1ff7..f33c3faf 100644 --- a/lib/audioeffects/ladderfilter.py +++ b/lib/audioeffects/ladderfilter.py @@ -80,9 +80,10 @@ class LadderFilter(_component.Component): **Levels.** The resonance peak and the drive both add gain, so no input level is clean on every setting. Keep the input under -15 dBFS and the - defaults and patches stay off the rails. 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. + defaults and patches stay off the rails. Drive at its top can still push + the output onto the rails. 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. **On a board.** The class needs audiodsp's `audioladder`, and on a board without it construction raises `ImportError`. @@ -112,6 +113,8 @@ class LadderFilter(_component.Component): #: 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") diff --git a/lib/audioeffects/lowpass.py b/lib/audioeffects/lowpass.py index 0af3a349..9a568997 100644 --- a/lib/audioeffects/lowpass.py +++ b/lib/audioeffects/lowpass.py @@ -7,16 +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. -Keep the input under -3 dBFS and the defaults and patches stay off the -rails. +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 @@ -178,6 +177,8 @@ class LowPass(_component.Component): #: 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", diff --git a/lib/audioeffects/multibandcompressor.py b/lib/audioeffects/multibandcompressor.py index d7e5269e..cf118bf0 100644 --- a/lib/audioeffects/multibandcompressor.py +++ b/lib/audioeffects/multibandcompressor.py @@ -260,11 +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. - Keep the input under -9 dBFS and the defaults and patches stay off the - rails. + 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. @@ -310,6 +310,8 @@ class MultibandCompressor(_component.Component): #: 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", diff --git a/lib/audioeffects/notch.py b/lib/audioeffects/notch.py index 7d07b048..7d163f8d 100644 --- a/lib/audioeffects/notch.py +++ b/lib/audioeffects/notch.py @@ -4,17 +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. Keep the input -under -6 dBFS and the defaults and patches stay off the rails. +**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 @@ -139,6 +139,8 @@ class Notch(_component.Component): #: 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", diff --git a/lib/audioeffects/parametriceq.py b/lib/audioeffects/parametriceq.py index acb60094..59748c00 100644 --- a/lib/audioeffects/parametriceq.py +++ b/lib/audioeffects/parametriceq.py @@ -48,10 +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. Keep the input under -12 dBFS and the -defaults and patches stay off the rails. +`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, @@ -221,6 +222,8 @@ class ParametricEQ(_component.Component): #: 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", diff --git a/lib/audioeffects/phaser.py b/lib/audioeffects/phaser.py index 272eb695..e432ce8b 100644 --- a/lib/audioeffects/phaser.py +++ b/lib/audioeffects/phaser.py @@ -295,10 +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. - Keep the input under -9 dBFS and the defaults and patches stay off the - rails. + 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 @@ -334,6 +333,8 @@ class reports the worst case, 552960 samples, for stages at their #: 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") diff --git a/lib/audioeffects/transientshaper.py b/lib/audioeffects/transientshaper.py index 4ae63a0f..7ec514cc 100644 --- a/lib/audioeffects/transientshaper.py +++ b/lib/audioeffects/transientshaper.py @@ -130,11 +130,11 @@ class TransientShaper(_component.Component): 3 dB a second, Sustain -24 reaches about -16 dB. **Levels.** At its defaults the class is a wire. Attack and Sustain add - gain where they are turned up, so a hit that is already loud reaches - the rails. Keep the input under -12 dBFS and the defaults and patches - stay off the rails. A plucked phrase stays off the rails on every patch - to -10 dBFS; at -3 dBFS Snap and Kick Punch rail. Turn Output down, or - your input. + gain where they are turned up, so a hit that is already loud reaches the + rails. Keep the input under -12 dBFS and the defaults and patches stay off + the rails. Attack at its top can still push the output onto the rails. A + plucked phrase stays off the rails on every patch to -10 dBFS; at -3 dBFS + Snap and Kick Punch rail. Turn Output down, or your input. **Timing.** Latency is zero at every setting, and so is the tail: silence in is silence out in the same frame. @@ -160,6 +160,8 @@ class TransientShaper(_component.Component): #: clean at this level; at -9 dBFS, patch 1 rails on #: a 100 Hz square. INPUT_CEILING_DBFS = -12 + #: Settings that clip by design: the rail check skips them. + CLIPS_AT_TOP = (0,) MACRO_LABELS = ("Attack", "Sustain", "Output", "Attack Speed", "Sustain Hold") From 6e60fb6870e4e7b321da153dad686ae97a9f333c Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:12:21 -0500 Subject: [PATCH 22/22] One stated level: the older plucked-phrase levels go CombFilter, LadderFilter, Reverb and TransientShaper each also stated a plucked phrase's clean level. Those tests probed above the new stated level, where the levels kit no longer plays, so they failed or passed on nothing. The stated level is now the one figure. --- lib/audioeffects/combfilter.py | 10 +++----- lib/audioeffects/ladderfilter.py | 4 +-- lib/audioeffects/reverb.py | 11 +++----- lib/audioeffects/transientshaper.py | 5 ++-- tests/test_cpython_effects_combfilter.py | 25 ++----------------- tests/test_cpython_effects_ladderfilter.py | 21 +++------------- tests/test_cpython_effects_reverb.py | 11 -------- tests/test_cpython_effects_transientshaper.py | 19 -------------- 8 files changed, 16 insertions(+), 90 deletions(-) diff --git a/lib/audioeffects/combfilter.py b/lib/audioeffects/combfilter.py index d99ffc15..490f37a1 100644 --- a/lib/audioeffects/combfilter.py +++ b/lib/audioeffects/combfilter.py @@ -259,12 +259,10 @@ 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. 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. + **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 always reaches exact zero, and `tail_samples` says when, for the diff --git a/lib/audioeffects/ladderfilter.py b/lib/audioeffects/ladderfilter.py index f33c3faf..41fb5964 100644 --- a/lib/audioeffects/ladderfilter.py +++ b/lib/audioeffects/ladderfilter.py @@ -81,9 +81,7 @@ class LadderFilter(_component.Component): **Levels.** The resonance peak and the drive both add gain, so no input 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. 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. + 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`. diff --git a/lib/audioeffects/reverb.py b/lib/audioeffects/reverb.py index e1eea48f..634c83ea 100644 --- a/lib/audioeffects/reverb.py +++ b/lib/audioeffects/reverb.py @@ -43,13 +43,10 @@ **Input ceiling.** There is no input gain, and the tank's lines clamp at the rail on every write whatever Mix is: a steady 362 Hz tone at 8 000 LSB RMS -comes back more than 1 dB quieter in the tail than at 4 000. On 2 s of -uniform noise at 4 000 LSB RMS at Mix 1, no shipped patch reaches the rail -at 48, 44.1 or 22.05 kHz; Bright Chamber at 44.1 kHz is not claimed. -A plucked phrase stays off the rails on every patch to -4 dBFS; at -3 dBFS -Slow Bloom rails. -Keep the input under -12 dBFS and the defaults and patches stay off the -rails. +comes back more than 1 dB quieter in the tail than at 4 000. On 2 s of uniform +noise at 4 000 LSB RMS at Mix 1, no shipped patch reaches the rail at 48, 44.1 +or 22.05 kHz; Bright Chamber at 44.1 kHz is not claimed. Keep the input under +-12 dBFS and the defaults and patches stay off the rails. `tail_samples` bounds the frames until the output is exactly zero once your input stops: 222 868 frames at the defaults at 48 kHz. One int16 diff --git a/lib/audioeffects/transientshaper.py b/lib/audioeffects/transientshaper.py index 7ec514cc..d53ff65a 100644 --- a/lib/audioeffects/transientshaper.py +++ b/lib/audioeffects/transientshaper.py @@ -132,9 +132,8 @@ class TransientShaper(_component.Component): **Levels.** At its defaults the class is a wire. Attack and Sustain add gain where they are turned up, so a hit that is already loud reaches the rails. Keep the input under -12 dBFS and the defaults and patches stay off - the rails. Attack at its top can still push the output onto the rails. A - plucked phrase stays off the rails on every patch to -10 dBFS; at -3 dBFS - Snap and Kick Punch rail. Turn Output down, or your input. + the rails. Attack at its top can still push the output onto the rails. Turn + Output down, or your input. **Timing.** Latency is zero at every setting, and so is the tail: silence in is silence out in the same frame. diff --git a/tests/test_cpython_effects_combfilter.py b/tests/test_cpython_effects_combfilter.py index 18c3d198..7e34d9c5 100644 --- a/tests/test_cpython_effects_combfilter.py +++ b/tests/test_cpython_effects_combfilter.py @@ -1191,23 +1191,6 @@ def test_a_mono_source_gets_the_same_tuning(self): self.assertLess(abs(cents(centroid(y, ideal), ideal)), 5.0) -class TheInputCeiling(unittest.TestCase): - """audiocomponents#192: the kit's plucked phrase is clean on every - patch at -9 dBFS, and rails at -3 dBFS on the four patches the - docstring names.""" - - def test_a_plucked_phrase_is_clean_to_minus_9_dbfs(self): - import kit_levels - self.assertEqual(kit_levels.red_patches(combfilter.CombFilter, -9), - []) - - def test_at_minus_3_dbfs_four_patches_rail(self): - import kit_levels - self.assertEqual(kit_levels.red_patches(combfilter.CombFilter, -3), - [1, 3, 4, 5]) - names = [combfilter.CombFilter.PATCHES[k][0] for k in (1, 3, 4, 5)] - self.assertEqual(names, ["Ringing Pitch", "Dark Resonator", - "Single Slap", "Filter Only"]) class TheClaims(unittest.TestCase): @@ -1299,11 +1282,7 @@ def test_clean_at_the_stated_input_level(self): ("TheTuningKnobIsClickFree.test_the_boundary_is_not_findable", "TheTuningKnobIsClickFree.test_a_jumped_read_head_is_red")), ("A comb gains, so a loud input can reach the rail by design.", - ("TheInputCeiling.test_at_minus_3_dbfs_four_patches_rail",)), - ("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.", - ("TheInputCeiling.test_a_plucked_phrase_is_clean_to_minus_9_dbfs", - "TheInputCeiling.test_at_minus_3_dbfs_four_patches_rail")), + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("`reset()` empties the line.", ("ResetEmptiesTheLine.test_reset_leaves_the_line_silent_and_the_" "source_rendering",)), @@ -1357,7 +1336,7 @@ def test_every_claim_is_in_the_docstring_and_tested(self): # docstring does not carry, a test that does not exist. self.assertTrue(claim_problems(doc + " It reads 12 ms.")) self.assertTrue(claim_problems(_flat(doc).replace( - "up to -9 dBFS", "up to -6 dBFS"))) + "under -24 dBFS", "under -18 dBFS"))) self.assertTrue(claim_problems( doc, CLAIMS + (("Latency", ("TheClaims.test_nothing_here",)),))) diff --git a/tests/test_cpython_effects_ladderfilter.py b/tests/test_cpython_effects_ladderfilter.py index 71c11eeb..875bf094 100644 --- a/tests/test_cpython_effects_ladderfilter.py +++ b/tests/test_cpython_effects_ladderfilter.py @@ -1,7 +1,5 @@ -"""`LadderFilter`'s input ceiling: the kit's plucked phrase is clean on -every patch at -10 dBFS and rails at -3 dBFS on Growl With Drive alone -(audiocomponents#192), and the class docstring's claims, each tied to its -test by the `CLAIMS` table. +"""`LadderFilter`'s input ceiling and the class docstring's claims, each +tied to its test by the `CLAIMS` table. python -m unittest tests.test_cpython_effects_ladderfilter """ @@ -17,7 +15,6 @@ sys.path.insert(0, os.path.join(ROOT, "tests", "support")) import kit_ceiling # noqa: E402 -import kit_levels # noqa: E402 import kit_probes as probes # noqa: E402 import numpy as np # noqa: E402 from audioeffects import ladderfilter # noqa: E402 @@ -53,14 +50,6 @@ def burst_then_silence(seconds=2.0): + [0] * int(2 * seconds * RATE)) -class TheInputCeiling(unittest.TestCase): - - def test_a_plucked_phrase_is_clean_to_minus_10_dbfs(self): - self.assertEqual(kit_levels.red_patches(LadderFilter, -10), []) - - def test_at_minus_3_dbfs_growl_with_drive_rails(self): - self.assertEqual(kit_levels.red_patches(LadderFilter, -3), [4]) - self.assertEqual(LadderFilter.PATCHES[4][0], "Growl With Drive") class TheClaims(unittest.TestCase): @@ -249,11 +238,7 @@ def test_clean_at_the_stated_input_level(self): "TheClaims.test_reset_stops_the_tone")), ("The resonance peak and the drive both add gain, so no input level is " "clean on every setting.", - ("TheInputCeiling.test_at_minus_3_dbfs_growl_with_drive_rails",)), - ("A plucked phrase stays off the rails on every patch to -10 dBFS; at " - "-3 dBFS Growl With Drive rails.", - ("TheInputCeiling.test_a_plucked_phrase_is_clean_to_minus_10_dbfs", - "TheInputCeiling.test_at_minus_3_dbfs_growl_with_drive_rails")), + ("TheCeiling.test_clean_at_the_stated_input_level",)), ("The class needs audiodsp's `audioladder`, and on a board without it " "construction raises `ImportError`.", ("TheClaims.test_construction_needs_audioladder",)), diff --git a/tests/test_cpython_effects_reverb.py b/tests/test_cpython_effects_reverb.py index 13ccf998..ce610788 100644 --- a/tests/test_cpython_effects_reverb.py +++ b/tests/test_cpython_effects_reverb.py @@ -1805,14 +1805,6 @@ def sine_wet_gain(hz, rms, rate=RATE): class InputCeiling(unittest.TestCase): - def test_a_plucked_phrase_is_clean_to_minus_4_dbfs(self): - """audiocomponents#192: the kit's phrase is clean on every patch - at -4 dBFS, and rails at -3 dBFS on Slow Bloom alone.""" - import kit_levels - self.assertEqual(kit_levels.red_patches(Reverb, -4), []) - self.assertEqual(kit_levels.red_patches(Reverb, -3), [9]) - self.assertEqual(Reverb.PATCHES[9][0], "Slow Bloom") - def test_no_patch_reaches_the_rail_at_4000_lsb_rms(self): # on eight noise seeds, at three rates, each patch's peak stays # under the rail by more than the spread of its peaks @@ -1922,9 +1914,6 @@ def test_clean_at_the_stated_input_level(self): "reaches the rail at 48, 44.1 or 22.05 kHz; Bright Chamber at 44.1 kHz " "is not claimed.", ("InputCeiling.test_no_patch_reaches_the_rail_at_4000_lsb_rms",)), - ("A plucked phrase stays off the rails on every patch to -4 dBFS; at " - "-3 dBFS Slow Bloom rails.", - ("InputCeiling.test_a_plucked_phrase_is_clean_to_minus_4_dbfs",)), ("`tail_samples` bounds the frames until the output is exactly zero " "once your input stops: 222 868 frames at the defaults at 48 kHz.", ("Tier1.test_the_tail_reaches_exact_zero_inside_tail_samples", diff --git a/tests/test_cpython_effects_transientshaper.py b/tests/test_cpython_effects_transientshaper.py index 6e5813b9..478e11cc 100644 --- a/tests/test_cpython_effects_transientshaper.py +++ b/tests/test_cpython_effects_transientshaper.py @@ -220,21 +220,6 @@ def test_t5_the_rms_detector_settles_where_the_peak_detector_does_not( self.assertGreater(steady["peak"], 1.5) -class TheInputCeiling(unittest.TestCase): - """audiocomponents#192: the kit's plucked phrase is clean on every - patch at -10 dBFS, and rails at -3 dBFS on Snap and Kick Punch.""" - - def test_a_plucked_phrase_is_clean_to_minus_10_dbfs(self): - import kit_levels - self.assertEqual(kit_levels.red_patches( - transientshaper.TransientShaper, -10), []) - - def test_at_minus_3_dbfs_snap_and_kick_punch_rail(self): - import kit_levels - cls = transientshaper.TransientShaper - self.assertEqual(kit_levels.red_patches(cls, -3), [1, 5]) - self.assertEqual([cls.PATCHES[k][0] for k in (1, 5)], - ["Snap", "Kick Punch"]) def attack(db): @@ -357,10 +342,6 @@ def test_clean_at_the_stated_input_level(self): ("On a slow decay Sustain runs out before its end: on a note fading at " "3 dB a second, Sustain -24 reaches about -16 dB.", ("TheClaims.test_sustain_runs_out_on_a_slow_decay",)), - ("A plucked phrase stays off the rails on every patch to -10 dBFS; at " - "-3 dBFS Snap and Kick Punch rail.", - ("TheInputCeiling.test_a_plucked_phrase_is_clean_to_minus_10_dbfs", - "TheInputCeiling.test_at_minus_3_dbfs_snap_and_kick_punch_rail")), ("Latency is zero at every setting, and so is the tail: silence in is " "silence out in the same frame.", ("TheClaims.test_latency_is_zero",