diff --git a/distrho/src/DistrhoPluginAU.cpp b/distrho/src/DistrhoPluginAU.cpp index 8f1d1f12..8a63f65f 100644 --- a/distrho/src/DistrhoPluginAU.cpp +++ b/distrho/src/DistrhoPluginAU.cpp @@ -21,6 +21,10 @@ #include "DistrhoPluginInternal.hpp" #include "../DistrhoPluginUtils.hpp" +#if DISTRHO_PLUGIN_HAS_UI +# include "../extra/RingBuffer.hpp" +#endif + #include #include #include @@ -221,6 +225,9 @@ public: fParameterCount(fPlugin.getParameterCount()), fLastParameterValues(nullptr), fBypassParameterIndex(UINT32_MAX) + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + , fMidiEventCount(0) + #endif { if (fParameterCount != 0) { @@ -245,6 +252,9 @@ public: OSStatus auInitialize() { fPlugin.activate(); + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + fMidiEventCount = 0; + #endif return noErr; } @@ -306,17 +316,24 @@ public: #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0 case kAudioUnitProperty_StreamFormat: - #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0 - DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output, inScope, kAudioUnitErr_InvalidScope); - #elif DISTRHO_PLUGIN_NUM_INPUTS != 0 - DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Input, inScope, kAudioUnitErr_InvalidScope); - #else - DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Output, inScope, kAudioUnitErr_InvalidScope); - #endif DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); - outDataSize = sizeof(AudioStreamBasicDescription); - outWritable = true; - return noErr; + #if DISTRHO_PLUGIN_NUM_INPUTS != 0 + if (inScope == kAudioUnitScope_Input) + { + outDataSize = sizeof(AudioStreamBasicDescription); + outWritable = true; + return noErr; + } + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0 + if (inScope == kAudioUnitScope_Output) + { + outDataSize = sizeof(AudioStreamBasicDescription); + outWritable = true; + return noErr; + } + #endif + return kAudioUnitErr_InvalidScope; #endif case kAudioUnitProperty_ElementCount: @@ -375,6 +392,13 @@ public: outWritable = true; return noErr; + case kAudioUnitProperty_InPlaceProcessing: + DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); + outDataSize = sizeof(UInt32); + outWritable = false; + return noErr; + case kAudioUnitProperty_PresentPreset: DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); @@ -393,6 +417,17 @@ public: return kAudioUnitErr_InvalidProperty; #endif + case kAudioUnitProperty_AudioUnitMIDIProtocol: + DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + outDataSize = sizeof(SInt32); + outWritable = false; + return noErr; + #else + return kAudioUnitErr_InvalidProperty; + #endif + #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS case 'DPFa': DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); @@ -416,6 +451,15 @@ public: outWritable = true; return noErr; + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT && DISTRHO_PLUGIN_HAS_UI + case 'DPFn': + DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); + outDataSize = sizeof(uint8_t) * 3; + outWritable = true; + return noErr; + #endif + #if DISTRHO_PLUGIN_WANT_STATE case 'DPFs': DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); @@ -670,6 +714,10 @@ public: // TODO break; + case kAudioUnitProperty_InPlaceProcessing: + *static_cast(outData) = 1; + return noErr; + case kAudioUnitProperty_PresentPreset: { AUPreset* const preset = static_cast(outData); @@ -709,6 +757,12 @@ public: return noErr; #endif + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + case kAudioUnitProperty_AudioUnitMIDIProtocol: + *static_cast(outData) = kMIDIProtocol_1_0; + return noErr; + #endif + #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS case 'DPFa': *static_cast(outData) = fPlugin.getInstancePointer(); @@ -967,6 +1021,20 @@ public: } return noErr; + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT && DISTRHO_PLUGIN_HAS_UI + case 'DPFn': + DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement); + DISTRHO_SAFE_ASSERT_UINT_RETURN(inDataSize == sizeof(uint8_t) * 3, inDataSize, kAudioUnitErr_InvalidPropertyValue); + { + const uint8_t* const midiData = static_cast(inData); + + fNotesRingBuffer.writeCustomData(midiData, 3); + fNotesRingBuffer.commitWrite(); + } + return noErr; + #endif + #if DISTRHO_PLUGIN_WANT_STATE case 'DPFs': DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope); @@ -1108,6 +1176,23 @@ public: return noErr; } + OSStatus auReset(const AudioUnitScope scope, const AudioUnitElement elem) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global || scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output, scope, kAudioUnitErr_InvalidScope); + DISTRHO_SAFE_ASSERT_UINT_RETURN(elem == 0, elem, kAudioUnitErr_InvalidElement); + + if (fPlugin.isActive()) + { + fPlugin.deactivate(); + fPlugin.activate(); + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + fMidiEventCount = 0; + #endif + return noErr; + } + OSStatus auRender(AudioUnitRenderActionFlags& ioActionFlags, const AudioTimeStamp& inTimeStamp, const UInt32 inBusNumber, @@ -1156,12 +1241,35 @@ public: constexpr float** outputs = nullptr; #endif - #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - fPlugin.run(inputs, outputs, inFramesToProcess, nullptr, 0); - #else - fPlugin.run(inputs, outputs, inFramesToProcess); + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + #if DISTRHO_PLUGIN_HAS_UI + if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading()) + { + uint8_t midiData[3]; + const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0; + + while (fNotesRingBuffer.isDataAvailableForReading()) + { + if (! fNotesRingBuffer.readCustomData(midiData, 3)) + break; + + MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]); + midiEvent.frame = frame; + midiEvent.size = 3; + std::memcpy(midiEvent.data, midiData, 3); + + if (fMidiEventCount == kMaxMidiEvents) + break; + } + } #endif + fPlugin.run(inputs, outputs, inFramesToProcess, fMidiEvents, fMidiEventCount); + fMidiEventCount = 0; + #else + fPlugin.run(inputs, outputs, inFramesToProcess); + #endif + float value; AudioUnitEvent event; std::memset(&event, 0, sizeof(event)); @@ -1190,20 +1298,49 @@ public: return noErr; } - OSStatus auReset(const AudioUnitScope scope, const AudioUnitElement elem) + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + OSStatus auMIDIEvent(const UInt32 inStatus, + const UInt32 inData1, + const UInt32 inData2, + const UInt32 inOffsetSampleFrame) { - DISTRHO_SAFE_ASSERT_UINT_RETURN(scope == kAudioUnitScope_Global || scope == kAudioUnitScope_Input || scope == kAudioUnitScope_Output, scope, kAudioUnitErr_InvalidScope); - DISTRHO_SAFE_ASSERT_UINT_RETURN(elem == 0, elem, kAudioUnitErr_InvalidElement); + if (fMidiEventCount >= kMaxMidiEvents) + return noErr; - if (fPlugin.isActive()) + MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]); + midiEvent.frame = inOffsetSampleFrame; + midiEvent.data[0] = inStatus; + midiEvent.data[1] = inData1; + midiEvent.data[2] = inData2; + + switch (inStatus) { - fPlugin.deactivate(); - fPlugin.activate(); + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xD0: + case 0xE0: + midiEvent.size = 3; + break; + case 0xC0: + midiEvent.size = 2; + break; + default: + midiEvent.size = 1; + break; } return noErr; } + OSStatus auSysEx(const UInt8* const inData, const UInt32 inLength) + { + // TODO + return kAudioUnitErr_PropertyNotInUse; + } + #endif + // ---------------------------------------------------------------------------------------------------------------- private: @@ -1227,6 +1364,14 @@ private: float* fLastParameterValues; uint32_t fBypassParameterIndex; + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + uint32_t fMidiEventCount; + MidiEvent fMidiEvents[kMaxMidiEvents]; + #if DISTRHO_PLUGIN_HAS_UI + SmallStackRingBuffer fNotesRingBuffer; + #endif + #endif + // ---------------------------------------------------------------------------------------------------------------- void notifyListeners(const AudioUnitPropertyID prop, const AudioUnitScope scope, const AudioUnitElement elem) @@ -1371,6 +1516,12 @@ struct AudioComponentPlugInInstance { case kAudioUnitProcessMultipleSelect: return reinterpret_cast(ProcessMultiple); */ + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + case kMusicDeviceMIDIEventSelect: + return reinterpret_cast(MIDIEvent); + case kMusicDeviceSysExSelect: + return reinterpret_cast(SysEx); + #endif } d_stdout("TODO Lookup(%3d:%s)", selector, AudioUnitSelector2Str(selector)); @@ -1594,6 +1745,22 @@ struct AudioComponentPlugInInstance { return self->plugin->auRender(*ioActionFlags, *inTimeStamp, inOutputBusNumber, inNumberFrames, *ioData); } + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + static OSStatus MIDIEvent(AudioComponentPlugInInstance* const self, + const UInt32 inStatus, + const UInt32 inData1, + const UInt32 inData2, + const UInt32 inOffsetSampleFrame) + { + return self->plugin->auMIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame); + } + + static OSStatus SysEx(AudioComponentPlugInInstance* const self, const UInt8* const inData, const UInt32 inLength) + { + return self->plugin->auSysEx(inData, inLength); + } + #endif + DISTRHO_DECLARE_NON_COPYABLE(AudioComponentPlugInInstance) }; diff --git a/distrho/src/DistrhoPluginVST2.cpp b/distrho/src/DistrhoPluginVST2.cpp index 1186bc96..33ad7093 100644 --- a/distrho/src/DistrhoPluginVST2.cpp +++ b/distrho/src/DistrhoPluginVST2.cpp @@ -1084,7 +1084,7 @@ public: if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading()) { uint8_t midiData[3]; - uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0; + const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0; while (fNotesRingBuffer.isDataAvailableForReading()) { diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp index 467b1362..5fd53af0 100644 --- a/distrho/src/DistrhoPluginVST3.cpp +++ b/distrho/src/DistrhoPluginVST3.cpp @@ -3103,7 +3103,7 @@ private: event.midi_cc_out.cc_number = data[1]; event.midi_cc_out.value = data[2]; if (midiEvent.size == 4) - event.midi_cc_out.value2 = midiEvent.size == 4; + event.midi_cc_out.value2 = data[3]; break; /* TODO how do we deal with program changes?? case 0xC0: diff --git a/distrho/src/DistrhoUIAU.mm b/distrho/src/DistrhoUIAU.mm index 68c41846..fe7c9cc5 100644 --- a/distrho/src/DistrhoUIAU.mm +++ b/distrho/src/DistrhoUIAU.mm @@ -133,6 +133,8 @@ private: fUI.parameterChanged(elem, value); } break; + case 'DPFS': + break; } } @@ -180,14 +182,16 @@ private: const size_t len_key = std::strlen(key); const size_t len_value = std::strlen(value); const size_t len_combined = len_key + len_value + 2; - char* const data = new char[len_combined]; + char* const data = static_cast(std::malloc(len_combined)); + DISTRHO_SAFE_ASSERT_RETURN(data != nullptr,); + std::memcpy(data, key, len_key); std::memcpy(data + len_key + 1, value, len_value); data[len_key] = data[len_key + len_value + 1] = '\0'; AudioUnitSetProperty(fComponent, 'DPFs', kAudioUnitScope_Global, len_combined, data, len_combined); - delete[] data; + std::free(data); } static void setStateCallback(void* const ptr, const char* const key, const char* const value) @@ -197,8 +201,10 @@ private: #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - void sendNote(uint8_t, uint8_t, uint8_t) + void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) { + const uint8_t data[3] = { static_cast((velocity != 0 ? 0x90 : 0x80) | channel), note, velocity }; + AudioUnitSetProperty(fComponent, 'DPFn', kAudioUnitScope_Global, 0, data, sizeof(data)); } static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity) @@ -226,7 +232,8 @@ END_NAMESPACE_DISTRHO #define MACRO_NAME2(a, b, c, d, e, f) a ## b ## c ## d ## e ## f #define MACRO_NAME(a, b, c, d, e, f) MACRO_NAME2(a, b, c, d, e, f) -#define COCOA_VIEW_CLASS_NAME MACRO_NAME(CocoaAUView_, DISTRHO_PLUGIN_AU_TYPE, _, DISTRHO_PLUGIN_AU_SUBTYPE, _, DISTRHO_PLUGIN_AU_MANUFACTURER) +#define COCOA_VIEW_CLASS_NAME \ + MACRO_NAME(CocoaAUView_, DISTRHO_PLUGIN_AU_TYPE, _, DISTRHO_PLUGIN_AU_SUBTYPE, _, DISTRHO_PLUGIN_AU_MANUFACTURER) @interface COCOA_VIEW_CLASS_NAME : NSObject { diff --git a/examples/SendNote/DistrhoPluginInfo.h b/examples/SendNote/DistrhoPluginInfo.h index ddb9018d..3762b383 100644 --- a/examples/SendNote/DistrhoPluginInfo.h +++ b/examples/SendNote/DistrhoPluginInfo.h @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2022 Filipe Coelho + * Copyright (C) 2012-2024 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -22,6 +22,9 @@ #define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/SendNote" #define DISTRHO_PLUGIN_CLAP_ID "studio.kx.distrho.examples.send-note" +#define DISTRHO_PLUGIN_AU_SUBTYPE note +#define DISTRHO_PLUGIN_AU_MANUFACTURER Dstr + #define DISTRHO_PLUGIN_HAS_UI 1 #define DISTRHO_PLUGIN_HAS_EMBED_UI 1 #define DISTRHO_PLUGIN_IS_RT_SAFE 1 diff --git a/examples/SendNote/Makefile b/examples/SendNote/Makefile index c6e3630e..c242387b 100644 --- a/examples/SendNote/Makefile +++ b/examples/SendNote/Makefile @@ -35,6 +35,7 @@ TARGETS += lv2_sep TARGETS += vst2 TARGETS += vst3 TARGETS += clap +TARGETS += au all: $(TARGETS)