Repository navigation
Effects: Flanger on audiodelays.Flanger, and a round of fixes across the library - #165
Merged
Merged
Conversation
…lays.Flanger in the node matrix AUDIODSP_PIN names the 7b candidate by commit until v0.6.6rc2 is cut. NodeFlanger joins the node matrix: green on CPython and MicroPython with its sweep's free-running LFO declared (reset, Rate moves, patches), and its E8 skipped on CircuitPython, where upstream's Flanger never returns from a looped source that ends empty, as Echo and the pitch shifters already are. NodePitchShift declares E1 with E2 and a freeze set mid-splice, and its CircuitPython difference is restated to the splice alignment (285 cells).
…fter's splice matches a chord as a chord
A class that raised without get_buffer at one patch read as a single 'run' cell for the whole class, and the check ran on CPython only, so a path a class takes off CPython was never checked. Each patch and layout is now its own run/<rate>/<ch>/<patch> cell, and the stand-in works on MicroPython and CircuitPython (no __mro__, vars or instance __dict__ there). Plants: PatchPulls (raises at the middle patch only) and NativeFlush (leans on get_buffer only off CPython), both red on the old check's tests and green on this one.
The class is the node's controls (Rate, Range, Manual, Color, Mix,
Polarity, Sync, Division), a live tail bound and a tempo, at the stock
tier. create("Flanger") serves it from audioeffects/flanger.py;
modulation.py and the parked rebuild under rebuilt/ are gone.
Its six traits are tested through the class with a planted fault each;
the lifecycle matrix declares the free-running sweep's P4 and the N1 step,
and skips E8 on CircuitPython, whose own Flanger spins on a source that
comes back empty. The cost tool gains an audiodelays.Flanger node row.
… through get_buffer _RampTap and _SplitOut exist only on the CPython twin and pulled their upstream with the script-facing audiocore.get_buffer on every pull, so the class raised wherever that is hidden. They now pull with the twin's in-graph _borrow and copy what they keep: the same bytes. Red then green in TheGraphPullsWithoutGetBuffer and the kit's nogb (CPython; the native graph never had the path).
reset() pushed each of the eight taps empty with audiocore.get_buffer, which raises where CircuitPython's board builds compile it out. A Splitter's ring cannot be cleared, so the reset now builds the three Splitters again on the same nodes and releases the old ones, on every interpreter: renders with and without get_buffer are byte-identical. The reset's own bytes move (it was not a fresh instance before or after; see the 7d report). Red then green in TheResetNeedsNoGetBuffer and the kit's nogb on three interpreters.
_charge_output_cap pulled the output capacitor with audiocore.get_buffer unconditionally at patches 6 to 9. Where it is absent the charge is now skipped, as Fuzz and Overdrive skip theirs; the scoop tables answer zero with zero, so nothing differs: every patch renders the same bytes with and without it. Red then green in TheScoopBuildsWithoutGetBuffer and the kit's nogb on three interpreters.
… tests transport_changed() now sets the sweep again when the meter moves at one tempo; the base followed the bpm alone. New cells hold Polarity from its knob and patch 3, Sync from its knob and patch 5, a 4/4 to 3/4 change, and a rack's restore of the exact constructor values, each beside a plant the old tests let through. The ring sentence no longer reads as a ceiling.
transport_changed() treats a NaN tempo or meter as unchanged, as the base does, and applies the Sync switch through _apply_macro, honouring _SYNC_MACRO, so the shared tempo tests hold it; Flanger joins their SYNCED list. The restore test gains its plant.
…m every patch kit_fresh.py holds a fresh instance to patch 0 exactly (boot), and a reset from the defaults and every patch, before the first pull and mid-stream, to a fresh instance handed patch 0 and fed the source from where the reset found it: the bytes for 2 s, the readback, and each macro moved after the reset. Shown red on three plants (OffGrid, PatchLeak, MoveLeak) against FreshKit, with the same counts on CPython, MicroPython and CircuitPython.
…patch is the onset again reset() re-played the mixer's voices inside the walk, before the chain behind them was cleared, so the first block after a reset was the old audio (1504 LSB at the defaults, every patch). The voices now stop in the walk and the graph is armed after it the way _build arms it. On CircuitPython the host reset of the stock Mixer also cost the reset a block of the source; stopping the voices instead keeps it. A visit to the lean patch set latency_samples from the group delay, so it read 3 at tube x4 afterwards where a fresh instance reads the onset, 1; the lean patch itself now reads the x2 onset. Two tests that pinned the group delay are restated to the onset CLICK_ONSET_SAMPLES declares. The fresh check builds its fresh instance with the constructor's patch=, as the lifecycle's PR does: a live program_change may wait for a zero crossing, and a reset is held to a build.
…re on the node A reset alone comes back as a fresh instance (green). A Time 0 move after it does not: audiodelays.MultiTapDelay's reset_buffer empties the line but leaves the write head where it was, so a shorter Time keeps a different stretch of the line than on a fresh node. Red on CPython, MicroPython and CircuitPython alike; a reset that puts the twin's write head at the top turns it green. The cure is the node's, so the test is an expected failure until it lands.
…nded, as built The rack's put-back can land Saturation on Mix 0; the reset armed the graph and then routed around it, which marked it stranded where a build does not (test_cpython_effects_rack TestReset, three interpreters).
…et lands where it starts Time was built off the 0-127 grid: a fresh DigitalDelay played 350 ms beside patch 0's 352.8 ms (grid 102), and a SlapbackDelay 135 ms beside 134.4 ms (grid 84), so a reset sounded different from a fresh instance and reported a looser tail (186 296 against 184 811 frames). An option left out now plays patch 0's step (_init_macros' new defaults flags); a value you pass plays as passed. NaN on SlapbackDelay takes the step, as it took the default. Tests that read figures at the dossier's settings pass them explicitly (DOSSIER, time_ms=350.0); tests about the defaults read patch 0's steps. The kit's ctor and opt cells read an option whose default is None from the class's _OPTION_DEFAULTS. CHANGELOG lines for these and Saturation.
… and nothing is charged Bias was the node's own offset into an asymmetric curve, so the shaper answered silence with a constant and the plate pole passed the step: 8489 / 7044 LSB out of silence at patches 4 and 6. The class charged the pole by pulling it with audiocore.get_buffer, which left 1 LSB, had to run again on every Bias, Drive, Output, Headroom and Hysteresis move, and where get_buffer is compiled out did nothing, so the bang came back. The shapers now read a table the class builds in integers: the curve at (1 + |Bias|) * u + Bias, less its value at u = 0, with pre_gain divided by the span and post_gain carrying any scale the table needed to fit. Silence reads exactly zero at every Bias, on every interpreter, with or without get_buffer, and the charge is gone. Red first: SilenceAnswersSilenceOffCentre (patches 4 and 6 on silence, with and without get_buffer; and nogb bytes) was 20 failures before. Plants that replaced the curve now fold Bias into theirs (_fold_planted); NodeBiasSaturation plants the old node offset in place of the two charge plants. Bias +0.25's A4 floor moved onto the bar (-60.047).
reset() cleared the graph in place and never came back fresh: lifecycle E1's PR read 'differs from a fresh instance at +3' from the defaults and every patch. It now builds the graph again at patch 0, re-points the port at the new tail, then releases the old graph. An option left out of the constructor plays patch 0's step (Component._init_macros(defaults=), the same hunk as on effects-p7/7d-reset), so a fresh instance is patch 0 exactly. Red then green in AResetIsAFreshInstance and the fresh check (86 red, now 0). The STATE harness tests now put the silent source under the class before the reset, since a fresh instance takes its first block from its source as it is built.
Patch values are integers and a declared default may sit off their grid (docs/audio-components.md), so a fresh instance reading exactly patch 0 is not a contract claim. boot now prints na(info: ...) where they differ; the reset cells, judged against a fresh instance built at patch 0, are the check. OffGrid becomes a control that is never red; PatchLeak and MoveLeak stay the plants.
…odsp's node fix Green against audiodsp effects-p7/mtd-reset, whose reset_buffer puts the write heads at the top; red at v0.6.6rc3 until the pin moves. The kit's DECLARED rows can name one interpreter, and CircuitPython's Time 0 cells after a reset are declared there: its MultiTapDelay is upstream's, which keeps the heads. NodeMultiTapDelay joins the node matrix.
…ollows it With Sync on, the sweep lasts Division bars of numerator x 4 / denominator quarter notes, the transport's fourth and fifth fields; it read the denominator alone as the beats in a bar, so 3/4 swept as 4/4 and 6/8 as eight beats. transport_changed() now sets the rate again when the time signature moves at one tempo, with NaN read twice as no change, as the base has it for a tempo. In 4/4 no byte moves. MeterDeaf, the base's follow, is the control the new tests tell apart.
…d room The room is normalised to unit energy, so a dark one gathers it low: an 82 Hz square at Mix 2 and Damping's 500 Hz stop comes back 11.06 times its peak (+20.87 dB), the worst of the kit's ceiling grid, and a 0 dBFS 50 Hz sine railed for 156 frames at the defaults. INPUT_CEILING_DBFS is -21.0, the docstring says so with its claim row, and the test holds the worst cells clean there and red 1.5 dB hotter. No byte of the audio path moves.
The API now says it: below 0, minus infinity and NaN set the bottom stop, above 127 and plus infinity the top, and the component plays what that stop plays. It is what the base class, every effect but CabinetSim and all 55 instruments already did. The kit's bad-m cells hold it: a raise of any kind is red and NaN must read back 0 (NanRaises and NanTop, green before, are red now). CabinetSim's snap clamps before int(), so +inf is the top stop instead of OverflowError; no finite value moves.
…t docs A reset is a fresh instance exactly on both (PR). P4 holds it to the control that never was reset, which neither can meet again: Vibrato's reset restarts its LFO, and Fuzz's 10.5 and 12.4 Hz coupling sections keep a few LSB of the earlier material in float state for seconds, raised to a few hundred by the 36 dB stage. Each class's test holds the declaration, and Fuzz's holds the residue under 1024 LSB. KIT.md and LIFECYCLE.md name the clamp contract's cells and plants; CHANGELOG lines for the three fixes.
The reset emptied the tape with the outgoing patch's Time as the head's reach, and patch 0's shorter Time kept it, so from patch 2 tail_samples read 361 384 frames where a fresh instance reads 251 836. A tape just emptied lands its head on the next Time it is handed, so that Time is the reach. Kit fresh green on three interpreters; the class tests pass.
With Sync on it read the transport's fifth field, the denominator, as the beats in a bar, so a host in 3/4 swept as 4/4 and one in 6/8 as eight beats; the tests' Transport put the meter there too and hid it. The test transport now hands (bpm, numerator, denominator) in the API's order, a new cell holds 3/4, 6/8 and 7/8, and transport_changed() follows all three fields (NaN read twice is no change). In 4/4 no byte moves; Phaser's rule.
… took Building the input stage again dropped the rest of a source buffer the old adapter had already taken, so a reset that landed part-way through one left the render shifted against an uninterrupted instance until the source ended (lifecycle E1-reset@part P4, red at 0ec69f5 too). The reset now keeps the adapter. Red then green in AResetPartWayThroughABufferDropsNothing; the fresh check stays 0 red.
…rts silence (recentre)
…n unconditionally rc7 fixes CPython's buffer-GC crash, Dynamics' mono stride and adds Splitter.clear(); rc8 swaps FeedbackDelay's wow_shape at zero depth. test_bypass_return's Splitter case no longer skips below the pin: it fails there instead (red at rc6, green at rc8).
491eff7 added 10**400 and -10**400 to the kit's off-grid values and updated test_kit, not this copy of the expected cell.
Overdrive is LAZY_BUILD now, so ClearOnlyRejoin keeps a block of silence and plays nothing: it takes EagerClearOnlyRejoin, as the other lazily built classes' tests do. Rack's T4 Mix 0 plant, PutBackRack, was red only through the pedals' eager re-arm on the Mix move after a reset (Bitcrusher, Overdrive, Saturation); with the re-arm lazy it renders as built, so the Mix 0 check gets PrimingRack, a reset that takes a block from each pedal. PutBackRack stays the exact-values plant. Both red at the merge, green here on CPython, MicroPython and CircuitPython.
…hout get_buffer does
Leaving Mix 0 under signal starts the wet chain cold for 16 samples, until the mixer level ramp lands. The lazy re-arm that causes it stays: priming from the source loses a block under a following Mix 0 (F1).
…n the way back from Mix 0 lc-2 cleared the Splitter on a rejoin but not the node reading its wet tap, which carries part of a chunk across blocks on source buffers that aren't 256 frames. At Telephone Grid (patch 3) that played up to 5 888 LSB from before the bypass. The lifecycle matrix's E9-src1000 P3 cell found it; test_bypass_return now holds patch 3, red without the change.
tests/test_lifecycle_matrix.py gated only the eight Phase 5 classes, so the Phase 2 to 4 classes' red cells were never held. It now runs all 45 (audioeffects.ALL). What is red today is in tests/support/lifecycle_known.py: 267 KNOWN_RED rows, one per (class, event, property), counted on the quick matrix on CPython at audiodsp v0.6.6rc8, each with its cause and the issue that tracks it (#153 control jumps, #154 free-running phases, #155 state past tail_samples, #156 stated behaviours, #157, #142), and five CircuitPython-own KNOWN_P6 rows (#158). MicroPython prints CPython's line for every cell of every class.
rc8's FeedbackDelay takes a new wow_shape through zero depth, so a Wow or Flutter move that changes their balance no longer steps (730 and 729 against the tone's 728, where rc7 read 803 and 1 117). The docstring says so, the test holds the new figures (red at rc6), and NoneAtZeroTape, which the node now renders as the class does, leaves the depth-move plants; the read-head jump plant stays.
…building added Since Overdrive builds lazily, the declared residue (the get_buffer path stops the pole charge at the first zero block, the board path runs all 64) also reads on E10-reset and E10-patch at patch 6 and E10-macros at the defaults. Same cause, same P4-only difference; nogb is clean on CPython, MicroPython and CircuitPython with the rows.
A fresh Overdrive now primes silence, so in mono a reset renders as a fresh build: test_overdrive_mono passes (it was an unexpected success), and the kit's 132 mono fresh declarations read ok on all three interpreters without being used. Both come out. What is left of #142 is a PR difference at patch 6, 22.05 kHz, which the lifecycle row now says.
Measured: from frame 10 a move to patch 7 before the first pull differs from a fresh patch=7 build by at most 2 LSB, in about one frame in eight, and keeps doing so past 3 s (22.05 kHz stereo). Inaudible; filed with a trigger and cited in the row.
…can move On a pull request LIFECYCLE_CHANGED_FROM names the base branch, and the matrix test asks git which files changed: a class module's change runs that class, a shared one (the base and package modules, tests/support, the matrix test, AUDIODSP_PIN) all 45, and a change to neither none. A push to main sets nothing and runs all 45. The checkout fetches history on a pull request so the base is there; without it the test falls back to all 45. test_a_change_picks_the_classes_it_can_move holds the selection.
…, about 13 minutes All 45 classes take 4.4 CPU hours on CPython. A pull request that changes a shared file now runs SAMPLE, one class per mechanism family (Bitcrusher, Tremolo, DigitalDelay, Reverb, Octaver, LowPass, Rack, Saturation, Distortion), beside the classes whose own module it changes: 3 114 CPU seconds, about 13 minutes on a 4-core runner. A push to main, a manual run or the 'lifecycle: all classes' label runs all 45. KIT.md and the workflow say why.
…he old bound at rc9 rc9's biquad walks a corner move over 20 ms instead of carrying the old section's state into the new one, so the move that rang 279 149 frames (LowPass), 8 981 (BandPass) and 288 768 (Notch) now rings 15 to 132 063. The old-bound controls are restated to that: each now holds the ring inside the pre-#114 bound, red at rc8 and green at rc9.
Recounted on the quick matrix at v0.6.6rc9, all 45 classes, three interpreters. The Frequency-jump P5 rows of LowPass, BandPass and Notch are gone with the thump; seventeen classes on the biquad changed cells. Where a corner move now walks over 20 ms, a move and back meets the control up to 20 ms past the bound (R_STATE_RAMP), and Octaver's patch picked before the first pull glides for 30 ms (#164). 273 rows; P6 unchanged: MicroPython prints CPython's line everywhere, CircuitPython differs only in the known rows.
The same restatement as the three filters: rc9's walk leaves the move ringing 56 frames where it rang 19 372, so the old-bound control holds the ring inside the pre-#114 bound (red at rc8, green at rc9).
…c9 (#164) Arming already cleared the all-pass, which had heard only zeros, so a Tracking move before the first pull lands at once. rc9's biquad walks any move after its first block, so the clock and Tone biquads (also only zeros) are cleared there too; a patch before the first pull renders as a fresh build again. test_a_patch_before_the_first_pull_renders_as_fresh was red at rc9 and is green; Octaver's lifecycle matrix is clean on all three interpreters and its E12 rows leave the gate (271 rows).
…start, and the old tail figure holds rc9's biquad walks a coefficient move over about 21 ms with its state remapped (#162). NoWakeGraphicEQ's band now walks in from the curve it held before the detent, which can stay under the fresh start's own overshoot, so the peak missed three crossings. The test now reads how far the faulted render departs from the class's fresh start: 0.25 to 33 times the settled level at rc9, and zero where the detent is never crossed. A Gain or Volume move to the bottom rings 9 566 frames, inside the old 0.47 s it outlived up to rc8, so that control is restated to hold.
Building a class used to pull one block from its source and throw it away, so a probe the render's own length ran dry 88 frames early. The lazy build no longer does, the render reached its last frame live, and the guard's two tests had nothing to catch. The probe is now planted 256 frames short: 98 silent frames, a -33.1 dB floor against -115.6 with headroom.
…ks; the node matrix takes the same rule fetch-depth was written `== 'pull_request' && 0 || 1`, and 0 is false in an expression, so every pull request checked out at depth 1. With no base to diff from, the lifecycle matrix fell back to all 45 classes and a pull request's run took 1 h 43 min. The expression now reads `!= ... && 1 || 0`, a base git cannot diff from is an error rather than a full run, and a new step prints what each matrix will run. The node matrix runs the nodes whose adapter class or table rows a pull request changes, read by the line, and NODE_SAMPLE (Biquad, Echo, SplitMix, ShimmerLoop: 86 s of 541 on CPython) when a shared file changes. Main runs every node. KIT.md and the workflow say why.
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.
The effects library's next release.
New:
Flangeris now a thin class on CircuitPython'saudiodelays.Flanger, and the two older Flanger implementations are gone.Fixed, each with a test that failed before:
bands=2there.Needs audiodsp 0.6.6 (pinned at its release candidate here); the filters' coefficient moves, the delay's wow ramp and shape swap, and several node fixes come from it.
Tests: the lifecycle matrix now gates all 45 classes. A pull request runs the classes it touches, a change to a shared module runs a nine-class sample, and a push to main runs all of them.
Known and filed: #142, #143 to #158, #160, #161.