delphimidihostvstlmms

Delphi: How to send MIDI to a hosted VST plugin?


I want to use VST plugins in my Delphi program which acts as a VST host. I have tried the tobybear examples, used the delphiasiovst stuf, got some of it even working, but... I don't know how to send MIDI messages to the plugin (I am aware that most plugins will not handle MIDI, but I have an example plugin that does).

To be more specific: I expect that when I send a MIDI message, I have to either use one or other method in the VST plugin or reroute the MIDI output. I just don't know how.

Can anyone point me to documentation or code on how to do this? Thanks in advance.

Arnold


I use two test plugins: the one compiled from the DelphiAsioVst package and PolyIblit. Both work in Finale and LMMS. Loaded into my test program both show their VST editor.

I did insert the TvstEvent record and initialized it, I inserted the MIDIData and the AddMIDIData procedures and a timer to provide test data and to execute the ProcessEvents routine of the plugin. ProcessEvents gets the correct test data, but no sound is heard. I hear something when I send it directly to the midi output port.

In the code below the PropcessEvents should be sufficient imho, the additional code is a test whether the MIDI information is correctly sent. VstHost [0] is the first plugin, being either the PolyIblit or the VSTPlugin, depending on the test.

procedure TMain_VST_Demo.TimerTimer (Sender: TObject);
var i: Int32;
begin
//   MIDIOutput.PutShort ($90, 60, 127);
   MIDIData (0, $90, 60, 127);

   if FMDataCnt > 0 then
   begin
      FMyEvents.numEvents := FMDataCnt;
      VSTHost[0].ProcessEvents(@FMyEvents);
//    if (FCurrentMIDIOut > 0) and MIMidiThru.Checked then
//     begin
      for i := 0 to FMDataCnt - 1 do
      MIDIOutput.PutShort (PVstMidiEvent (FMyEvents.events[i])^.midiData[0],
                           PVstMidiEvent (FMyEvents.events[i])^.midiData[1],
                           PVstMidiEvent (FMyEvents.events[i])^.midiData[2]);
//       FMidiOutput.Send(//FCurrentMIDIOut - 1,
//                   PVstMidiEvent(FMyEvents.events[i])^.midiData[0],
//                   PVstMidiEvent(FMyEvents.events[i])^.midiData[1],
//                   PVstMidiEvent(FMyEvents.events[i])^.midiData[2]);
//     end;
     FMDataCnt := 0;
   end;
end; // TimerTimer //

So I don't get the events in the plugin. Any idea what do I wrong?


Solution

  • You should really look at the minihost core example (Delphi ASIO project, v1.4).

    There is a use of midi events. Basically

    1. you have a TVstEvents variable ( let's say MyMidiEvents: TvstEvents).
    2. for the whole runtime you allocate the memory for this variable ( in the app constructor for exmaple)
    3. When you have an event in your MIDI callback, you copy it on the TVstEvents stack.
    4. Before calling process in the TVstHost, you call MyVstHost.ProcessEvents( @MyMidiEvents ).

    this is how it's done in the example (minihost core), for each previously steps:

    1/ at line 215, declaration

    FMyEvents: TVstEvents;
    

    2/ at line 376, allocation:

    for i := 0 to 2047 do
    begin
     GetMem(FMyEvents.Events[i], SizeOf(TVSTMidiEvent));
     FillChar(FMyEvents.Events[i]^, SizeOf(TVSTMidiEvent), 0);
     with PVstMidiEvent(FMyEvents.Events[i])^ do
      begin
       EventType := etMidi;
       ByteSize := 24;
      end;
    end;
    

    3/ at line 986 then at line 1782, the midi event is copied from the callback:

    the callback

    procedure TFmMiniHost.MidiData(const aDeviceIndex: Integer; const aStatus, aData1, aData2: Byte);
    begin
     if aStatus = $FE then exit; // ignore active sensing
     if (not Player.CbOnlyChannel1.Checked) or ((aStatus and $0F) = 0) then
      begin
       if (aStatus and $F0) = $90
        then NoteOn(aStatus, aData1, aData2) //ok
        else
       if (aStatus and $F0) = $80
        then NoteOff(aStatus, aData1)
        else AddMidiData(aStatus, aData1, aData2);
      end;
    end;
    

    event copy

    procedure TFmMiniHost.AddMIDIData(d1, d2, d3: byte; pos: Integer = 0);
    begin
     FDataSection.Acquire; 
     try
      if FMDataCnt > 2046 
       then exit;                 
    
      inc(FMDataCnt);
      with PVstMidiEvent(FMyEvents.events[FMDataCnt - 1])^ do
       begin
        EventType := etMidi;
        deltaFrames := pos;
        midiData[0] := d1;
        midiData[1] := d2;
        midiData[2] := d3;
       end;
     finally 
      FDataSection.Release;
     end; 
    end;
    

    4/ at line 2322, in TAsioHost.Bufferswitch, the TVstHost.ProcessEvents is called

    FDataSection.Acquire;
    try
      if FMDataCnt > 0 then
       begin
        FMyEvents.numEvents := FMDataCnt;
    
        VSTHost[0].ProcessEvents(FMyEvents);
    
        if (FCurrentMIDIOut > 0) and MIMidiThru.Checked then
         begin
          for i := 0 to FMDataCnt - 1 do
           FMidiOutput.Send(FCurrentMIDIOut - 1,
                       PVstMidiEvent(FMyEvents.events[i])^.midiData[0],
                       PVstMidiEvent(FMyEvents.events[i])^.midiData[1],
                       PVstMidiEvent(FMyEvents.events[i])^.midiData[2]);
         end;
         FMDataCnt := 0;
       end;
     finally  
      FDataSection.Release;
     end; 
    

    this should help you a lot if you were not able to analyse the method used.