Repository navigation
audioladder: a cutoff input read every sample, for audio-rate filter FM - #293
Merged
Merged
Conversation
set(cutoff_hz=...) moves the cutoff once a block, so a cutoff swept by an audio-rate signal is a staircase and buzzes at the block rate. Ladder now takes a `cutoff` audio input, connected with modulate(cutoff=...) or in the constructor, as audiooscillator.Oscillator takes its pitch: the cutoff is cutoff_hz * 2 ** (v * cutoff_octaves) for an input at v of full scale, first channel only, clamped to the band cutoff_hz is. cutoff_octaves defaults to 10, the oscillator's pitch scale. The per-sample coefficients come from a deterministic exp2 (moved out of the oscillator into shared/audiodsp_exp2.h) and sin/(sin + cos) series rather than libm's tanf, so every target renders them alike; a held input is cached. An input of zero, or none, is the configured filter bit for bit, and the unmodulated loop is unchanged in output and speed. All three bindings (MicroPython, the CircuitPython spike and CPython) get the same surface. Tests: zero input matches no input exactly, a constant input matches set() within 1 LSB, and FFT shows the 750 Hz block-rate spur of a 64-frame block 190 dB under the carrier with the input against 18 dB under when stepped with set(). ladder_probe gains swept, held and stereo-input fixtures for verify_dsp.
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.
Ladder.set(cutoff_hz=...)moves the cutoff once a block. That's fine for an envelope or a knob, but sweep the cutoff with an audio-rate signal that way and you get a staircase, which you hear as a buzz at the block rate.This adds a
cutoffaudio input, following the wayaudiooscillator.Oscillatortakes its pitch:The cutoff becomes
cutoff_hz * 2 ** (v * cutoff_octaves)for an input atvof full scale. Only the first channel is read, and the result is clamped to the same bandcutoff_hzis.cutoff_octavesdefaults to 10, which is the oscillator's pitch scale. An input of zero, an input that has run dry, or no input at all gives you the configured filter bit for bit.The per-sample coefficients use a deterministic
exp2(moved out of the oscillator intoshared/audiodsp_exp2.h) and a sin/(sin + cos) series instead of libm'stanf, so every target renders them the same. A held input value is cached. The unmodulated path's output is unchanged: the ladder probe gives identical bytes before and after. Its speed is unchanged too, at 23.7-24.1 µs per stereo 256-frame block both ways. The modulated path costs about 15% more.The MicroPython binding, the CircuitPython spike and CPython all get the same surface:
modulate(), thecutoffkeyword andcutoff_octaves.Tests, in
tests/test_cpython_audioladder_cutoff.py:set()at the cutoff it names, within 1 LSB.set(), the control, it's 18 dB under.modulate()refuses names and values that aren't inputs.ladder_probe.pygains swept, held and stereo-input fixtures, soverify_dspcompares the new path across interpreters. Locally, MicroPython v1.29.0 unix and CPython agree on all 58 comparisons.