Repository navigation
Effects Phase 6: pitch, stereo, racks, and the soundtrack's classes - #141
Merged
Merged
Conversation
…, input ceiling and paperwork checks Six mechanical checks on one class, each printing one line per cell and a SUMMARY per check. The four render checks are numpy-free and print the same lines on CPython, MicroPython and CircuitPython; the claims and paperwork checks read source and git, on CPython. kit_planted.py holds the tiny planted components the checks are proven on.
tests/test_kit.py holds every kit check red on a planted fault with its control green on the same cell, a do-nothing class never read as a pass, and the render checks printing the same lines on CPython, MicroPython and CircuitPython (with a plant whose bytes differ natively to show that agreement can fail). KIT.md says what each check measures, its bars and conventions, and how a class declares an exception. The ceiling's wrap rule now asks that the jump unwraps to a neighbour, so a full-scale click followed by a delay's repeat is not read as a wrap.
…nd Character Haas crossfades the right channel toward a copy of itself 1 to 40 ms late and turns the left down above Width 1; Mid/Side scales the difference between the channels on audioroute.MidSide. A mono source is a wire, a Delay move glides, and the six traits each have a test shown red on a planted fault (KIT_PLANTS), with every docstring claim in CLAIMS. The lifecycle matrix declares the family's control-jump step and one glide cell; the kit declares the slew plant, which only a moving control shows.
program_change's body moves into a private _apply_positions(positions, patch_index), which program_change calls with a patch's positions. A rack calls it to put a child back exactly as it built it, which the public set_macro cannot do for a measured ConvolutionReverb or a setting off the 0-127 grid. The classes that wrapped program_change to do work once per patch (the deferred refreshes, the lean re-routes, GraphicEQ's clean bank, the old Reverb's single rebuild) now wrap _apply_positions, so a patch and a restore take the same path. The test plants that stood for the base's per-macro path move with them.
rebuilt/rack.py beside the old class. The chain renders exactly the same pedals patched by hand; latency_samples and tail_samples are live sums; reset() is each child's reset then patch 0, which puts every child back as built; deinit() releases every child and a failed build releases what it built; children get the rack's transport and transport_changed() reaches nested racks; capabilities is the union of the children's. Tests for T1-T6 with their planted faults, the CLAIMS table and KIT_PLANTS. The kit runs Rack on the probe chain K (KIT_OPTIONS), and the lifecycle CLI reads the same table; the lifecycle skips its macro events for a class with no macros. Declared: the plant cells whose faults no render reaches, ConvolutionReverb's own 0 dBFS sine ceiling, and the E1 P4 cells Vibrato and Fuzz are red on alone.
…lock without get_buffer The constructor's exact delay_ms plays until Delay moves or a patch loads, and the patches' Delays play their round numbers by step, so patch 0 and reset() are 14 ms whatever the constructor said. Where audiocore.get_buffer is compiled out, a constructor patch no longer wires Haas, and a return to Haas re-plays the voices, so a block a voice held is dropped rather than played; the one-block skip that remains is in the docstring. The clipping sentence says below half of full scale, tested at the bound, and the H1, W and M1 tests run every seed and rate the traits list.
The send runs through an audioecho.FeedbackDelay with its loop filters, an audiobiquad Top Cut and the rebuilt Reverb as a chamber; the dry comes back untouched beside it. Eleven macros and seven patches, the card's five traits each tested and shown red on its planted fault (the plants live in tests/airspace_plants.py so the kit checks them natively), every docstring claim tied to a test. Declared: the chamber's P4 rows (a reset or a return from Mix 0 empties the tank, which never re-converges with an uninterrupted one) and the glide plant, which only a Time move can show.
…rt) and the CPython dry check (audiodsp#228)
… refused program_change keeps the exact Delay
… row no longer hides Rack's own reset faults
Where audiocore.get_buffer is compiled out, the class could not hand out the block a voice primes, so after wiring (or a re-play) a voice could hold a block read from the line that a reset then emptied, or read at a Delay the reset then left: the copy of that block never played, and the right channel was silent for one block. The class now pulls the mixer through a one-voice mixer of its own playing the Haas tail, so wiring primes from silence and hands it out on every build, and no voice ever holds a block between pulls. The re-play special cases are gone, and so is the disclosed one-block skip when leaving Haas before its first block. Renders with get_buffer are unchanged byte for byte. The new test compares both channels with and without get_buffer from the first frame, twelve paths at 48, 16 and 8 kHz.
…hidden), each red on a plant
The OC-2's octave-down on a clock leg (two Q 0.5 low-passes, a dead-band comparator, SubOctave) and a signal leg (a matched two-stage AllPass and Multiply), Octave 2 dividing Tone 1's output, three voices on a Mixer. Seven macros, six patches; constructor values land on the 0-127 grid. Declares the divider's free count (lifecycle P4), the family P5 rows, CircuitPython's own Mixer on a host reset, and clicks under Sensitivity in the ceiling check.
…ntence At 22.05 kHz a macro move that reaches the tape's loop or the chamber leaves the tank off the uninterrupted control for good (P4, as Reverb's), and Mix, Top Cut and patch moves step the output (P5, the family's disclosed limit). The docstring's sentence on jumping controls now names those declared rows.
…is compiled out On a board's own audiocore the constructor's wiring primes the source's first block into the voices, and a Mix 0 before the first pull would play that block stale on the return. The return now re-plays the voices there; mid-stream it renders byte for byte what the quiet wiring renders. Tested with get_buffer hidden from the class and _component, with the replay taken out as the red control.
… the pin move in the changelog
A rack's reset used to run each pedal's own reset, which loads patch 0, then put the rack's settings back over the top. A pedal built at Mix 0 re-armed its voices inside that patch 0 and pulled a block of the source, so the rack's output skipped 256 frames and the held block played stale when Mix came back up (Compressor, Saturation, Bitcrusher, DynamicEQ, Exciter, Overdrive). The put-back also lost the exact constructor values a delay keeps beside its knobs, so DigitalDelay(glide_ms=10) glided for 815 ms on its next Time move. Component gains a small private hook: _record() takes an instance's positions, patch index and the values its class names in _KEPT, and _restore() puts them back with _seeding set. Component.reset() reloads a record a parent hands in through _reset_to in place of patch 0, so a child's own reset clears and restores in one step and never passes through patch 0. With no record handed in, every class's reset is what it was: 550 renders of every class at every patch, reset mid-stream, are byte-identical before and after. The five delays that keep exact values (DigitalDelay, TapeDelay, AnalogDelay, SlapbackDelay, PingPongDelay) name them in _KEPT. A refused patch= now releases every pedal the rack built.
… 2, a return from Mix 0 to 127 and a Mix move before the first pull
…tion names it and is held to a table the scan finds
…ts exact-value flags on a rack's reset
…player's words, each with a test; a seventh patch, Octave Bloom - lean (Rise at Rise 0), for an S3; the lower-rate band claim struck
# Conflicts: # CHANGELOG.md # tests/support/kit_common.py # tests/support/lifecycle.py
At -12 dBFS a square railed at Mix 1 with Feedback, Decay or Time at 127, on Long Send and on Dark Loop. The worst peak gain over every patch, every other macro at its stops, all macros open, six low squares and noise is 16.47 dB (Decay 127, 220 Hz), so -17 leaves 0.51 dB under the rail. The ceiling test renders the worst cells for 12 s.
…hecks read them (Windows wrote cp1252)
…he -26 dBFS for Feedback, Decay and Time open together Pairs and triples of Feedback, Decay and Time at 127 on every patch, over 110, 147 and 220 Hz squares and 12 s, gain up to 24.71 dB (Short Send with Feedback and Decay). One test pins that gain under the quoted 25 dB.
Rebuilt StereoWidener, Rack and Octaver
…ys clean at 22.05 kHz; a sweep of every upward patch, red on the card's Slow Rise
# Conflicts: # CHANGELOG.md # tests/support/kit_common.py # tests/test_cpython_effects_rack.py
…to play a Rise patch there, pinned to the patches in Bloom
# Conflicts: # CHANGELOG.md # tests/support/lifecycle.py
… Rise 0, which an ESP32-S3 keeps up with; Octave Bloom moves to patch 6 Patch 0 is now Rise at Rise 0 (steps 127, 89, 0, 90, 96, 94, 64, 91, 32), and the old defaults are patch 6, Octave Bloom. The S3 sentence names the Bloom patches as they now are. Tests that measured Bloom at the defaults ask for it by name, so every trait is still held where it was.
Rebuilt PitchShifter, Harmonizer and AirSpace
…ShimmerHall are adopted: Phase 6's seven ADOPTED names all seven, so audioeffects.<Name> and create() serve the rebuilds. They stay under rebuilt/, and pitch.py and rack.py keep the old classes until the rebuilds are promoted. The old ShimmerHall built its octave branch by NAME with the old Octaver's down= and up=, which the rebuild refuses, so it now names pitch.Octaver directly, as it already does for the tape delay and the reverb. Its render and AirSpace's old rack are unchanged when built from rack.py. Tests: each class gains an "adopted is what the package serves" test (create() returns the rebuild), and the registry names all fifteen adopted classes; with ADOPTED cut back to the first eight, all of them go red. The tests written against the old classes by their served name now name them from their own module: test_cpython_effects_pitch (the shifter-based Octaver's octave reach and shifter count) and test_cpython_effects_racks (the old Rack, ShimmerHall and AirSpace). Their assertions are unchanged. The CHANGELOG entries for the seven say they are adopted (2026-10-13), and the note on the soundtrack racks now includes the octaver. Gates, this tree: validate_api 55 instruments / 45 effects; metadata valid; flake8 clean; contract and the seven classes' scoped tests, the kit and the registry OK; the library smoke 45 classes, 318 patches, 0 failures on CPython, MicroPython and CircuitPython. The CPython digest census (burst plus two seconds, every served class at the defaults and every patch) moves only the six served names whose rebuilds differ from the old classes, onto the rebuilds' own digests; Rack's default chain is a wire either way, and every other class is byte-identical.
Rebuilt ShimmerHall
At the Rise defaults the hall sounds at once, so a Size or Mix jump early in the render lands on sound and steps the output: the disclosed family limit (a control that jumps makes the output step; moving Size empties the hall). On CircuitPython, Octave Bloom loaded before the first pull now re-arms Bloom's shifter from the defaults, and CircuitPython's own PitchShift keeps its positions through a clear, as for the other Bloom patches.
ShimmerHall plays on every board; Phase 6's classes adopted
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges the Phase 6 branch into main. Seven classes through the class gate, each on the phase branch and adopted there (#135, #136, #137, #140); the kit (#133's commits, already on this branch) and the lifecycle matrix with Phase 6's rows.
The merge with main auto-resolved in tests/test_lifecycle_matrix.py: main's run_cpython_all split and the Phase 6 rows are both kept. The branch CI is the three-job PR run; the push to main then runs all six.
Supersedes #133, whose commits are already on this branch.