This project has moved. For the latest updates, please go here.

Creating MIDI events manually

Jan 14, 2010 at 3:00 AM
Edited Jan 14, 2010 at 3:01 AM

Somebody could tell me how to use the VstMidiEvent class?

I want to create midi notes by code to send them to a VSTi, but I don't know how to use the VstMidiEvent type (I don't understand its contructor arguments), please I need a small example about creating midi notes to send them via ProcessEvents to the VSTi... please help me!

Thank you.

Coordinator
Jan 14, 2010 at 9:30 AM

Important to note is that the VstMidiEvent class is immutable. That means you cannot change property values once an instance is constructed. When a plugin outputs midi events it syncs up with the audio processing cycles (ProcessReplacing). So each time ProcessReplacing is called you can output all midi events that should play in that 'slice'. Gather the midi events in an instance of the VstEventCollection class. It does not matter when you fill the collection, though.

deltaFrames - the number of sample frames that preceed the start of the midi event.

noteLength - the length of the note played. As I understand it, this is only set when receiving events from the host and the host is playing a track - no during real-time midi event handling. Leave zero for outputting events.

noteOffset - the offset in sample frames into the note (cryptic, don't know what it means exactly). Also only set by the host. Leave zero for outputing events.

midiData[4] - the actual midi data of the event. Refer to the midi specifications for more details. Here's an unofficial online midi spec: http://home.roadrunner.com/~jgglatt

detune - used for micro tuning -63 / +64 for cents. You can leave this zero.

noteOffVelocity - A value between 0 - 127 that indicates the velocity at which the note was released. Also only used by the host. Leave zero for outputting events.

So to recap: you only need deltaFrames to indicate the time at which the midi event should occur (calculate using the SampleRate) and the midiData[] that contains the actual midi bytes of the midi event. Start with that and leave all the other fields zero.

You can refer to the MidiNoteMapper sample plugin to see how to output Midi events.

Jan 15, 2010 at 12:50 AM

Thank you for the reply, but I'm trying to develop a Host Application, so I need to generate at least one MIDI event (simulating a C4 note played in a keyboard for 2 seconds with a 127 of velocity for example) to send it to any VSTi plugin loaded. This could be the beginning for creating a visual keyboard :)

I used the next code in the ASIO thread:

VstEventCollection vMidiEvents = new VstEventCollection();
VstMidiEvent vMidiNote = new VstMidiEvent(0, 100, 100, new byte[] { 60, 100 }, 0, 110);
vMidiEvents.Add(vMidiNote);

this.PluginContext.PluginCommandStub.StartProcess();           
this.PluginContext.PluginCommandStub.ProcessEvents(vMidiEvents.ToArray());
this.PluginContext.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers);           
this.PluginContext.PluginCommandStub.StopProcess(); 

But I get an exception when calling the method ProcessReplacing "AccessViolationException was unhandled: Attempted to read or write protected memory. This is often an indication that other memory is corrupt"

Maybe the problem is in the values of the contructor arguments.... Could you help me please?

Sorry for the inconvenience.

Coordinator
Jan 18, 2010 at 5:45 AM

Are you sure you send the correct midi data?

Note-on on channel 1 = 144 (90 Hex)

Middle C = 60

Velocity = 100

So I think you need to use these values for the midi data passed into the event constructor { 144, 60, 100 }.

Hope it helps.

PS: this will produce a sticking note (one that never stops)

Jan 18, 2010 at 5:07 PM
Edited Jan 18, 2010 at 5:11 PM

Hello Marc, I corrected the values of the constructor, but after pressing the button "SendMidiNote" my program stops with an AccessViolationException.

This is the code I'm using now:

       VstEventCollection vMidiEvents = new VstEventCollection(); // private field

        // ASIO event handler to update the output
        void driver_BufferUpdate(object sender, EventArgs e)
        {           
            if (vMidiEvents.Count > 0 && this.PluginContext.PluginCommandStub.CanDo("receiveVstMidiEvent") >= 0)
            {
                this.PluginContext.PluginCommandStub.ProcessEvents(vMidiEvents.ToArray());
                //vMidiEvents.Clear();
            }

            this.PluginContext.PluginCommandStub.StartProcess();
            this.PluginContext.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers);
            this.PluginContext.PluginCommandStub.StopProcess();
           
            for (int index = 0; index < leftOutput.BufferSize; index++)
            {
                leftOutput[index] = outputBuffers[0][index];
                rightOutput[index] = outputBuffers[1][index];
            }
        }

This code works fine, after loading a VSTi I can play some notes in the keyboard of the plugin and it works because no MIDI event has been processed, but then I press the button "Create MIDI Event":

        private void btnCreateMidiEvent_Click(object sender, RoutedEventArgs e)
        {           
           VstEventCollection vMidiEvents = new VstEventCollection();
           VstEvent vstEvent = new VstMidiEvent(0, 0, 0, new byte[] { 144, 64, 100}, 0, 0);               
           vMidiEvents.Add(vstEvent);
        }

This code executes without errors, but an AccessViolationException is raised on the "this.PluginContext.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers);" line. I did a debug an step into over that line and the exception is raised in the red line:

       // VstPluginCommandStub.h File

       void CallProcess32(float** inputs, float** outputs, ::VstInt32 sampleFrames)
       {
          if(_pEffect && _pEffect->processReplacing)
          {
             _traceCtx->WriteProcess(_pEffect->numInputs, _pEffect->numOutputs, sampleFrames, sampleFrames);

             _pEffect->processReplacing(_pEffect, inputs, outputs, sampleFrames);
          }
       }

Please help me one more time, I don't know why this line raises the AccessViolationException when I create a MIDI Event.

Thank you in advance..... (sorry by the size of this post)

Coordinator
Jan 19, 2010 at 7:53 AM

The btnCreateMidiEvent_Click method uses a local instance of the MidiEventCollection. So no events will end up in the private class member field that is used during processing..???

Jan 19, 2010 at 11:35 AM

Sorry, I made a mistake when copying the code from visual studio --> note pad --> here, (it was necessary to remove the text format) , the event handler is this:

        private void btnCreateMidiEvent_Click(object sender, RoutedEventArgs e)
        {            
           VstEvent vstEvent = new VstMidiEvent(0, 0, 0, new byte[] { 144, 64, 100}, 0, 0);               
           vMidiEvents.Add(vstEvent);
        }

So, the event is added to the private class field, I'm using the vstnet-33227 source code version and the execution stoped here: _pEffect->processReplacing(_pEffect, inputs, outputs, sampleFrames);

I'm not using an effect, I'm using a VSTi synth, please help me.

Thany you in advance.

Coordinator
Jan 19, 2010 at 12:35 PM

Its hard to see from a distance what is going wrong here.

A couple of things that I notice and might help (or might not ;-)

  • In the ASIO callback StartProcessing and StopProcessing is called repeatedly. I don't thing this is how these calls were meant. Refer to the object interaction diagrams in the VST 2.4 SDK documentation for more information (turn on/turn off). My understanding is that these methods are called when your (host) audio engine (the code that pushed audio data through plugins and to the soundcard) is started or stopped.
  • Depending on how often the ASIO callback is called, it will pass a Midi event in each cycle. So I would call that Clear method on the VstMidiEventCollection (now commented out).
  • Repeatedly calling CanDo is also sub-optimal (you can use the VstPluginContext to store these values so you don't hit the plugin each time you need it. But I doubt that this is the cause of your problems.

Are you testing this with that minimal drum plugin?

If you want you can send me your code (or post it here if you want to) so I can take a look at it.

Hope it helps.

Jan 20, 2010 at 2:28 AM
Edited Jan 24, 2010 at 2:09 PM

Hello Marc, I don't know how to attach a file here, so I sent you a mail with my attached code.
 
I tested my application with a very basic VSTi plugin and it works fine!!! yes, the exception does not raise,  but when I load ColossusVST.dll or Kontakt3.dll (bigger plugins) the AccessViolationException is raised because of ProcessEvents() method :( 

With minimal drum plugin I have the same problem after calling ProcessEvents.
 
 
Thank you in advance.

PS: Thank you for the 3 corrections, I fixed my code but the problem persists

Feb 2, 2010 at 4:30 PM

Hello obiwanjacobi, sorry to bother you. Could you tell me if you get the mail with my source code about this problem?

Thank you.

Coordinator
Feb 3, 2010 at 5:23 AM

Hi Hugo,

I did receive your email and even looked at the source code. I briefly tried some plugins but could not find any problems. Unfortunately my time has been limited since then. I am planning to give it another try, but I cannot promise you when that will be.

Sorry.

Feb 3, 2010 at 1:18 PM

Hello Marc, I understand, you must be a very busy man and for that I am very grateful that you have reviewed my code. If someday you can return to review it, test with Minimal Drum plugin or any Native Instruments plugin, I have the same problem after calling ProcessEvents.

Thank you.

Feb 11, 2010 at 7:32 PM

Hi Marc, I found the source of my AccessViolationException exception when trying to send MIDI events to VST plugin, the problem was in the commented lines:

System::Boolean VstPluginCommandStub::ProcessEvents(array<Jacobi::Vst::Core::VstEvent^>^ events)
{
 ::VstEvents* pEvents = TypeConverter::AllocUnmanagedEvents(events);
 
 /*try
 {
  return (CallDispatch(effProcessEvents, 0, 0, pEvents, 0) != 0);
 }
 finally
 {
  TypeConverter::DeleteUnmanagedEvents(pEvents);
 }*/

 return (CallDispatch(effProcessEvents, 0, 0, pEvents, 0) != 0);
 TypeConverter::DeleteUnmanagedEvents(pEvents);
}

For some reason (I don't know why) when using try/finally block in the ProcessEvents method, throws an AccessViolationException in the ProcessReplacing method, so I commented that block of code and I copied those two lines at the end of the method without the try/finally block and my application works perfectly :D

Thanks for spending your time checking my source code.

P.S. In the future I will be bothering you with other questions... sorry :)

Coordinator
Feb 12, 2010 at 5:06 AM
Edited Feb 12, 2010 at 6:05 AM

Strange...??

Thanks for posting your findings. Problem with your solution is that it now leaks (unmanaged) memory. The cleanup method (DeleteUnmanagedEvents) will never be called because its after the return statement. You could change the code to something like:

System::Boolean success = (CallDispatch(effProcessEvents, 0, 0, pEvents, 0) != 0);
TypeConverter::DeleteUnmanagedEvents(pEvents);
return success;

Let me know if the problem re-occurs with this code. Then its a bug in the DeleteUnmanagedEvents method...

Coordinator
Feb 12, 2010 at 5:09 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Feb 13, 2010 at 12:00 PM

Hi Hugo,

Could you download the latest source code?

I've fixed the DeleteUnmanagedEvents method but was not able to test it.

Could you give it a go?

Thanx,
Marc

Feb 13, 2010 at 4:21 PM

Hi Marc, I tested the latest source code but unfortunately the problem persists due to DeleteUnmanagedEvents method.

This are the steps describing the process that takes to AccessViolationException:

1. Load any pluggin (from Native Instruments or EastWest).

2. Enable ASIO event handler driver_BufferUpdate, in this place are the next important lines of code:

this.PluginContext.PluginCommandStub.ProcessEvents(vMidiEvents.ToArray());
this.PluginContext.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers);               
vMidiEvents.Clear();

the first time these lines are executed, the vMidiEvents collection is empty, so no MIDI event was generated yet.

3. So, firstly the this.PluginContext.PluginCommandStub.ProcessEvents(vMidiEvents.ToArray());  line is executed, and the DeleteUnmanagedEvents method is executed too, but the execution doesn't go into the for statement because there is no MIDI events yet, so it just executes the delete[] pEvents; line.

4. The exception is not raised yet, but then the second line is executed this.PluginContext.PluginCommandStub.ProcessReplacing(inputBuffers, outputBuffers); in this point is where the exception happens :(

Marc, thanks a lot for your help.

Coordinator
Feb 14, 2010 at 9:18 AM

I cannot find any fault in the DeleteUnmanagedEvents code. I've also done some more testing with your host code and the minimal plugin. I can reproduce the problem.

So this can only mean one thing: The lifetime of the (Midi)Events has to span beyond the call to ProcessEvents. And it makes sense that a plugin processes midi events on the (audio) Process(Replacing) call.

So I have to find a way to make these events live longer and still not cause a memory leak.

Thanx for persuing this with me (and having the patience ;-)!

Coordinator
Feb 14, 2010 at 9:46 AM

I have checked in some new code (Interop) which seems to work with your host code and the minimal plugin.

Let me know if you have any other troubles.

BTW: Could you take a look at the comments I placed on the request for a Host Framework (Issues tab). I ask for more specific information on what such a framework should do. I am currently examining a plugin audio/midi processing engine -for I figured that would be the hardest to get right- but you may want/mean something else.

Thanx.

Feb 14, 2010 at 7:22 PM

Hi Marc, I tested your latest source code and now it works perfectly with any pluggin, thank you a lot! :)

About Host Framework, I was focused following this discussion and I ignored that issue temporally, so I did not read it lately... sorry, but now I will continue developing an application that is a host, sequencer and a few other things for live performance, so I will think in some requeriments for a Host Framework but maybe they would be done with the actual implementation of VST.NET I don't know... I will post some comment about it very soon.

Thanks for the solution!