Transpose a MIDI Note using MidiBuffer::Iterator


#1

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


#2

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”?


#3

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:


#4

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?


#5

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]


#6

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.


#7

Hi,

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

:lol:


#8

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

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

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