Repository navigation
audiograph: a graph the pump pulls, changed through its own queue - #317
Merged
Merged
Conversation
…h its own queue A Graph can now be handed to the pump, an output's play() or a mixer voice, and it renders a block each time one is pulled, on whichever thread pulls it. set(), reset() and the new enable() go into a lock-free single-producer queue the renderer drains at the top of each block, so a change never lands inside a block; hold() and commit() make a batch land on one block together. A full queue is drained by the control side under the pump lock, so nothing is dropped. get() reads a shadow of the last value set; sounding() returns the sounding mask with the frame and the applied and queued counts it belongs to; stats() counts the queue. While the pump pulls a graph, buffer(), add(), table(), write() and run() raise RuntimeError, and deinit() stops the pump cleanly. Same API on MicroPython, CircuitPython and CPython. Checked by graph_source_probe.py (pulled renders equal run() to the sample, on all three interpreters, with planted faults drain and split) and a new graph case in pump_probe.py (one value per channel per block, held batches silent until commit, landing no later than the block after; planted faults split, late and unqueued), and the graph joins the pump's identity and heap-locked allocation gates.
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.
A compiled
audiograph.Graphcan now play the way any other sample does. Hand it to the pump, an output'splay()or a mixer voice, and it renders a block each time one is pulled. On a board where the pump has a core of its own, the whole patch renders there, and your code only changes it.What changes for you
set(),reset()and the newenable(mask)go into the graph's own lock-free queue. The renderer drains it at the top of each block, so a change never lands partway through a block.hold()andcommit()turn a batch into one change. A patch change no longer sounds its cutoff in one block and its resonance in the next.Graph(queue=...)sizes the queue; by default it holds the larger of 256 and four per node.get()returns the last valueset()stored.sounding()returns(sounding, frame, applied, pushed), so you can tell which block a mask came from and whether your last change has been heard.stats()counts the queue.buffer(),add(),table(),write()andrun()raiseRuntimeError.deinit()stops the pump cleanly. It works as a context manager too.run()behaves as before, except that it drains the queue first.All of this behaves the same on MicroPython, CircuitPython and CPython. See "Playing a graph from the pump" in
docs/audiograph.md.How it's checked
tests/parity/graph_source_probe.pyplays one script of changes three ways: throughrun(), pulled with batches held, and pulled through a 16-record queue that splits every batch. All three are identical to the sample.verify_graph.pycompares the pulled render across interpreters at 0 counts: CPython, MicroPython double and float, and the CircuitPython unix build. Planted faultsdrainandsplitmust fail.tests/pump/pump_probe.py graphhands a graph to the pump and changes it from the interpreter's thread. Every block must hold one value per channel from one batch. A held batch must stay silent untilcommit(), and a committed batch must sound no later than the block after the one in flight. With the pthread driver, 3000 batches are also committed while the pump pulls, and none may tear. Planted faultssplit,lateandunqueuedmust fail; CI runs them first.identityand heap-lockedallocgates, so a pull allocates nothing.deinit.check_portable.pyandcheck_lock_shape.pypass.Carrying these changes on
audiopump.Events, or taking the pump lock in everyset(), was rejected. Either way, every set would wait for the block being rendered, so a patch change of a hundred sets could stall the caller for hundreds of milliseconds.On the ESP32-P4 DEV-KIT
The Poly6 at two voices, compiled to one graph and played by the audio pump on the core the interpreter isn't on (48 kHz stereo, 128-frame blocks of 2.67 ms,
examples/hmi_instrument.py --pumpin vrack):Every run had no sink timeouts, no queue overflows or split batches, and 0 us of interpreter wait on the pump lock (
lock_stats()); the queue's high water was 261 records. The single slow pulls are absorbed by the I2S DMA queue (6 x 240 frames, about 30 ms). The pump task finished with 12428 bytes of its stack never used. The client's 41 changes were all acknowledged.