while working on my room impulse response measurement software raumklang I came up with this code for recording new measurements:
Unfortunately, with all the things like async, jacks real-time audio thread and the non-realtime notification thread together, I find it really hard to organize my code. I would really appreciate any help on this.
There may not be much you can do about the shape of the code; you are already using channels as the synchronization primitives between threads, and the syntactic boilerplate from these threads/async tasks is not going away.
In general terms, the file you linked is above my threshold for "this needs to be split into multiple modules." And you even linked directly to an inline module. Moving this code to another file is a very easy way to start paying down the organizational costs.
The second observation I have is that half of this file is a GUI. It's iced, which is very modular, but you have what looks like everything piled into a single view with deep nesting. If you cannot break these nests into their own widgets, it might be worth turning the match statement into a dispatcher that calls functions. Even that much reintroduces modularity and the functions can be reorganized however you like.
Sorry I can't be more specific. I haven't seen anything in that code which would suggest the organizational problems are inherently related to threading or audio. A few broadly applicable suggestions might good enough to get you started, though.
Thank you very much for your help. I really appreciate it, because I know it's a lot of effort to look into others code - especially if it's such a mess like my current problem case.
That helps a lot, because I thought I did something totally wrong there. That also was the main reason I asked here before trying to refactor things.
Totally agree. I knew that the code has to be separated from the UI right from the beginning, but at the time I started I had no clue what I would need and how to organize it, so I at least put it into the sub-module to ease the follow up refactoring.
That is good to know and it really helped more than you might think.
It's not completely obvious to me what semantics you want from your channel, but when treating apart some other audio libraries recently I found one using
Which is a very efficient "use the latest version of this value" API.
Alternatively, you might just want to chunk your processing and use try_iter to read from your channels.
I don't know which channels you mean, I'm using a lot of them for different purposes. But, because you mentioned the triple_buffer together with stds sync::mpsc channel, be aware, that a mpsc channel might need to allocate and thus is not save to use in the real-time audio thread.
But allocation is paid for by the sender, which is normally fine; whatever channel library you're using should have an equivalent to pull only until exhausted without blocking. You want the iterator mostly so you can slap a take(limit) on there to avoid starving other sources. I'll mention that normally I see an explicit ring buffer library instead of a channel for sample data, which has a fairly different API, you'd just need to make sure you're avoiding a blocking call.
Be aware this should only be done if you expect there to almost always be data from someone to process, otherwise you're essentially going to spin in a loop and possibly starve other threads: you can mitigate that with a call to yield_now in std::thread - Rust
Unfortunately you can't really do much better if you have multiple independent synchronous sources and you want to block until there's work from any of them. The fix there is most often to move all your producers to share a single mpsc of "work items", or move to async.