I'm trying to understand the rationale behind the messaging code in MidiDemo.cpp
<!--break-->
I'm looking through MidiDemo.cpp
I can see that it handles callbacks from the active midi device as well as the on-screen midi keyboard:
class MidiDemo : public Component, private ComboBox::Listener, private MidiInputCallback, private MidiKeyboardStateListener, private AsyncUpdater { void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override // <-- MIDI device callback {...} // Note ON/OFF are on-screen keyboard callbacks void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override {...} void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) override {...}
all three of these call:
postMessageToList (m);
I can see that these callbacks may be occurring on different threads, and we would like to handle them on the main thread.
But I don't quite understand the implementation. It seems to involve two stages of sending a message.
Firstly,
void postMessageToList (const MidiMessage& message) { // this only gets reached from the above three functions, i.e. if a midi signal was received // or if we got a note ON/OFF from the MIDI keyboard component // // remember, we are in whatever thread initiated this midi signal // // we want to respond on the main thread, hence we post a message // which will get picked up in the message thread // // CallbackMessage contains the machinery for this (new IncomingMessageCallback (this, message))->post(); } // This is used to dispach an incoming message to the message thread struct IncomingMessageCallback : public CallbackMessage // <-- contains post() { IncomingMessageCallback (MidiDemo* d, const MidiMessage& m) : demo (d), message (m) {} // this will fire when the message gets processed (on the message thread) void messageCallback() override { // so this code gets executed on the message thread if (demo != nullptr) demo->addMessageToList (message); } Component::SafePointer<MidiDemo> demo; MidiMessage message; };
and now finally we handle the message on the message-thread:
void addMessageToList (const MidiMessage& message) { midiMessageList.add (message); triggerAsyncUpdate(); }
I can see that the intention here is to allow a bunch of messages to build up before AsyncUpdater sends out handleAsyncUpdate() from the main thread
// AsyncUpdater calls this from the main thread void handleAsyncUpdate() override { messageListBox.updateContent(); messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1); messageListBox.repaint(); }
but given that this is happening, was there really any need to go through the first messaging process?
Do we have three threads here, or two? It looks as though firstly there is the thread that the midi signal came in on,ext the messaging thread, and finally the main thread.
Wouldn't it make sense to just cut to the chase and just call addMessageToList(...) from each of the original three midi-handling callbacks?
Finally, it looks as though if an event from the on-screen keyboard occurs while we processing from the external midi source, it is just going to get lost:
void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override { if (! isAddingFromMidiInput) { MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); postMessageToList (m); } }
Wouldn't a better approach be to spin on isAddingFromMidiInput?
It seems the problem is that two separate threads might simultaneously call back, trying to add an item to the midi-events list. Is this why the first level of messaging happens? To merge this into a single message stream...
Seeing as midiMessageList.add (...) requires very little processing, wouldn't it be more efficient to use a spinlock?
π