Transpose a MIDI Note using MidiBuffer::Iterator

Hi,

One year ago I wrote the Zoabis ZS001 for a Masters Thesis. I used the jVSTwRapper and Java. Below is the code that kills all MIDI notes.

The Following Code is Java!

[code]if (kill == true) {

			for (int k = 0; k < 128; k++) {

				byte midiNoteMessage[] = new byte[4];

				// Set the type of the MIDI EVENT
				vme.setType(VSTEvent.VST_EVENT_MIDI_TYPE);

				vme.setByteSize(24); // 4 * 8 bytes

				vme.setDeltaFrames(event.getDeltaFrames()); // set this to
															// 0.

				midiNoteMessage[0] = (byte) (NOTE_ON); // sets midi status
														// to
														// note on

				midiNoteMessage[1] = (byte) (k); // sets midi note number
													// (changes the note by
													// 1)

				midiNoteMessage[2] = (byte) (0); // sets velocity

				midiNoteMessage[3] = (byte) (0); // Always zero.

				vme.setData(midiNoteMessage); // Copy the NOTE message into
												// VstMidiEventStruct

				VSTEvent[] ve = new VSTEvent[1]; // create a VSTEVENT
				ve[0] = vme; // insert the MIDIEVENT to the VSTEVENT
				ves.setNumEvents(1); // set the number of the VSTEVENTS to 1				
				ves.setEvents(ve); // insert the VSTEVENT to VSTEVENTS

				sendVstEventsToHost(ves);
			}
			kill = false;
		}[/code]

For the relevance of this read on


One year on and I have just began learning C++ and using Juce. I would like, for now, to [color=#FF0000]simply transpose a MIDI in real-time, adding 5 semitones.[/color] This is just a proof of concept. So far I have no luck. I am using the Audio Plugin Demo as a starting point, though obviously ticking ‘Plugin wants MIDI input/ Output/ Is a Synth’.

I assumed the place to do the transposition would be in processBlock and here is my code for that method:

[code]void ZS1AudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
const int numSamples = buffer.getNumSamples();
int channel, dp = 0;

// Go through the incoming data, and apply our gain to it...
for (channel = 0; channel < getNumInputChannels(); ++channel)
    buffer.applyGain (channel, 0, buffer.getNumSamples(), gain);

// 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, numSamples, true);

//midi input part
if (!midiMessages.isEmpty())
{

    MidiMessage midi_message(0xf0);
    MidiBuffer output;
    int sample_number;
    
    MidiBuffer::Iterator midi_buffer_iter(midiMessages);
    while(midi_buffer_iter.getNextEvent(midi_message,sample_number))
    {
       
        //I sent some messages to my GUI to make sure I was editing the right numbers...
        
        if( this->_currentMidi) { delete this->_currentMidi; this->_currentMidi = NULL; }
        this->_currentMidi = new MidiMessage(midi_message);
        
        _sampleNumber = sample_number;
        
        _lastNoteNumber = midi_message.getNoteNumber();
        
        _lastNoteVelocity = midi_message.getVelocity();
        
        
       //Preparing the transpose message:
        
        const int notenum = midi_message.getNoteNumber();
        
        const uint8 velocity = midi_message.getVelocity();
        
        const int channel = midi_message.getChannel();

        
        //Transposing by 5 semitones:
        
        int newNote = notenum+5;
      
        
        //Thought this would work but it didn't:
        
        output.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number);
    
    }
}


// and now get the synth to process these midi events and generate its output.
synth.renderNextBlock (buffer, midiMessages, 0, numSamples);

// Apply our delay effect to the new output..
for (channel = 0; channel < getNumInputChannels(); ++channel)
{
    float* channelData = buffer.getSampleData (channel);
    float* delayData = delayBuffer.getSampleData (jmin (channel, delayBuffer.getNumChannels() - 1));
    dp = delayPosition;

    for (int i = 0; i < numSamples; ++i)
    {
        const float in = channelData[i];
        channelData[i] += delayData[dp];
        delayData[dp] = (delayData[dp] + in) * delay;
        if (++dp > delayBuffer.getNumSamples())
            dp = 0;
    }
}

delayPosition = dp;

// In case we have more outputs than inputs, we'll clear any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
    buffer.clear (i, 0, buffer.getNumSamples());

// ask the host for the current time so we can display it...
AudioPlayHead::CurrentPositionInfo newTime;

if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (newTime))
{
    // Successfully got the current time from the host..
    lastPosInfo = newTime;
}
else
{
    // If the host fails to fill-in the current time, we'll just clear it to a default..
    lastPosInfo.resetToDefault();
}

}
[/code]

Can anybody please tell me why

doesn’t do it for me?

I included the Java code as there is a process like so:

[list]*Copy the NOTE message into a VstMidiEventStruct[/list]
[list]*Create a VSTEVENT[/list]
[list]*Insert the MIDIEVENT to the VSTEVENT[/list]
[list]*Set the number of the VSTEVENTS to 1[/list]
[list]*Insert the VSTEVENT to VSTEVENTS[/list]
and finally:

[list]*sendVstEventsToHost(ves);[/list]

So, it seems like

is quite simple in comparison. I must be missing some steps?

[color=#FF0000]How can I edit the code so my plugin adds 5 semitones to the note number?[/color]

Thanks in advance for any help

You definitely are
 I don’t see the point in code-time where you add the messages from this “MidiBuffer output” to “midiMessages” so that “synth.renderNextBlock (buffer, midiMessages, 0, numSamples);” will actually capture your changes!

Surely you can’t expect those messages to magically appear in “midiMessages”?

You definitely are
 I don’t see the point in code-time where you add the messages from this “MidiBuffer output” to “midiMessages” so that “synth.renderNextBlock (buffer, midiMessages, 0, numSamples);” will actually capture your changes!

Surely you can’t expect those messages to magically appear in “midiMessages”?[/quote]

Hi jrlangois,

Thanks for replying. So, I’m a novice. I know that. Any chance you could tell me what the code would be to add the [color=#FF0000]output[/color] to [color=#FF0000]midiMessages[/color]?

I tried this but it crashed Ableton:

[code] //Thought this would work but it didn’t:

        //output.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number);
        
        midiMessages.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number); [/code] 

Remember I’m a beginner so explicit lines of code are appreciated. I don’t believe in magic.

:lol:

OK, So I think I should be doing something like this but still the MIDI notes are not transposed:

[code] output.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number);

        midiMessages.addEvents(output, 0, 1, 0);[/code]

The format of the message is:

but I’m not sure exactly what numbers to fill in for the parameters.

What else do I need?

Any bright ideas?

For future reference for anybody who may struggle while testing an Audio Unit with transposition, the problem was not my code but the AU format. The following code easily changes a note when using a VST in a host but the Audio Unit doesn’t work as it cannot send MIDI to the host.

[code]void ZS1AudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
MidiBuffer output;

MidiBuffer::Iterator mid_buffer_iter(midiMessages);
MidiMessage m(0xf0);
int sample;
while(mid_buffer_iter.getNextEvent(m,sample))
{

		if (m.isNoteOn()) {

				const int ch = m.getChannel();
                          
                //Pre Transpose:
                _lastNoteNumber = m.getNoteNumber();
            
                //Add 5 semitones:
                const int tnote = m.getNoteNumber() + 5;
            
                //Display new transposed note:
                _tNote = tnote;
                            
				const uint8 v = m.getVelocity();
            
                output.addEvent(MidiMessage::noteOn(ch,tnote,v),sample);

        }
            
		else if (m.isNoteOff()) {

            
            const int ch = m.getChannel();

            
            //Add 5 semitones:
            const int tnote = m.getNoteNumber() + 5;
            const uint8 v = m.getVelocity();
            
            output.addEvent(MidiMessage::noteOff(ch,tnote,v),sample);

            
		}

}
midiMessages.clear();
midiMessages = output;
   

MidiBuffer finaloutput;

MidiBuffer::Iterator mid_buffer_iter2(midiMessages);
MidiMessage m2(0xf0);
int sample2;
while(mid_buffer_iter2.getNextEvent(m2,sample2))
{
    //Confirm the note got transposed, it did:
    _finalNoteNumber = m2.getNoteNumber();
}

}[/code]

Here’s exactly what I would do (warning: I didn’t test it out, but it seems right):

//Transpose midiMessages in processBlock algorithm:
static const int transpositionAmount = 5;
juce::MidiBuffer output;

juce::MidiBuffer::Iterator iterator (midiMessages);
juce::MidiMessage msg;
int sampleNum;

while (iterator.getNextEvent (msg, sampleNum))
{
    if (msg.isNoteOnOrOff())
    {
        msg.setNoteNumber (msg.getNoteNumber() + transpositionAmount);
    }

    output.addEvent (msg, sampleNum);
}

midiMessages = output;

It’s about as simple and straightforward as it gets. To be honest, I would even convert that into a static method to have an additional tool in my code-base.

1 Like

Hi,

Thanks for that. Looks good. I appreciate your help.

:lol:

surprise I was searching for this!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Thanks!!!!!!!!!!!!!!!

jrlanglois code works perfectly!!!!!!!!!!!!!!!!!!!!!

Â