Why are my sample offsets not working for MIDIEvents in processBlock()?

I wanted to understand how sample offsets work when adding MIDI Events to the passed in MIDI Buffer in processBlock().

So I modified the ArpeggiatorDemo to send out a MIDI Controller Event (in the AU version, which does send MIDI Controllers).

I wrote this simple code that should result in a Midi Controller every ms.

void Arpeggiator::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi)
{
    auto numSamples = buffer.getNumSamples();
    MidiMessage msg;
    midi.clear();
    
        // time starts at 0 and is incremented by the numSamples processed each block
    if (*onOffParameter)
    {
        
        ArpeggiatorEditor *arped = dynamic_cast<ArpeggiatorEditor*>(getActiveEditor());
        if (arped)
        {
            String post = String::formatted("  processBlock: rate %f, numSamples %d", rate, numSamples);
            arped->postStringToList(post, true);
        }

        // will carry over remainders to the next call
        static int curNumSamples = 0;
        
        int samplesPerMs = (int) (rate / 1000);
        
        // increment the carried over remainder by the new numSamples (block size)
        curNumSamples += numSamples;

        // modulo the curNumSamples by the buffer size to get the first offset
        // will start at 0 in first block
        int offset = curNumSamples % numSamples;

        while (curNumSamples >= samplesPerMs)
        {
            static int ccVal = 0;

            if (arped)
            {
                String post = String::formatted("before: ccVal %3d, curSamples %3d, offset %3d", ccVal, curNumSamples, offset);
                arped->postStringToList(post, true);
            }

            offset = jmin(offset, numSamples - 1);
            midi.addEvent (MidiMessage::controllerEvent(1, 10, ccVal), offset);
            ccVal = (ccVal + 1) % 128;
                       
            offset += samplesPerMs;
            curNumSamples -= samplesPerMs;
        }
    }
}

The sample rate is 48000, so 1 ms = 48 samples. The buffer size is 512 for this example. Therefore, one buffer equals approx. 10.666 ms.

Therefore, if I add essentially 10.666 events per call to processBlock, staggering the offsets by 48, and carrying over the remainders to the next call, shouldn’t I get a CC val every ms at the output?

Here is a printout of my debug window showing the loop in execution. As you can see, for each call to processBlock(), there are 10 or 11 events added to the MidiBuffer, with the offsets increasing by 48 for each new event.

However, when I monitor the output (with a program named MidiMonitor on the Mac), the events are all clumped up on the same ms, basically a clump every 10.666 ms (when processBlock executes). It’s like the offsets are doing nothing.

This is with the JUCE AudioPluginHost. Am I missing something?

That’s a limitation of the AudioProcessorPlayer that the AudioPluginHost uses internally.

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

                if (midiOutput != nullptr)
                    midiOutput->sendBlockOfMessagesNow (incomingMidi);

                return;
            }

It just grabs the block of midi messages and dumps them to the MidiOutput discarding any timing information.

If you want timing you’ll probably need to use a real host. Or use a midi monitor vst plugin.

1 Like

Thank you! However, wouldn’t changing that code to:

if (midiOutput != nullptr)
    midiOutput->sendBlockOfMessages(incomingMidi, 
    Time::getMillisecondCounterHiRes(), 
    getCurrentProcessor()->getSampleRate());

…fix that problem then, for the AudioPluginHost? It doesn’t…

Coming back to this issue. It’s still a problem. The AudioPluginHost throws away any sample offsets from outgoing MIDI data.

This is with JUCE 5.4.7.

My previous message was wrong. It DOES INDEED FIX IT to simply change sendBlockOfMessagesNow() to sendBlockOfMessages().

juce_AudioProcessorPlayer.cpp

    AudioBuffer<float> buffer (channels, totalNumChans, numSamples);

    {
        const ScopedLock sl (lock);

        if (processor != nullptr)
        {
            const ScopedLock sl2 (processor->getCallbackLock());

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

                if (midiOutput != nullptr)
                     //midiOutput->sendBlockOfMessagesNow (incomingMidi); change to:
                   midiOutput->sendBlockOfMessages(incomingMidi,
                                                    Time::getMillisecondCounterHiRes(),
                                                    getCurrentProcessor()->getSampleRate());

                return;
            }
        }
    }

I can see no reason why this was not done in the first place!

Here you can see the results of the same experiment at the top, with this one-line fix. The CC messages come out of the AudioPluginHost with 1 ms between each of them - the offsets are working.

Can the JUCE Team please fix this for JUCE 6 at least?