commit 5560dc02cee4430acb95bff8fe3e318a430cef52
parent 475c2125c974f85ab41e4b12acf834694de6837b
Author: falkTX <falktx@falktx.com>
Date: Fri, 23 Feb 2024 22:50:42 +0100
Implement MIDI out for AU
Signed-off-by: falkTX <falktx@falktx.com>
Diffstat:
6 files changed, 177 insertions(+), 26 deletions(-)
diff --git a/distrho/src/DistrhoPluginAU.cpp b/distrho/src/DistrhoPluginAU.cpp
@@ -198,6 +198,13 @@ typedef std::vector<PropertyListener> PropertyListeners;
// --------------------------------------------------------------------------------------------------------------------
+typedef struct {
+ UInt32 numPackets;
+ MIDIPacket packets[kMaxMidiEvents];
+} MIDIPacketList;
+
+// --------------------------------------------------------------------------------------------------------------------
+
#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
static constexpr const writeMidiFunc writeMidiCallback = nullptr;
#endif
@@ -242,6 +249,15 @@ public:
fBypassParameterIndex = i;
}
}
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ std::memset(&fMidiEvents, 0, sizeof(fMidiEvents));
+ #endif
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ std::memset(&fMidiOutput, 0, sizeof(fMidiOutput));
+ std::memset(&fMidiOutputPackets, 0, sizeof(fMidiOutputPackets));
+ #endif
}
~PluginAU()
@@ -255,6 +271,9 @@ public:
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
fMidiEventCount = 0;
#endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ fMidiOutputPackets.numPackets = 0;
+ #endif
return noErr;
}
@@ -417,10 +436,33 @@ public:
return kAudioUnitErr_InvalidProperty;
#endif
+ case kAudioUnitProperty_MIDIOutputCallbackInfo:
+ 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_OUTPUT
+ outDataSize = sizeof(CFArrayRef);
+ outWritable = false;
+ return noErr;
+ #else
+ return kAudioUnitErr_InvalidProperty;
+ #endif
+
+ case kAudioUnitProperty_MIDIOutputCallback:
+ 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_OUTPUT
+ outDataSize = sizeof(AUMIDIOutputCallbackStruct);
+ outWritable = true;
+ return noErr;
+ #else
+ 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
+ // FIXME implement the event list stuff
+ #if 0 && (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT)
outDataSize = sizeof(SInt32);
outWritable = false;
return noErr;
@@ -757,10 +799,26 @@ public:
return noErr;
#endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ case kAudioUnitProperty_MIDIOutputCallbackInfo:
+ DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
+ DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement == 0, inElement, kAudioUnitErr_InvalidElement);
+ {
+ CFStringRef refs[1] = { CFSTR("MIDI Callback") };
+ *static_cast<CFArrayRef*>(outData) = CFArrayCreate(nullptr,
+ reinterpret_cast<const void**>(refs),
+ 1,
+ &kCFTypeArrayCallBacks);
+ }
+ return noErr;
+ #endif
+
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ /* FIXME implement the event list stuff
case kAudioUnitProperty_AudioUnitMIDIProtocol:
*static_cast<SInt32*>(outData) = kMIDIProtocol_1_0;
return noErr;
+ */
#endif
#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
@@ -980,6 +1038,15 @@ public:
// TODO
return noErr;
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ case kAudioUnitProperty_MIDIOutputCallback:
+ 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(AUMIDIOutputCallbackStruct), inDataSize, kAudioUnitErr_InvalidPropertyValue);
+ std::memcpy(&fMidiOutput, inData, sizeof(AUMIDIOutputCallbackStruct));
+ return noErr;
+ #endif
+
case 'DPFe':
DISTRHO_SAFE_ASSERT_UINT_RETURN(inScope == kAudioUnitScope_Global, inScope, kAudioUnitErr_InvalidScope);
DISTRHO_SAFE_ASSERT_UINT_RETURN(inElement < fParameterCount, inElement, kAudioUnitErr_InvalidElement);
@@ -1190,6 +1257,9 @@ public:
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
fMidiEventCount = 0;
#endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ fMidiOutputPackets.numPackets = 0;
+ #endif
return noErr;
}
@@ -1241,27 +1311,13 @@ public:
constexpr float** outputs = nullptr;
#endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ fMidiOutputPackets.numPackets = 0;
+ #endif
+
#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;
- }
- }
+ importRingBufferNotes();
#endif
fPlugin.run(inputs, outputs, inFramesToProcess, fMidiEvents, fMidiEventCount);
@@ -1270,6 +1326,16 @@ public:
fPlugin.run(inputs, outputs, inFramesToProcess);
#endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ if (fMidiOutputPackets.numPackets != 0 && fMidiOutput.midiOutputCallback != nullptr)
+ {
+ fMidiOutput.midiOutputCallback(fMidiOutput.userData,
+ &inTimeStamp,
+ 0,
+ reinterpret_cast<const ::MIDIPacketList*>(&fMidiOutputPackets));
+ }
+ #endif
+
float value;
AudioUnitEvent event;
std::memset(&event, 0, sizeof(event));
@@ -1296,6 +1362,11 @@ public:
}
return noErr;
+
+ #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ // unused
+ (void)inTimeStamp;
+ #endif
}
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
@@ -1331,6 +1402,28 @@ public:
break;
}
+ // if plugin has no audio, assume render function is not going to be called
+ #if DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS == 0
+ #if DISTRHO_PLUGIN_HAS_UI
+ importRingBufferNotes();
+ #endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ fMidiOutputPackets.numPackets = 0;
+ #endif
+
+ fPlugin.run(nullptr, nullptr, std::max(1u, inOffsetSampleFrame), fMidiEvents, fMidiEventCount);
+ fMidiEventCount = 0;
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ if (fMidiOutputPackets.numPackets != 0 && fMidiOutput.midiOutputCallback != nullptr)
+ {
+ fMidiOutput.midiOutputCallback(fMidiOutput.userData,
+ nullptr, 0, // FIXME do we need a valid timestamp?
+ reinterpret_cast<const ::MIDIPacketList*>(&fMidiOutputPackets));
+ }
+ #endif
+ #endif
+
return noErr;
}
@@ -1372,8 +1465,38 @@ private:
#endif
#endif
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ AUMIDIOutputCallbackStruct fMidiOutput;
+ MIDIPacketList fMidiOutputPackets;
+ #endif
+
// ----------------------------------------------------------------------------------------------------------------
+ #if DISTRHO_PLUGIN_HAS_UI
+ void importRingBufferNotes()
+ {
+ 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
+
void notifyListeners(const AudioUnitPropertyID prop, const AudioUnitScope scope, const AudioUnitElement elem)
{
for (PropertyListeners::iterator it = fPropertyListeners.begin(); it != fPropertyListeners.end(); ++it)
@@ -1398,8 +1521,20 @@ private:
// DPF callbacks
#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
- bool writeMidi(const MidiEvent&)
+ bool writeMidi(const MidiEvent& midiEvent)
{
+ DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("MIDI output unsupported", fMidiOutput.midiOutputCallback != nullptr, false);
+
+ if (midiEvent.size > sizeof(MIDIPacket::data))
+ return true;
+ if (fMidiOutputPackets.numPackets == kMaxMidiEvents)
+ return false;
+
+ const uint8_t* const midiData = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data;
+ MIDIPacket& packet(fMidiOutputPackets.packets[fMidiOutputPackets.numPackets++]);
+ packet.timeStamp = midiEvent.frame;
+ packet.length = midiEvent.size;
+ std::memcpy(packet.data, midiData, midiEvent.size);
return true;
}
@@ -1410,8 +1545,18 @@ private:
#endif
#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
- bool requestParameterValueChange(uint32_t, float)
+ bool requestParameterValueChange(const uint32_t index, const float value)
{
+ AudioUnitEvent event;
+ std::memset(&event, 0, sizeof(event));
+ event.mEventType = kAudioUnitEvent_ParameterValueChange;
+ event.mArgument.mParameter.mAudioUnit = fComponent;
+ event.mArgument.mParameter.mParameterID = index;
+ event.mArgument.mParameter.mScope = kAudioUnitScope_Global;
+
+ fLastParameterValues[index] = value;
+ AUEventListenerNotify(NULL, NULL, &event);
+ notifyListeners('DPFP', kAudioUnitScope_Global, index);
return true;
}
diff --git a/distrho/src/DistrhoPluginChecks.h b/distrho/src/DistrhoPluginChecks.h
@@ -120,7 +120,7 @@
# define DISTRHO_PLUGIN_AU_TYPE aumf /* kAudioUnitType_MusicEffect */
# elif (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) && DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
# define DISTRHO_PLUGIN_AU_TYPE aumu /* kAudioUnitType_MusicDevice */
-# elif (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) && DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0
+# elif DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
# define DISTRHO_PLUGIN_AU_TYPE aumi /* kAudioUnitType_MIDIProcessor */
# elif DISTRHO_PLUGIN_NUM_INPUTS == 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0
# define DISTRHO_PLUGIN_AU_TYPE augn /* kAudioUnitType_Generator */
diff --git a/distrho/src/DistrhoPluginVST2.cpp b/distrho/src/DistrhoPluginVST2.cpp
@@ -1084,7 +1084,7 @@ public:
if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading())
{
uint8_t midiData[3];
- const 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/DistrhoUIAU.mm b/distrho/src/DistrhoUIAU.mm
@@ -67,10 +67,11 @@ public:
setStateCallback,
sendNoteCallback,
setSizeCallback,
- nullptr, // TODO file request
+ nullptr,
d_nextBundlePath,
instancePointer)
{
+ d_stdout("UI created");
constexpr const CFTimeInterval interval = 60 * 0.0001;
CFRunLoopTimerContext context = {};
@@ -86,6 +87,7 @@ public:
~DPF_UI_AU()
{
+ d_stdout("UI destroyed");
AudioUnitRemovePropertyListenerWithUserData(fComponent, 'DPFP', auPropertyChangedCallback, this);
if (fTimerRef != nullptr)
diff --git a/examples/MidiThrough/DistrhoPluginInfo.h b/examples/MidiThrough/DistrhoPluginInfo.h
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
*
* 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/MidiThrough"
#define DISTRHO_PLUGIN_CLAP_ID "studio.kx.distrho.examples.midi-through"
+#define DISTRHO_PLUGIN_AU_SUBTYPE midt
+#define DISTRHO_PLUGIN_AU_MANUFACTURER Dstr
+
#define DISTRHO_PLUGIN_HAS_UI 0
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
#define DISTRHO_PLUGIN_NUM_INPUTS 0
diff --git a/examples/MidiThrough/Makefile b/examples/MidiThrough/Makefile
@@ -28,6 +28,7 @@ TARGETS += lv2_dsp
TARGETS += vst2
TARGETS += vst3
TARGETS += clap
+TARGETS += au
all: $(TARGETS)