-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lock-free processing #141
Comments
Thanks a lot for opening this @raphlinus, performing allocations/deallocations on a separate thread is something I've had in mind for a while now but have not yet had the chance to explore. If you'd find it beneficial to experiment and improve upon dsp-chain I'd be more than happy to discuss and review changes.
I think the kind of changes you are discussing will be beneficial enough to warrant how far reaching they will be! I'd be happy to see them implemented here if you are still interested. Whatever you decide, I'd love to be involved.
I wonder if the Rough Design IdeasI can picture the graph constructor API looking something like this: let (api, audio) = dsp::Graph::new(); where the The user could call methods on the enum Message<N, F> {
AddNode(NodeIndex, N),
AddConnection {
index: EdgeIndex,
buffer: Vec<F>,
source: NodeIndex,
destination: NodeIndex,
},
RemoveNode(NodeIndex),
// etc
} where When Questions that come to mind
Thanks again for the issue, I'll keep thinking on this. |
Thanks for the response. I've started prototyping on my end, but still not sure whether it's better to push that to release or work here. The problem with Other than that, I think what you're saying is reasonable, especially having garbage collection done by the api object. There are some details I would do differently, for example I would atomically update a predecessor list rather than adding and removing edges one by one. I also have buffers on nodes rather than edges (and I haven't gone too far into the api yet, but yes, cloning is important. I'm kinda thinking along the lines of a "factory" which basically replicates a source graph into the audio graph; the source graph can be in a more friendly format. The factory would also take care of maintaining invariants, such as building the graph in topological sort order so that no node has an edge from a nonexistent node. Your
(Note: in playing with this, it's not quite so easy, the compiler complains about Sized bounds, but I think it can be made to work) My original thought was just sending an enum of popular data types to an |
I released my prototype: https://github.com/google/synthesizer-io. I'm not sure how much I'm going to continue developing it, whether it's primarily useful as a source of ideas or as a real codebase. It does address a lot of the issues I outlined; it's fully lock-free, allows combinations other than sum, and has some other interesting properties. |
Excellent, thanks for sharing! I'm looking forward to having a closer look. |
This adds a new crate for working with dynamic audio graphs. From the new docs: > `dasp_graph` is targeted towards users who require an efficient yet > flexible and dynamically configurable audio graph. Use cases might > include virtual mixers, digital audio workstations, game audio systems, > virtual modular synthesizers and related usecases. This work has been a long-time coming and is the result of many discussions in the rust audio community and many lessons learned over the last few years of working with rust and audio. In particular: - the development of and reflection on [dsp-chain](https://crates.io/crates/dsp-chain) and its shortcomings. - The (reasonable) limitations of [dasp_signal](https://crates.io/crates/dasp_signal) when dynamically configuring graphs. - Discussion on the design of audio graphs with @raphlinus at RustAudio/dsp-chain#141. - The development of the [spatial audio server](https://github.com/museumsvictoria/spatial_audio_server). - A recent email discussion with Sami Perttu on DASP and audio graphs. `dasp_graph` is of course not a one-size-fits-all solution. Instead, it is designed specifically to work well alongside (and fill a gap within) the rest of the `dasp` crate ecosystem. Please refer to the "Comparing `dasp_signal`" section of the `dasp_graph` root documentation for a more detailed overview of the design choices between the two, what applications each are best suited toward and how the two best interoperate together. A small suite of node implementations are provided out of the box including a `Delay`, `Sum`, `Pass`, `GraphNode` and `BoxedNode`, all of which can be enabled/disabled via their associated features. Following this, I have some ideas for adding an optional `sync` module to the crate, aimed at controlling and monitoring a dasp graph and it's nodes from a separate thread (i.e. for convenient use alongside a GUI) in a non-dynamically-allocating, non-blocking manner. The work so far has been performed with these plans in mind. The ideas are for the most part based on the discussion at RustAudio/dsp-chain#141. Also, `no_std` support for `dasp_graph` is currently blocked on petgraph support for `no_std`. A PR is open for adding `no_std` support at petgraph/petgraph#238. In the meantime, the `std` feature must be enabled to use the new `dasp::graph` module. This is also noted in the updated docs. For more information about the crate and inner workings feel free to read through the new `dasp_graph` docs. I'm yet to add examples, but hopefully the added tests can give a good idea of how to use the crate in the meantime.
For real, glitch-free low latency audio performance, the audio processing path needs to be lock-free. That, in turn, implies no allocations or deallocations. The music-synthesizer-for-android project (which was a testbed for high performance audio on Android) dealt with this by doing fixed-size ring buffers, and preallocating the state for all DSP. That meet the lock-free needs, but is limiting.
Thinking about this more and starting to prototype, I believe the answer is lock-free queues such as Treiber stacks (though a number of other lock-free queue data structures would also work). That way, a main thread allocates nodes at will, sends them over a lock-free queue to the realtime audio thread, which at that point basically has
&mut
access to the nodes. For garbage collection, the realtime thread can send them back over a return channel. Control and status can be multiplexed over the same channels (which has the additional benefit of ordering guarantees between these and graph mutation).This is something of an exploratory issue. Is this a good repo to develop such ideas? The architectural changes are likely to be far-reaching. It may be less work to just develop a new graph runner and modules from scratch than retrofitting the RustAudio stack. But I'd like to at least sound out working with existing projects.
The text was updated successfully, but these errors were encountered: