Transpose a MIDI Note using MidiBuffer::Iterator

8 posts / 0 new
Last post
Zoabis
Offline
Last seen: 5 months 6 days ago
Joined: 14 Jun 2012 - 20:34
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!**

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;
			}

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 simply transpose a MIDI in real-time, adding 5 semitones. 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:

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();
    }
}

Can anybody please tell me why

output.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number); doesn't do it for me?

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

    *Copy the NOTE message into a VstMidiEventStruct
    *Create a VSTEVENT
    *Insert the MIDIEVENT to the VSTEVENT
    *Set the number of the VSTEVENTS to 1
    *Insert the VSTEVENT to VSTEVENTS
and finally:
    *sendVstEventsToHost(ves);

So, it seems like

output.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number); is quite simple in comparison. I must be missing some steps? How can I edit the code so my plugin adds 5 semitones to the note number?

Thanks in advance for any help

jrlanglois
Offline
Last seen: 1 day 12 hours ago
Joined: 12 Aug 2011 - 17:54
Re: Transpose a MIDI Note using MidiBuffer::Iterator

Quote:
I must be missing some steps?

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

[Visual Studio 2013] [Xcode 5.1.1]
[latest JUCE tip] [CodeTools]

Zoabis
Offline
Last seen: 5 months 6 days ago
Joined: 14 Jun 2012 - 20:34
Re: Transpose a MIDI Note using MidiBuffer::Iterator

jrlanglois wrote:
Quote:
I must be missing some steps?

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

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 output to midiMessages?

I tried this but it crashed Ableton:

            //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); 

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

:lol:

Zoabis
Offline
Last seen: 5 months 6 days ago
Joined: 14 Jun 2012 - 20:34
Re: Transpose a MIDI Note using MidiBuffer::Iterator

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

            output.addEvent(MidiMessage::noteOn(channel,newNote,velocity),sample_number);
            
            midiMessages.addEvents(output, 0, 1, 0);

The format of the message is:

midiMessages.addEvents(<#const juce::MidiBuffer &otherBuffer#>, <#int startSample#>, <#int numSamples#>, <#int sampleDeltaToAdd#>) but I'm not sure exactly what numbers to fill in for the parameters.

What else do I need?

Any bright ideas?

Zoabis
Offline
Last seen: 5 months 6 days ago
Joined: 14 Jun 2012 - 20:34
Re: Transpose a MIDI Note using MidiBuffer::Iterator

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.

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();
    }

}
jrlanglois
Offline
Last seen: 1 day 12 hours ago
Joined: 12 Aug 2011 - 17:54
Re: Transpose a MIDI Note using MidiBuffer::Iterator

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.

[Visual Studio 2013] [Xcode 5.1.1]
[latest JUCE tip] [CodeTools]

Zoabis
Offline
Last seen: 5 months 6 days ago
Joined: 14 Jun 2012 - 20:34
Re: Transpose a MIDI Note using MidiBuffer::Iterator

Hi,

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

:lol:

BK8
Offline
Last seen: 2 months 1 week ago
Joined: 1 Sep 2013 - 23:13
Thanks it works

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

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

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