AudioPluginHost 5.4.3 : MIDI effect produces double notes?

(didn’t find any existing topic, so here goes)
I think I’m trying to do something simple, and it almost works. I’ve created a simple MIDI effect “Transpose”. I tried using the AudioPluginHost to test it.
MIDI input (internal) => MIDI effect “Transpose” => Synth plugin
(image hopefully attached)
The odd thing is that when Transpose is changed, the Synth plays both the original notes and the transposed ones. As if the Synth is always listening to the internal MIDI input, and the effect is just adding to it?
Important note: I was suspecting an error in “Transpose” and tested it in Cakewalk DAW. But it seems to work just fine there. The “MIDI effect” is built as a “Synth-with-MIDI-out”, which Cakewalk requires in order for it to accept MIDI input. Maybe that causes an issue? Maybe AudioPluginHost isn’t often used for testing MIDI plugins? (It’s much more convenient than a DAW.) AudioPluginHost code is very deep and I didn’t get too far with a debugger.
JUCE 5.4.3 / Win10 / VisualStudio 2017 / Cakewalk by BandLab latest
juce-winaudio%20rapsess

Still digging with debugger. Issue seems to occur in an Operator loop
op->process(context);
which gets called about 8 times. Part way thru, a MidiBuffer which started with one noteOn suddenly has 2. I will press on a bit. This section is very hard to trace.

Without seeing your code it’s hard to help :slight_smile: , but 2 things:

  • I use the Juce AudioPluginHost extensively for debugging/testing of MIDI effect plugins (I only do MIDI effects, really) - it works great for me and I doubt your issue would be caused by the pluginHost.
  • without seeing your code, are you sure you clear the incoming MidiBuffer& midiMessages in your processBock() ? - if not you will echo all incoming midi back out again and that would cause something like what you are describing.

Initially I assumed it was issue #2. After lots of fruitless effort, I tried the plugin directly in Cakewalk and it behaved as I expected. The “odd” configuration Cakewalk requires may be involved:
Y Plugin is a Synth
Y Plugin MIDI Input
Y Plugin MIDI Output
N MIDI Effect Plugin

Relevant code:
void MidiTransposeAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages)
{
// …audio clearing code here …

MidiMessage msg;
int samplePos;
MidiBuffer translated;
translated.clear();

for (MidiBuffer::Iterator it(midiMessages); it.getNextEvent(msg, samplePos); /*nothing*/)
{
	if (msg.isNoteOnOrOff())
	{
		int chan = msg.getChannel();
		int noteIn = msg.getNoteNumber();
		uint8 vel = msg.getVelocity(); //correction (not float)
		int noteOut = noteIn + semitones;
		MidiMessage msgOut;
		if (msg.isNoteOn())
			msgOut = MidiMessage::noteOn(chan, noteOut, vel);
		else
			msgOut = MidiMessage::noteOff(chan, noteOut, vel);
		translated.addEvent(msgOut, samplePos);
	}
	else
	{
		translated.addEvent(msg, samplePos); // pass thru
	}
}

midiMessages.clear();
midiMessages.addEvents(translated, 0, -1, 0); // copy modified

}

Assuming semitones is properly assigned the code looks OK to me. To what extent your Cakewalk config might screw up audioPluginHost … I don’t know.

Have you tried looking at the contents of midiMessages in debug mode, after addEvents() when you run it in audioPluginHost?

What I typically do different is to swap translated and midiMessages - saves you from clearing midiMessages, and swapWith() is not even a copy event - it just changes the pointers so it’s very fast.

midiMessages.swapWith(translated);

Doubt that would make a difference?

I think I may have found it, although I don’t really understand it. When my plugin is being called, it seems its MIDI output is being added to its input.
juce_VST3PluginFormat.cpp:
template
void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages,
Vst::SymbolicSampleSizes sampleSize, bool isProcessBlockBypassedCall)
{
// midiMessages has 1 event here, the input for my plugin
// …data setup skipped…
associateTo (data, buffer);
associateTo (data, midiMessages);

    processor->process (data);

// midiMessages still has one event here
for (auto* q : outputParameterChanges->queues)
{
// …
}
MidiEventList::toMidiBuffer (midiMessages, *midiOutputs);
// now midiMessages has 2 events (input + output)
// …
}
If I look at toMidiBuffer:

case Steinberg::Vst::Event::kNoteOnEvent:
result.addEvent (MidiMessage::noteOn (createSafeChannel (e.noteOn.channel),
createSafeNote (e.noteOn.pitch),
(Steinberg::uint8) denormaliseToMidiValue (e.noteOn.velocity)),
e.sampleOffset);
break;

Ah! VST3. I stick to VST2 for now - IIRC Juce has issues with Midi generator plugins under VST3. Google around and you’ll find lots of references to what these issues are. Here’s a couple of links:

https://forum.juce.com/search?q=vst3%20midi

Thanks @tomto66. I tried adding a midiBuffer.clear() (in the host) just before the plugin MIDI output events were loaded, and it solved my specific situation. I haven’t seen any other unexpected behavior.

I don’t really have access to VST2 SDK (maybe an old copy somewhere), but it’s nice to be aware of VST3 issues in advance. I probably won’t go too far down the MIDI road.

[tiny correction in my code: should have been
uint8 vel = msg.getVelocity(); // not float ]

Just to be 100% clear, in case anyone is following the topic. Without the indicated line being added in the AudioPluginHost code, the plugin’s MIDI output is always added to its input. (Don’t see how that could ever work.)

in juce_VST3PluginFormat.cpp:

template 
void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages,
Vst::SymbolicSampleSizes sampleSize, bool isProcessBlockBypassedCall)
{
// …
    processor->process (data);
// ...
    midiMessages.clear(); // <== **REQUIRED TO ADD THIS**
    MidiEventList::toMidiBuffer (midiMessages, *midiOutputs);
// …
}

Makes sense to me - I’m going to guess that VST3 Midi FX in Juce ais simply not completed (yet) - from what I can understand googling VTS3 specs it seems that the original (?) VST3 spec did not even allow for (certain) midi ins - may have evolved since and Juce has not caught up? Perhaps one of the Juce developers can shed a light on the exact state of affairs of Juce and VST3 Midi FX?

1 Like

Still digging with debugger. Issue seems to occur in an Operator loop
op->process(context); 9Apps VidMate.vin
which gets called about 8 times. Part way thru, a MidiBuffer which started with one noteOn suddenly has 2. I will press on a bit. This section is very hard to trace.

it was tough for me too earlier , once you’ll get use to it , it won’t be a pain in ass