I have an idea for a midi effect plugin, but as in any case I need to start from the basics tasks.
So, the first tasks i put on myself is to do a simple midi-arp functionality like this:
1. plugin receives midi msg note-on (C3 - for example)
2. plugin produce (1/16 - for example) midi note (C3) on/off msgs and sends them to host constantly
while input midi msg note-off (C3) not received.
midi event : on off on off
plug's input : C . . . . C C - C
plug's output: C . C . C . C . C (1/16th notes)
I'm struggling to find the right structure to hold produced midi messages, so it will be use to use it in a processBlock() to forward them to host.
(currently I store produced midi messages in a MidiMessageSequence, but I have problem with keeping right offset)
For icoming midi messages I have in-house MidiReceiver class that handles all the necessary info (pressedKeysNumber, lastNoteNumer, lastVelocity, lastNoteOffSamplePos, etc...) that a plugin will need to interpret.
Here is my processBlock() and it looks/works not good:
void MyPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
const int blockSamples = buffer.getNumSamples();
// TODO: clean this...
double noteMiliseconds = (60000.0 / lastPosInfo.bpm) * rate;
double samplesPerMilisecond = getSampleRate();
samplesPerMilisecond /= 1000.0;
int noteSamples = noteMiliseconds * samplesPerMilisecond;
// Now pass any incoming midi messages to our keyboard state object, and let it
// add messages to the buffer if the user is clicking on the on-screen keys
keyboardState.processNextMidiBuffer (midiMessages, 0, blockSamples, true);
// track incoming midi messages...
midiReceiver.onMessagesReceived (midiMessages);
midiMessages.clear();
// This is the place where you'd normally do the guts of your plugin's
MidiMessage midiMsg (0xf0);
int midiMsgPos = 0.0;
// reset offset on the first pressed key...
if (midiReceiver.getFirstNoteOnSamplePos() >= 0)
{
offsetSamples = midiReceiver.getFirstNoteOnSamplePos();
midiReceiver.setFirstNoteOnSamplePos (-2); // set to undefined state
}
// clear midi sequence events after last key was released...
for (int i = 0; i < midiSequence.getNumEvents(); ++i)
{
midiMsg = midiSequence.getEventPointer(i)->message;
midiMsgPos = midiSequence.getEventTime(i);
if (midiMsgPos > midiReceiver.getLastNoteOffSamplePos())
{ // after last key was released...
if (midiMsg.isNoteOff())
{
midiMessages.addEvent (midiMsg, midiReceiver.getLastNoteOffSamplePos());
}
midiSequence.clear();
midiReceiver.setLastNoteOffSamplePos (INT_MAX);
break;
}
}
// add new events to midi sequence...
if (midiReceiver.getPressedKeys() > 0 && midiSequence.getNumEvents() == 0)
{
midiMsg = MidiMessage::noteOn (1, midiReceiver.getLastNoteNumber(), midiReceiver.getLastVelocity());
midiSequence.addEvent (midiMsg, offsetSamples);
offsetSamples += noteSamples;
midiMsg = MidiMessage::noteOff (1, midiReceiver.getLastNoteNumber());
midiSequence.addEvent (midiMsg, offsetSamples);
offsetSamples += noteSamples;
midiSequence.updateMatchedPairs(); // connect the note-on and note-off messages
}
// add midi sequence's events to midi buffer and update offsetSamples of events...
for (int i = 0; i < midiSequence.getNumEvents(); ++i)
{
midiMsg = midiSequence.getEventPointer(i)->message;
midiMsgPos = midiSequence.getEventTime(i);
if (midiMsgPos < blockSamples)
{ // event should appear in current block
midiMessages.addEvent (midiMsg, midiMsgPos);
midiSequence.deleteEvent (i--, false);
// TODO: update event's offsets (and offsetSamples?)
midiSequence.updateMatchedPairs();
}
else
{ // advance midi sequence's events to next block
midiSequence.addTimeToMessages (-blockSamples); // TODO: update event's offsets (and offsetSamples?)
midiSequence.updateMatchedPairs();
break;
}
}
}