Am 08.04.22 um 00:38 schrieb Will Godfrey:
In almost all cases we want to send any changes to everyone who could have
created them or would like to know. Currently that's the CLI and the GUI. If
these don't want to know, then they can just ignore the message, and the CLI
will want to know that a change to padsynth has occurred. If we ever support
either a network and/or OSC these too would want to know. This is where
resolveReplies comes in.

This is not what the code base tells me. InterChange::resolveReplies() does not
produce the feedback. It only handles some midiLearn() stuff and produces and
feeds response text for CLI commands into the global Log.
And it sets the finishedCLI flag by side-effect.

As far as I can see, resolveReplies() is not responsible to pass on the
reactions to the GUI. It does not even have an outlet towards the GUI.
Rather, the outlet towards the GUI is within the processing of the mediate()
function (InterChange.cpp line 1866), somewhere right in the middle of the
processing sequence, because mediate() uses a do-while-loop.

This is of course a bit special as the result has been split in
two 'action started' and 'action completed'. Everywhere else just has
'action completed'.

It's actually more than that as we don't want to send multiple 'action
started' if one hasn't yet had time to complete so before sending the
response we need to check if one is already in progress.

Or is there something I've missed?


It's actually much more shades of Gray.
We have several Parts and each has a Kit with multiple PADSynth items.
Each of those produces its own Status updates. All of this runs concurrently.
For example you can load several instruments into several parts and while
one instrument loads you can trigger the next one or manipulate the UI, while
further commands can be fed from a CLI script (or even from MIDI), and all of
this can happen in any order. Moreover, it is not just start/stop, we have
re-started builds, and we have wavetables which are computed but not yet
picked up by the Synth, and we have extended cross fade times.

Interchange::mediate() is the command dispatcher of Yoshimi -- but honestly,
I am not able to get a clear grasp of the logic of the "mediate()" function.
This function is way to complicated for my poor brain. It has conditional
compiled code, it has hidden side effects in every clause (the "returns"
function actually dispatches to the low priority thread, under certain
condidtions). It uses an uninitialised Command block, which is passed as
pointer to several functions in sequence, which gradually fill out fields
in various usage patterns, but also only under some conditions. The mediate()
function has Read and Write functionality lumped into each other. And it uses
three object fields of the Interchange object (comeFrom, syncWrite and setUndo).
Note these would be messed up by concurrent calls. And each SynthEngine instance
has its own Interchange object, and we can have several Synths, which are passed
on by pointer almost everywhere into the code base. I have now looked almost two
hours at that mediate() and at sendDirect() and sendNormal() functions and I am
just not "genius" enough to come to a reliable conclusion what happens when
under which condition, even more so when events are coming in concurrently.

Please note that the status updates will come from several threads;
Some commands will be dispatched directly, some via the background thread.
The status updates from finished Wavetable builds will come from a pool of
background threads. And the signal for the crossfade start and stop and
the "clean" state will come from the Synth thread.
And all of that any time in any order.
That's poisonous.

On the other hand, when we drop those concurrent invocations directly
into the "toGUI()" queue, I can understand what will happen. This code
is a non-blocking ringbuffer, it uses state-of-the-art memory synchronisation
with Atomics, and thus will ensure that concurrent calls will be serialised
without data corruption and the single consumer will get them cleanly, albeit
with no predictable order (but that is fine here). On the receiver side, there
is a single consumer in the UI thread, and we can order the states to be
displayed and use the maximum, and this way the UI will always reflect
the "hottest" state.

Will, please understand me right: I want to deliver good work and
don't mess up something in subtle ways, which are hard to detect and fix.
Since this "padthread" stuff works concurrently, we're operating on a
higher "danger level", and various assumptions from the good old single
threaded world do not hold any more.

-- Hermann



_______________________________________________
Yoshimi-devel mailing list
Yoshimi-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/yoshimi-devel

Reply via email to