Transpose a MIDI Note using MidiBuffer::Iterator

Discuss issues relating to audio plugins

Transpose a MIDI Note using MidiBuffer::Iterator

Postby Zoabis » Fri Aug 17, 2012 9:19 pm

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: Select all
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:

Code: Select all
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

Code: Select all
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

Code: Select all
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
User avatar
Zoabis
JUCE Geek
 
Posts: 25
Joined: Thu Jun 14, 2012 8:34 pm
Location: Tallinn, Estonia

Re: Transpose a MIDI Note using MidiBuffer::Iterator

Postby jrlanglois » Fri Aug 17, 2012 9:47 pm

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 2012] [Xcode 4.6] [x64 Ubuntu 12.10 - NetBeans 7.3]
[latest JUCE tip] [My JUCE Fork] [FriendlyBinaryBuilder]
User avatar
jrlanglois
JUCE UberWeenie
 
Posts: 573
Joined: Fri Aug 12, 2011 5:54 pm
Location: Ottawa, Ontario, Canada

Re: Transpose a MIDI Note using MidiBuffer::Iterator

Postby Zoabis » Fri Aug 17, 2012 10:00 pm

jrlanglois wrote:
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:

Code: Select all
            //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:
User avatar
Zoabis
JUCE Geek
 
Posts: 25
Joined: Thu Jun 14, 2012 8:34 pm
Location: Tallinn, Estonia

Re: Transpose a MIDI Note using MidiBuffer::Iterator

Postby Zoabis » Fri Aug 17, 2012 10:30 pm

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

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


The format of the message is:

Code: Select all
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?
User avatar
Zoabis
JUCE Geek
 
Posts: 25
Joined: Thu Jun 14, 2012 8:34 pm
Location: Tallinn, Estonia

Re: Transpose a MIDI Note using MidiBuffer::Iterator

Postby Zoabis » Sat Aug 18, 2012 5:15 pm

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

}
Last edited by Zoabis on Sat Aug 18, 2012 8:44 pm, edited 1 time in total.
User avatar
Zoabis
JUCE Geek
 
Posts: 25
Joined: Thu Jun 14, 2012 8:34 pm
Location: Tallinn, Estonia

Re: Transpose a MIDI Note using MidiBuffer::Iterator

Postby jrlanglois » Sat Aug 18, 2012 5:50 pm

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

Code: Select all
//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 2012] [Xcode 4.6] [x64 Ubuntu 12.10 - NetBeans 7.3]
[latest JUCE tip] [My JUCE Fork] [FriendlyBinaryBuilder]
User avatar
jrlanglois
JUCE UberWeenie
 
Posts: 573
Joined: Fri Aug 12, 2011 5:54 pm
Location: Ottawa, Ontario, Canada

Re: Transpose a MIDI Note using MidiBuffer::Iterator

Postby Zoabis » Sat Aug 18, 2012 8:39 pm

Hi,

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

:lol:
User avatar
Zoabis
JUCE Geek
 
Posts: 25
Joined: Thu Jun 14, 2012 8:34 pm
Location: Tallinn, Estonia


Return to Audio Plugins

Who is online

Users browsing this forum: Neirbo and 2 guests