Send Midi CC directly or via ProcessBlock?

Hi,

I am a newbie @Juce so quick question:

Why is it best practice to send MIDI CC out via the processblock then just sending it direct?

My plugin will have 25-40 parameters that will be automatable.

This is what I use now:

midiOutput = MidiOutput::openDevice(0);
midiOutput->sendMessageNow(MidiMessage::controllerEvent(channel, cc, value));

but I have seen examples where you need to check if values have been changed in the processBlock but that seems to me much more cumbersome!

Any help much appreciated!

If you are doing a plugin that is supposed to output MIDI internally within a host into the following plugins in the processing chain, MidiOutput is not going to work. (It’s meant for outputting into MIDI hardware.)

2 Likes

thanks!

So what is the best way for me to line up a midi CC message and put that into the buffer for sending?

Do I need to check the parameter change in the processBlock or can I put it in the buffer somehow from outside the block (if that makes sense)

{
    buffer.clear();
    midiMessages.clear();
    MidiBuffer generatedMidi;
    int time;
    MidiMessage m;
    int8 ccTempVal;
    int8 myVal = (int8)CC00Val;

    if (myVal != ccTempVal)
    {
        m = MidiMessage::controllerEvent(2, 1, myVal);
        generatedMidi.addEvent(m, midiMessages.getLastEventTime());
    }
    else generatedMidi.clear();

    ccTempVal = myVal;
    midiMessages.swapWith (generatedMidi);
    
    
}

This is what I saw in another post and they recommended this:

So I can keep the code but move the ccTempVal out.
Is it maybe wise then, since I need to check 30-40 variables that I make an array and then put the checking loop and the sending in the processBlock?

I have adapted the code and moved the variables out, but the MidiBuffer does not get triggered…

{
    
    buffer.clear();
    //midiMessages.clear();
    MidiBuffer processedMidi;
    int time;
    MidiMessage m;
    for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);)
    {
        
        if (myVal != ccTempVal)
        {
            m = MidiMessage::controllerEvent(2, 1, myVal);
            processedMidi.addEvent(m, midiMessages.getLastEventTime());
            
        }
        else processedMidi.clear();
        ccTempVal = myVal;
        
//        processedMidi.addEvent (m, time);
    }
    midiMessages.swapWith (processedMidi);
}

this is my outer part in PluginEditor.cpp:

{
    processor.noteOnVel = midiVolume.getValue();
    std::cout << "midiVolume " << midiVolume.getValue() << std::endl;
    processor.myVal = midiVolume.getValue();
    midiMessages.addEvent (MidiMessage::noteOn (1, 22, vel), midiMessages.getLastEventTime());
}

Last part I put in as a test and it gives errors. So how do I add my event to the processBlock? I just have spent 5 hours Googling and I have found absolutely nothing…

@Xenakios

Can you maybe point me in the right direction?

How can I send a message to the processBlock from PluginEditor.cpp so it gets sent out via MIDI?

Any help is greatly appreciated!

It would help, if you posted, what errors you got :wink:

That’s the only way. The buffer is not available anywhere outside the processBlock()

1 Like

It’s quite involved because you would need to manipulate the MIDI buffer from the GUI thread. I don’t remember at the moment if I’ve done any, even passably good, code for that myself.

1 Like

You could in principle create a new MIDI buffer elsewhere and in “some manner” pass it into the audio thread to be used in the processBlock call and switch/merge it with the processBlock MIDI buffer…

1 Like

Hi @daniel and @Xenakios ,

I found a way how to do it thanks!

Anyway I have one last question:

I do need my plugin to select a MIDI out port because I want to use it in Ableton. I am building an external synth editor so I need to send to a physical MIDI out.
Right now Ableton thinks it is a synth and I can not route the MIDI coming out of the plugin because it thinks it is audio.

Do you have any idea if that is possible?

Everything works fine in GUI mode, but getting it to work in plugin mode is a real pain.

I checked it CTRLR and it is possible to be a plugin AND select the midi out, so why do people discourage that usage?

btw this worked:

{
    buffer.clear();
    midiMessages.clear();
    MidiBuffer generatedMidi;
    MidiMessage m;
    
    if (myVal != ccTempVal)
    {
        m = MidiMessage::controllerEvent(1, 1, myVal);
        generatedMidi.addEvent(m, midiMessages.getLastEventTime());
    }
    else if (myVal2 != ccTempVal2)
    {
        m = MidiMessage::controllerEvent(2, 1, myVal2);
        generatedMidi.addEvent(m, midiMessages.getLastEventTime());
    }
    else generatedMidi.clear();
    
    ccTempVal = myVal;
    ccTempVal2 = myVal2;
    midiMessages.swapWith (generatedMidi);
}
2 Likes

It might be “good enough” for CC-data in live mode, but the problem is, that the execution time of the processBlock() is independent of the playback time.

The event from the MidiInput may occur as soon as it arrives at the port in a dedicated thread. In a host it would get now a timestamp, that correlates to the playback time (including the sample within the block).
A plugin doesn’t have that information. And due to latency compensation of a following plugin, it might already process samples of the future, or in case of an offline bounce, there is everything happening virtually at the same time (a milli second becomes suddenly a nano second).

The next problem is, that your processBlock happens at discrete time points. Usually you would pick up a the midi events that happened at the beginning of a processBlock(), but since you don’t have the sample offset, all events can only happen at block starts, which can be up to 24 ms if running with 512 samples blocks (can be even longer).
If that was a constant latency, it might still sound not too bad, but since it is a jitter moving the notes into a non-musical grid, a performance would suffer quite severely.

1 Like

@daniel thanks!

thanks for that in depth information!

How can I adapt the processBlock so it will send out to a dedicated physical MIDI output?

        MidiOutput *midiOutput;
        midiOutput = MidiOutput::openDevice(0);
        midiOutput->sendMessageNow(MidiMessage::controllerEvent(1, 23, 23));

This works in Ableton when I put it in the processBlock but I don’t know if that is good practice.

Also when I quit the debug mode it tells me :

*** Leaked objects detected: 530 instance(s) of class MidiOutput
JUCE Assertion failure in juce_LeakedObjectDetector.h:90

Question is thus:

Where do I put this code

MidiOutput *midiOutput;
midiOutput = MidiOutput::openDevice(0);

In what part of the Audio Plugin template code?

First of all, in the latest JUCE (develop and master) openDevice() returns a unique_ptr, so you cannot assign it directly to a raw pointer (best to update your JUCE).

You are responsible for the destruction of the device you just opened (that’s the memory leaks you are warned about, apart from being best practice, if you run your plugin a while longer, there will be no memory left).

Second, you need to open your device outside the processBlock and keep it as member variable. Best to do it in prepareToPlay and check, if it is already open, since prepareToPlay can be called multiple times.

And last but not least, you have to check, if sendMidiMessageNow is realtime safe, i.e. it must not block, not use any OS resources and not allocate memory.
If it is not realtime safe, you need to implement a FIFO, where you can safely put midi messages, that are picked up from a non realtime task and be sent out.

Some snippets without having checked the above:

// member variable:
std::unique_ptr<MidiOutput> midiOutput;

// prepareToPlay:
if (midiOutput.get() == nullptr)
    midiOutput = MidiOutput::openDevice (MidiOutput::getDefaultDevice().identifier);

// with your old version in prepareToPlay rather:
if (midiOutput.get() == nullptr)
    midiOutput.reset (MidiOutput::openDevice (0));

// processBlock (please check, if that is realtime safe, no guarantee!):
if (midiOutput)
    midiOutput->sendMessageNow(MidiMessage::controllerEvent(1, 23, 23));
1 Like

Awesome!!! That worked like a charm!