VST3 (midi) and JUCE

I was looking into this recently. The fact is, I was able to modify the JUCE AudioPluginHost to send CC Output from VST3’s, using the kLegacyMIDICCOutEvent. The problem is, it seems, is this: the modifications do not have anything to do with your plugin itself, but only the host. So fixing this in JUCE’s host does nothing to fix your VST3 plugin’s ability to send CCs in Reaper, Digital Performer, or any other host. It seems nobody supports this.

Here’s the completely unfinished hack (two modified functions), but it did work as a test. Note that I only implemented it for actual real CCs, but they have a way of using this for pitch bend, aftertouch, maybe a few other things, with Controller Numbers > 127.

juce_VST3Common.h

    static void toMidiBuffer (MidiBuffer& result, Steinberg::Vst::IEventList& eventList)
    {
        const int32 numEvents = eventList.getEventCount();

        for (Steinberg::int32 i = 0; i < numEvents; ++i)
        {
            Steinberg::Vst::Event e;

            if (eventList.getEvent (i, e) == Steinberg::kResultOk)
            {
                switch (e.type)
                {
                    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;

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

                    case Steinberg::Vst::Event::kPolyPressureEvent:
                        result.addEvent (MidiMessage::channelPressureChange (createSafeChannel (e.polyPressure.channel),
                                                                             denormaliseToMidiValue (e.polyPressure.pressure)),
                                         e.sampleOffset);
                        break;

                    case Steinberg::Vst::Event::kDataEvent:
                        result.addEvent (MidiMessage::createSysExMessage (e.data.bytes, (int) e.data.size),
                                         e.sampleOffset);
                        break;
                        
                    // srk - unfinished hack to allow VST3s to send Midi Controllers
                    // fixes it in AudioPluginHost, but has no effect in other DAWs
                   /*
                    case Steinberg::Vst::Event::kLegacyMIDICCOutEvent:
                        result.addEvent (MidiMessage::controllerEvent (e.midiCCOut.channel, e.midiCCOut.controlNumber, e.midiCCOut.value),
                                         e.sampleOffset);
                        break;
                    */
                    default:
                        break;
                }
            }
        }
    }
    static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer,
                             Steinberg::Vst::IParameterChanges* parameterChanges = nullptr,
                             Steinberg::Vst::IMidiMapping* midiMapping = nullptr)
    {
        MidiBuffer::Iterator iterator (midiBuffer);
        const uint8* midiEventData = nullptr;
        int midiEventSize = 0;
        int midiEventPosition = 0;

        enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once
        int numEvents = 0;

        while (iterator.getNextEvent (midiEventData, midiEventSize, midiEventPosition))
        {
            if (++numEvents > maxNumEvents)
                break;

            MidiMessage msg (midiEventData, midiEventSize);

            if (midiMapping != nullptr && parameterChanges != nullptr)
            {
                Vst3MidiControlEvent controlEvent;

                if (toVst3ControlEvent (msg, controlEvent))
                {
                    Steinberg::Vst::ParamID controlParamID;

                    if (midiMapping->getMidiControllerAssignment (0, createSafeChannel (msg.getChannel()),
                                                                  controlEvent.controllerNumber,
                                                                  controlParamID) == Steinberg::kResultOk)
                    {
                        Steinberg::int32 ignore;

                        if (auto* queue = parameterChanges->addParameterData (controlParamID, ignore))
                            queue->addPoint (midiEventPosition, controlEvent.paramValue, ignore);
                    }

                    continue;
                }
            }

            Steinberg::Vst::Event e = { 0 };

            if (msg.isNoteOn())
            {
                e.type              = Steinberg::Vst::Event::kNoteOnEvent;
                e.noteOn.channel    = createSafeChannel (msg.getChannel());
                e.noteOn.pitch      = createSafeNote (msg.getNoteNumber());
                e.noteOn.velocity   = normaliseMidiValue (msg.getVelocity());
                e.noteOn.length     = 0;
                e.noteOn.tuning     = 0.0f;
                e.noteOn.noteId     = -1;
            }
            else if (msg.isNoteOff())
            {
                e.type              = Steinberg::Vst::Event::kNoteOffEvent;
                e.noteOff.channel   = createSafeChannel (msg.getChannel());
                e.noteOff.pitch     = createSafeNote (msg.getNoteNumber());
                e.noteOff.velocity  = normaliseMidiValue (msg.getVelocity());
                e.noteOff.tuning    = 0.0f;
                e.noteOff.noteId    = -1;
            }
            else if (msg.isSysEx())
            {
                e.type          = Steinberg::Vst::Event::kDataEvent;
                e.data.bytes    = midiEventData + 1;
                e.data.size     = (uint32) msg.getSysExDataSize();
                e.data.type     = Steinberg::Vst::DataEvent::kMidiSysEx;
            }
            else if (msg.isChannelPressure())
            {
                e.type                   = Steinberg::Vst::Event::kPolyPressureEvent;
                e.polyPressure.channel   = createSafeChannel (msg.getChannel());
                e.polyPressure.pitch     = createSafeNote (msg.getNoteNumber());
                e.polyPressure.pressure  = normaliseMidiValue (msg.getChannelPressureValue());
            }
            // srk - unfinished hack to allow VST3s to send Midi Controllers
            // fixes it in AudioPluginHost, but has no effect in other DAWs
            /*
            else if (msg.isController())
            {
                e.type                   = Steinberg::Vst::Event::kLegacyMIDICCOutEvent;
                e.midiCCOut.channel   = (Steinberg::int8) msg.getChannel();
                e.midiCCOut.controlNumber = (Steinberg::uint8) msg.getControllerNumber();
                e.midiCCOut.value     = (Steinberg::int8) msg.getControllerValue();
                e.midiCCOut.value2    = 0;
            } */
            
            else
            {
                continue;
            }

            e.busIndex = 0;
            e.sampleOffset = midiEventPosition;

            result.addEvent (e);
        }
    }
2 Likes