Repository navigation
audiosampler: keep the filter and LFO state out of the voice table - #355
Merged
Merged
Conversation
…of the voice table
…d LFOs cost in memory
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.
Since the sampler gained its low-pass filter, modulation envelope and LFOs (#337), every voice slot carried their state whether a note used them or not. A voice grew from 112 to 256 bytes, and a sampler allocates two slots per voice (the voice and the tail that fades its last note) in one block.
Sampler(voice_count=32)went from 16 KB to 27 KB, and its voice table from 7 KB to 16 KB.That broke SoundFont instruments on a crowded heap. In an 8 MB MicroPython heap, once a General MIDI bank (FluidR3_GM.sf2) has loaded, the 16 KB table no longer fits in any free block. The instrument raises
MemoryError: memory allocation failed, allocating 16384 bytes, and its notes are silent.What changes
The filter, envelope and LFO state moves out of the voice into its own record,
audiodsp_sampler_mod_t. A voice gets one pair of records (its own and its tail's, 272 bytes on a 64-bit build) the first time it needs them: aplay()that sets any of those arguments away from its default, acutoffset on the voice, or a nonzerovibrato_depth. The voice keeps the pair after that. A voice that never asks carries none, and a sampler whose voices all ask allocates 32 small pieces rather than one large one.The voice's byte-sized fields (
stage,loop_mode,looping,mods) now share the padding beforeposition. A plain voice is 112 bytes on a 32-bit board, the same as before #337, and 120 on a 64-bit desktop. A size check inaudiodsp_sampler.cfails the build if the voice grows past that.Measured on the unix port (64-bit):
Sampler(voice_count=32), stereoSampler(voice_count=32), monoSampler(voice_count=128), stereoRenders don't change
A record attached to a voice that is already sounding starts as
play()would have left it for a note that asked for none of this. The note goes on rendering exactly as it would have.tests/parity/sampler_probe.pygives byte-identical output before and after the change on CPython and on the unix MicroPython build, and the two agree. Itsclosedsection turns a filter on live, on a voice that had played plainly.vibrato_depth, and runs mono and stereo. Its output is byte-identical before and after on both runtimes. Two planted faults each changed its output: not copying the record to the tail, and a wrong default in a new record.tests/test_cpython_audiosampler.py,tests/test_cpython_audiosampler_modulation.pyandtests/test_binding_parity.pypass.-Wall -Werroragainst CircuitPython 11's unix headers. No CircuitPython build was run.With this change alone, the mpvst unix engine at an 8 MB heap loads FluidR3_GM.sf2 and plays: SoundFont note 60 peaks at 11094 and SoundFont Drums note 36 at 13222. Forty notes held at once leave 2 MB free.