From 96e4ba06afa466227023bab075d7b696b2aa9bd0 Mon Sep 17 00:00:00 2001 From: Oliver James Date: Fri, 13 Dec 2024 16:08:49 +0000 Subject: [PATCH] AudioProcessor: Add VST2/VST3 midi note name support Co-authored-by: Roland Rabien --- .../juce_audio_plugin_client_VST2.cpp | 17 ++++++++++++++ .../juce_audio_plugin_client_VST3.cpp | 22 +++++++++++++++++-- .../format_types/juce_VST3PluginFormat.cpp | 16 ++++++++++++++ .../format_types/juce_VSTPluginFormat.cpp | 12 ++++++++++ .../processors/juce_AudioProcessor.cpp | 5 +++++ .../processors/juce_AudioProcessor.h | 15 +++++++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp index 065247a3b3..b83fa02a75 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp @@ -918,6 +918,7 @@ public: case Vst2::effSetProcessPrecision: return handleSetSampleFloatType (args); case Vst2::effGetNumMidiInputChannels: return handleGetNumMidiInputChannels(); case Vst2::effGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels(); + case Vst2::effGetMidiKeyName: return handleGetMidiKeyName (args); case Vst2::effEditIdle: return handleEditIdle(); default: return 0; } @@ -2049,6 +2050,22 @@ private: return 0; } + pointer_sized_int handleGetMidiKeyName (VstOpCodeArguments args) + { + if (processor != nullptr) + { + auto keyName = (Vst2::MidiKeyName*) args.ptr; + + if (auto name = processor->getNameForMidiNoteNumber (keyName->thisKeyNumber, args.index)) + { + name->copyToUTF8 (keyName->keyName, Vst2::kVstMaxNameLen); + return 1; + } + } + + return 0; + } + pointer_sized_int handleEditIdle() { #if JUCE_LINUX || JUCE_BSD diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index 2e62ed015c..55fe1004a5 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -525,9 +525,27 @@ public: return kResultFalse; } + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override + { + for (int i = 0; i <= 127; ++i) + if (audioProcessor->getNameForMidiNoteNumber (i, 1)) + return kResultTrue; + + return kResultFalse; + } + + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16 midiNote, Vst::String128 nameOut) override + { + if (auto name = audioProcessor->getNameForMidiNoteNumber (midiNote, 1)) + { + toString128 (nameOut, *name); + return kResultTrue; + } + + return kResultFalse; + } + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override { return kNotImplemented; } - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override { return kNotImplemented; } - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override { return kNotImplemented; } tresult PLUGIN_API selectUnit (Vst::UnitID) override { return kNotImplemented; } tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index fb122ff5d0..2aff5e32da 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -3090,6 +3090,22 @@ public: return result; } + std::optional getNameForMidiNoteNumber (int note, int /*midiChannel*/) override + { + if (unitInfo == nullptr || unitInfo->getProgramListCount() == 0) + return std::nullopt; + + Vst::String128 name{}; + Vst::ProgramListInfo programListInfo{}; + + const auto nameOk = unitInfo->getProgramListInfo (0, programListInfo) == kResultOk + && unitInfo->hasProgramPitchNames (programListInfo.id, 0) == kResultTrue + && unitInfo->getProgramPitchName (programListInfo.id, 0, (Steinberg::int16) note, name) == kResultOk; + + return nameOk ? std::make_optional (toString (name)) + : std::nullopt; + } + //============================================================================== void updateTrackProperties (const TrackProperties& properties) override { diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 0a8f485642..68ca74d5ae 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -1335,6 +1335,18 @@ struct VSTPluginInstance final : public AudioPluginInstance, int pluginCanDo (const char* text) const { return (int) dispatch (Vst2::effCanDo, 0, 0, (void*) text, 0); } + std::optional getNameForMidiNoteNumber (int note, int midiChannel) override + { + Vst2::MidiKeyName keyName{}; + + keyName.thisProgramIndex = getCurrentProgram(); + keyName.thisKeyNumber = note; + + return dispatch (Vst2::effGetMidiKeyName, midiChannel, 0, &keyName, 0.0f) != 0 + ? std::make_optional (String::createStringFromData (keyName.keyName, Vst2::kVstMaxNameLen)) + : std::nullopt; + } + //============================================================================== void prepareToPlay (double rate, int samplesPerBlockExpected) override { diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 740eb051cf..a28e46aa5e 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -950,6 +950,11 @@ void AudioProcessor::copyXmlToBinary (const XmlElement& xml, juce::MemoryBlock& = ByteOrder::swapIfBigEndian ((uint32) destData.getSize() - 9); } +std::optional AudioProcessor::getNameForMidiNoteNumber (int /*note*/, int /*midiChannel*/) +{ + return std::nullopt; +} + std::unique_ptr AudioProcessor::getXmlFromBinary (const void* data, const int sizeInBytes) { if (sizeInBytes > 8 && ByteOrder::littleEndianInt (data) == magicXmlNumber) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 103d303c12..bd8ce8556a 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1327,6 +1327,21 @@ public: */ virtual void updateTrackProperties (const TrackProperties& properties); + /** Returns a custom name for a MIDI note number. + + This method allows the host to query your plugin for a custom name to display + for a given MIDI note number. It's useful for plugins that work with drum kits, + microtonal scales, or other mappings. + + @param note The MIDI note number for which the name is being requested. + This is an integer in the range [0, 127], representing the + full MIDI note range. + @param midiChannel The MIDI channel associated with the note. This is a 1-based + index (1-16). Use this parameter if your plugin provides + channel-specific note mappings. + */ + virtual std::optional getNameForMidiNoteNumber (int note, int midiChannel); + //============================================================================== /** Helper function that just converts an xml element into a binary blob.