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);
// …
}
1 Like

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

I had this problem also. The answer was that my controller was sending MIDI on two ports (i.e. USB MIDI In A and USB MIDI In B). When I selected “All MIDI Inputs” for the input source for the track, it was receiving two sets of notes. You could tell your track to only accept the input from a specific port rather than “all MIDI inputs” or tell your controller to only send the MIDI data on one port. For my controller I had the choice of “USB MIDI In A” or “USB MIDI In B”.

any other solution? not working
shareit https://get-vidmateapk.com

@barryallen1337 Adding the missing line and rebuilding the AudioHostPlugin was the only solution for me.

This!! ->

I had the exact same observation. Why would the notes be automatically passed through on top of what the plugin is generating? Thank you!!

By the way, I am interested in developing MIDI only plugins as well, and I am using the modifications in this thread to rebuild the AudioPluginHost to have an actual MIDI Output, so you don’t need to put other plugins inside it:

So my test of the ArpeggiatorDemoPlugin looks like this:

25%20AM

This has the bogus Audio Input required to make the Arpeggiator actually work, as detailed here:


(post withdrawn by author, will be automatically deleted in 24 hours unless flagged)