"audio plugin host" MIDI Output

As promised, here are the instructions originally posted by oxxyyd on how to modify the plugin host to handle midi out, updated to JUCE version 5.4.4:

1 First you need to add some code to actually create the midi out connection so to say
In InternalPlugins.h you change

PluginDescription audioInDesc, audioOutDesc, midiInDesc;

to

PluginDescription audioInDesc, audioOutDesc, midiInDesc, midiOutDesc;

in InternalPlugins.cpp you add in the relevant places:

{
    AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode);
    p.fillInPluginDescription (midiOutDesc);
}

and

if (name == midiOutDesc.name)  return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor> (AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode);

and

midiOutDesc,

to the line that says

results.add (audioInDesc, audioOutDesc, midiInDesc,

(hint: search for midiInDesc and add the corresponding code for midiOutDesc).

In PluginGraph.cpp you add

addPlugin (internalFormat.midiOutDesc,  { 0.25, 0.9 });

NOTE: at this point your code should be in a state that will compile and the MIDI Output node should appear in the plugin graph (but it doesn’t work yet).

2 Some more updates
Add to the collection of privates of GraphDocumentComponent in GraphEditorPanel.h

MidiOutput *midiOut;

Make GraphDocumentComponent a ChangeListener i.e in GraphEditorPanel.h it should look like

class GraphDocumentComponent  : public Component,
                                public DragAndDropTarget,
                                public DragAndDropContainer,
                                public ChangeListener

and add the following line to the public part of the class declaration:

void changeListenerCallback(ChangeBroadcaster* source) override;

3 Add to GraphEditorPanel.cpp

void GraphDocumentComponent::changeListenerCallback(ChangeBroadcaster* source) {
  if (source == &deviceManager) {
    midiOut = deviceManager.getDefaultMidiOutput();
    
    if (midiOut) {
        midiOut->startBackgroundThread();
    }
    
    graphPlayer.setMidiOutput(midiOut);
  }
}

4 Now add the line

changeListenerCallback(&deviceManager);

somewhere in the constructor of GraphDocumentComponent (GraphEditorPanel.cpp).

and put following at the to beginning of ~GraphDocumentComponent

if (midiOut)
    midiOut->stopBackgroundThread();

and add to the method init(), after the line that begins with “keyState.addListener”, the line

deviceManager.addChangeListener(this);

5 Now we’ve come to the part where midi output is actually taken from the AudioProcessorGraph.
Modify the code below by adding the 7 lines after

// modification for MIDI output:

In juce_AudioProcessorPlayer.cpp:

        if (! processor->isSuspended())
        {
            if (processor->isUsingDoublePrecision())
            {
                conversionBuffer.makeCopyOf (buffer, true);
                processor->processBlock (conversionBuffer, incomingMidi);
                buffer.makeCopyOf (conversionBuffer, true);
            }
            else
            {
                processor->processBlock (buffer, incomingMidi);
            }

            // modification for MIDI output:
            AudioProcessorGraph* processorgraph=dynamic_cast<AudioProcessorGraph*>(processor);
            if (processorgraph) {
                MidiBuffer* midiBuffer (processorgraph->getCurrentMidiOutputBuffer());
                if (midiBuffer && !midiBuffer->isEmpty() && midiOutput) {
                    midiOutput->sendBlockOfMessages(*midiBuffer, Time::getMillisecondCounter(), getCurrentProcessor()->getSampleRate());
                }
            }

            return;
        }

6 And last add following method declaration to the publics of juce_AudioProcessorGraph.h

// modification for MIDI output:
MidiBuffer* getCurrentMidiOutputBuffer();

and the corresponding implementation in juce_AudioProcessorGraph.cpp:

// modification for MIDI output:
MidiBuffer* AudioProcessorGraph::getCurrentMidiOutputBuffer() {
  if (isUsingDoublePrecision()) {
    if (renderSequenceDouble != nullptr) {
        return &renderSequenceDouble->currentMidiOutputBuffer;
    } else {
        return nullptr;
    }
} else {
    if (renderSequenceFloat != nullptr) {
        return &renderSequenceFloat->currentMidiOutputBuffer;
    } else {
        return nullptr;
    }
  }
}

After building and starting the plugin host you should now have a MIDI output. Draw a connection between midi input and midi output. If you have selected a midiout device in the audiosettings (e.g Microsft GS Wavetable Synth in windows) you should now here a piano when playing on the midikeyboard (or see the MIDI Out LED blink if you selected a physical MIDI interface).

Note from original instructions (which I would not necessarily recommend, especially if it involves copying and pasting a lot of JUCE library code, which then has to be maintained to keep track with the changes that are being made to JUCE), added here for the sake of completeness:

If you’re more into copy & paste programming you could instead of modifying the two [now three] juce files create a “midiout processor” by subclassing juce_AudioProcessor. But I leave that as an excersise for the reader…

2 Likes