I’m working on an open source midi sequencer which runs on a rapsberry pi and uses Ableton’s Push2 as UI. Source code repository (with rather hacky code so far) is here: GitHub - ffont/shepherd
After some initial development and getting some basic features working, I’m now refactoring the code to base the app on a state stored in a ValueTree, following the techniques explained by @dave96 in his ADC 2017 talk (David Rowland - Using JUCE value trees and modern C++ to build large scale applications (ADC'17) - YouTube). The concept of storing the state in a ValueTree sounds great to me and also is interesting because then I can sync the ValueTree with an external application that deals with the actual UI*. I already did some work for this refactoring using
juce::CachedValue and the
drow::ValueTreeObjectList as explained in the talk.
What I understand from Dave’s talk (and from reading in many posts here) is that I should not read/write directly from the ValueTree in the audio thread. I’m trying to learn all these concepts with thread safety and real-time safety issues, but I’m no expert C++ dev so this is becoming a bit confusing. Here are my main questions which I hope someone can help me clarify:
When in the
getNextAudioBlock(audio thread), how should I go about reading the state? My state contains a list of tracks which has a list of MIDI clips with MIDI notes. Should I be adding some “atomic” members in my “proxy”
Clipclasses so when properties are updated in the value tree (eg, clip length is updated using the
juce::CachedValue<double> lengthproperty) these atomic members are also updated and are later read from the audio thread?
If tracks/clips/notes are added or removed while the audio thread is working, this could cause conflicts. How can I prevent the state from being changed at all while the audio thread is doing something relevant? The
Noteobjects are automatically synched with the ValueTree using
drow::ValueTreeObjectList, therefore I guess the best solution would be to simply delay any writing to the ValueTree happening in the message thread to a moment where the audio thread is not reading it, but I don’t know how to achieve that in a nice way…
How can I update the state from the
getNextAudioBlockfunction (eg to update the current playhead position of a clip)? Maybe the answer is that I should design my application so I don’t need to write to the state form there? I guess I could do something like that by simply removing some things from the ValueTree state (so my audio thread does not depend on them) and directly use object members. Then, if I need this data to be synchronized with the UI (eg to display clip playhead position) and I do my synchronization with the main ValueTree, I guess I could still use a timer on the message thread to add copies of these clip properties to the ValueTree (ie add playhead position to the value tree) so these get reflected in the UI.
As you can see I have some ideas about how to proceed, but because I’m not 100% sure I understand all the concepts I’d like to get some feedback before continuing with this. I’m not planning to add more threads in my app besides the message thread and the audio thread, so hopefully the solution is not too complex?
Thanks a lot in advance for your help!
*So far I’m doing something like that in a very hacky way, but I expect to do it much better using ValueTree and change listeners.