Repository navigation
soundfont: play a region's filter, modulation envelope and LFOs, with the default modulators - #243
Merged
Merged
Conversation
… the default modulators The reader now gives a region the rest of what a bank says about a note: its low-pass cutoff and resonance, its modulation envelope and the depths it drives pitch and cutoff by, and both LFOs with their rates, delays and depths. The instrument hands them to the sampler's voice. Over them, the SoundFont 2.04 default modulators: velocity darkens a filtered region by up to two octaves, the mod wheel and channel pressure each add up to 50 cents of vibrato, and Control Change 7 and 11 set the level by the square of their value. Volume starts at General MIDI's 100, so a channel that never sends it plays about 4 dB under where it did. A region that doesn't use its filter stays unfiltered at every velocity. A bank's own modulators are still not applied; the docstring says so. tests/test_cpython_soundfont_modulation.py measures each on a bank the suite writes, with planted faults that fail them.
Both handed a mono dry voice 1 - 2^-15 so CircuitPython's float mixer, which scaled by level / 32767, would pass it through exactly. CircuitPython 11.0.0-alpha.2 scales by (x * level) >> 15, and audiodsp follows it from 46f78dc: 1.0 is now the exact wire and 1 - 2^-15 is one LSB cold on every positive sample (MultiTapDelay's dry read 19999 for 20000, AirSpace's T1 dry was off on up to 11952 samples). Measured: both effects' test files pass at audiodsp 46f78dc and 0c0824e with level 1.0.
mixer_scale and pan_muls reproduced the float kernel (x * level / 32767, truncated; pan scaled from 32767). From audiodsp 46f78dc the Mixer is CircuitPython 11.0.0-alpha.2's: (x * level) >> 15, with pan from 32768. The model now follows it; T5 passes at 46f78dc and 0c0824e and fails at the commit before, so it still tells the two apart.
The digests anchor the renders from before _replay was taken out. The class's code is unchanged; the old digests still reproduce on audiodsp 1877bb9, the commit before the integer mixer (46f78dc), and the new ones, the same at 46f78dc and 0c0824e, keep the same scenarios equal to each other as the old ones did.
The sampler's filter, modulation envelope and LFOs merged as 0c0824e. The pin note now says what renders differently beneath the components: the 11.0.0-alpha.2 integer mixer from 46f78dc.
bdbarnett
force-pushed
the
sampler/phase5
branch
from
October 29, 2026 21:27
145e99b to
bcb2f19
Compare
CircuitPython 11's integer mixer clamps a voice's panning to 32767/32768, so the far channel of a hard-panned voice at level 1 keeps a scale of 1 and gets x >> 15: -1 on every negative sample. At Width 1 that put -1 in the left wherever the delayed left was negative, and -1 in the right wherever the source's right was negative, so the copy was no longer the right channel exactly Delay late. No pan or level gives an exact near channel and a silent far one, so the class now cancels the -1 instead. A branch multiplies the source by 1 and then by -32768 (audiomath.Multiply), which is +1 on each channel whose sample is negative and 0 otherwise, exactly. The line is fed from a small mixer that adds that +1 to the source panned hard right, so its left is an exact 0 and the copy voice has nothing to leave there; a third voice adds it to the right while the lead sits at unity hard left. Widths between 0 and 1 render as before. The CopyFromMid plant now centres the line's feed as well, so the line still folds both channels into the copy.
The integer mixer rounds the -55 dBFS probe's few-LSB dry voice where the float mixer truncated it toward zero, so the level spread fell at every patch. Patch 3 (HF-only) went from 2.262 dB to 0.583 dB and meets D1's 1 dB; patch 4 went from 2.479 dB to 1.321 dB and still misses it. The test now records the miss at patch 4 only, and the module's figures are the integer mixer's: 0.213 dB over -6..-40 dBFS, 1.176 dB at worst with the -55 dBFS take.
The output pole's float32 state stalls short of zero when it holds a large constant: at 44.1 kHz shipped patch 6's 5666 LSB offset left it resting at -0.7 LSB, which the integer Level stage floors to a standing -1 LSB in silence, and a burst of signal leaves it elsewhere in that band. A second Waveshaper at x1 on a two-point straight line, at a post_gain of 32768/32767, plays x + bias back exactly for every int16 x. It subtracts the offset before the pole, so the pole holds a few LSB. Silence after a noise or sine burst is now 0 at every shipped patch at 48, 44.1, 22.05 and 96 kHz. Patch 6 renders within 4 LSB of before and no other patch moves; patch 7 still reaches the rail at 0 dBFS. Two Overdrive E10 rows in the lifecycle ledger go from 4 red cells to 2.
The Rat's two leg levels, (1+a)/total and b/total, sit below unity ahead of up to 67 dB of gain. The integer mixer floors x * level, which is half an LSB of DC on most samples: at Distortion max a 4 LSB input stood at -0.070 of full scale and a -40 dBFS one at -0.017, where the float mixer read 0 and -0.0003. The levels now sit on two x1 Waveshaper level nodes, on a two-point straight line at a post_gain of level * 32768/32767, which round to the nearest LSB. The mixer voices stay at an exact 1. DC at low input is back to the float mixer's figures, F2's contour and the reset residue pass again, and the clean class renders the same on both mixers to a fraction of an LSB of DC. The DS-1 graph's Distortion 0 now reads -70.8 dB THD where the float mixer's truncation gave -60.8, which puts it inside F4's row at Character 64 and up. F4's two guard sweeps leave those positions out, because the faults are planted in the Rat's _push_filter and the DS-1 graph never calls it. The leg levels now move at the block instead of at a zero crossing, so two Distortion P5 rows in the lifecycle ledger change: E4 goes from 12 red cells to 13, and E6 keeps 77 with a new digest.
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.
soundfontandsoundfont_drumsnow play the rest of what a bank says about a note. That is the region's low-pass filter, its modulation envelope (on pitch and cutoff), its vibrato LFO, and its modulation LFO (on pitch, cutoff and volume). It needs audiodsp#337, andAUDIODSP_PINmoves to that branch's head.The default modulators (SoundFont 2.04, section 8.4)
A bank's own modulators (
pmod/imod) are still not applied, and the docstring and README say so.Tests
The reader's tests read every new generator from a bank the suite writes.
tests/test_cpython_soundfont_modulation.pymeasures what the instrument renders from that bank:Two planted faults each failed tests:
Checks run
validate_apiand the effects library smoke are clean.