Best way to record MIDI input


#1

I need to store MIDI keyboard activity.

This is my current render callback:

    void getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill) override {
        bufferToFill.clearActiveBufferRegion();

        MidiBuffer incomingMidi;
        midiCollector.removeNextBlockOfMessages(incomingMidi, bufferToFill.numSamples);

        //midiFromKeyboard.addEvents(incomingMidi, 0/*startSample*/, bufferToFill.numSamples, 0/*sampleDeltaToAdd*/);
        // ^ ADD
        
        // add events from playing midi-file
        if (midifileIsLoaded && !isPaused) {
            int sampleDeltaToAdd = -samplesPlayed;
            incomingMidi.addEvents(*midiBuffer, samplesPlayed, bufferToFill.numSamples, sampleDeltaToAdd);

            samplesPlayed += bufferToFill.numSamples;
            if (samplesPlayed >= totalSamples) {
                samplesPlayed = 0;
                isPaused = true;
            }
        }

        // pass these messages to the keyboard state so that it can update the component
        // to show on-screen which keys are being pressed on the physical midi keyboard.    
        const bool injectIndirectEvents = true;  // add midi messages generated by clicking on the on-screen keyboard.
        keyboardState.processNextMidiBuffer(incomingMidi, 0 /*startSample*/, bufferToFill.numSamples, injectIndirectEvents);

        synth.renderNextBlock(*bufferToFill.buffer, incomingMidi, 0 /*startSample*/, bufferToFill.numSamples);
    }

I’m considering:

    MidiBuffer midiFromKeyboard;

… and adding the line marked ADD above.

Is this healthy?

Just asking because potentially this buffer could grow huge, say I am playing & recording for an hour. So adding data in a render callback is surely going to occasionally force a malloc and memcpy of a big chunk of memory.

Maybe a better architecture might be to hold a vector of outstanding mini-buffers (each render callback that detects MIDI keyboard activity would add a new element) and periodically empty this vector into the main buffer on a separate thread.

There is just going to be some awkwardness involved with 2 threads playing with this vector.

Any guidance / suggestion / debate gratefully received!

π


#2

Having another look at this. Looks like I need something like:

MidiBuffer* accumulator = new MidiBuffer();
Lock lock;

void getNextAudioBlock(...) {
    : 
    MidiBuffer incomingMidi;
    midiCollector.removeNextBlockOfMessages(incomingMidi, bufferToFill.numSamples);

    lock.acquire();
    accumulator.addEvents(incomingMidi, 0/*startSample*/, bufferToFill.numSamples, 0/*sampleDeltaToAdd*/);
    lock.release();
    : 
} 

MidiBuffer* flush {
    lock.acquire();
    MidiBuffer* ret = accumulator;
    accumulator = new MidiBuffer();
    lock.release();
    return ret;
}

~MySynth() {
    delete accumulator; // are we guaranteed not to be inside getNextAudioBlock?
}

And maybe start a separate thread in the constructor to periodically flush accumulator into some big MidiBuffer object.

And maybe for Lock I could use JUCE’s SpinLock…?

Does this look decent?

π