mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added midi-out support for AU hosting.
This commit is contained in:
parent
43a1037656
commit
08b57b8a0d
4 changed files with 110 additions and 30 deletions
|
|
@ -72,8 +72,8 @@ public:
|
|||
*/
|
||||
virtual void handlePartialSysexMessage (MidiInput* source,
|
||||
const uint8* messageData,
|
||||
const int numBytesSoFar,
|
||||
const double timestamp)
|
||||
int numBytesSoFar,
|
||||
double timestamp)
|
||||
{
|
||||
// (this bit is just to avoid compiler warnings about unused variables)
|
||||
(void) source; (void) messageData; (void) numBytesSoFar; (void) timestamp;
|
||||
|
|
|
|||
|
|
@ -48,8 +48,9 @@ public:
|
|||
pendingDataTime = 0;
|
||||
}
|
||||
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void pushMidiData (const void* inputData, int numBytes, double time,
|
||||
MidiInput* input, MidiInputCallback& callback)
|
||||
UserDataType* input, CallbackType& callback)
|
||||
{
|
||||
const uint8* d = static_cast <const uint8*> (inputData);
|
||||
|
||||
|
|
@ -105,8 +106,9 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
template <typename UserDataType, typename CallbackType>
|
||||
void processSysex (const uint8*& d, int& numBytes, double time,
|
||||
MidiInput* input, MidiInputCallback& callback)
|
||||
UserDataType* input, CallbackType& callback)
|
||||
{
|
||||
if (*d == 0xf0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <AudioUnit/AUCocoaUIView.h>
|
||||
#include <CoreAudioKit/AUGenericView.h>
|
||||
#include <AudioToolbox/AudioUnitUtilities.h>
|
||||
#include <CoreMIDI/MIDIServices.h>
|
||||
|
||||
#if JUCE_SUPPORT_CARBON
|
||||
#include <AudioUnit/AudioUnitCarbonView.h>
|
||||
|
|
@ -39,6 +40,8 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
#include "../../juce_audio_devices/native/juce_MidiDataConcatenator.h"
|
||||
|
||||
#if JUCE_SUPPORT_CARBON
|
||||
#include "../../juce_gui_extra/native/juce_mac_CarbonViewWrapperComponent.h"
|
||||
#endif
|
||||
|
|
@ -120,7 +123,7 @@ namespace AudioUnitFormatHelpers
|
|||
if (nameString != 0 && nameString[0] != 0)
|
||||
{
|
||||
const String all ((const char*) nameString + 1, nameString[0]);
|
||||
DBG ("name: "+ all);
|
||||
DBG ("name: " + all);
|
||||
|
||||
manufacturer = all.upToFirstOccurrenceOf (":", false, false).trim();
|
||||
name = all.fromFirstOccurrenceOf (":", false, false).trim();
|
||||
|
|
@ -185,9 +188,9 @@ namespace AudioUnitFormatHelpers
|
|||
return false;
|
||||
|
||||
const char* const utf8 = fileOrIdentifier.toUTF8();
|
||||
CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8,
|
||||
strlen (utf8), file.isDirectory());
|
||||
if (url != 0)
|
||||
|
||||
if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8,
|
||||
strlen (utf8), file.isDirectory()))
|
||||
{
|
||||
CFBundleRef bundleRef = CFBundleCreate (kCFAllocatorDefault, url);
|
||||
CFRelease (url);
|
||||
|
|
@ -212,14 +215,12 @@ namespace AudioUnitFormatHelpers
|
|||
if (manuString != 0 && CFGetTypeID (manuString) == CFStringGetTypeID())
|
||||
manufacturer = String::fromCFString ((CFStringRef) manuString);
|
||||
|
||||
short resFileId = CFBundleOpenBundleResourceMap (bundleRef);
|
||||
const short resFileId = CFBundleOpenBundleResourceMap (bundleRef);
|
||||
UseResFile (resFileId);
|
||||
|
||||
for (int i = 1; i <= Count1Resources ('thng'); ++i)
|
||||
{
|
||||
Handle h = Get1IndResource ('thng', i);
|
||||
|
||||
if (h != 0)
|
||||
if (Handle h = Get1IndResource ('thng', i))
|
||||
{
|
||||
HLock (h);
|
||||
const uint32* const types = (const uint32*) *h;
|
||||
|
|
@ -275,13 +276,17 @@ class AudioUnitPluginInstance : public AudioPluginInstance
|
|||
public:
|
||||
AudioUnitPluginInstance (const String& fileOrIdentifier)
|
||||
: fileOrIdentifier (fileOrIdentifier),
|
||||
wantsMidiMessages (false), wasPlaying (false), prepared (false),
|
||||
wantsMidiMessages (false),
|
||||
producesMidiMessages (false),
|
||||
wasPlaying (false),
|
||||
prepared (false),
|
||||
currentBuffer (nullptr),
|
||||
numInputBusChannels (0),
|
||||
numOutputBusChannels (0),
|
||||
numInputBusses (0),
|
||||
numOutputBusses (0),
|
||||
audioUnit (0)
|
||||
audioUnit (0),
|
||||
midiConcatenator (2048)
|
||||
{
|
||||
using namespace AudioUnitFormatHelpers;
|
||||
|
||||
|
|
@ -328,6 +333,7 @@ public:
|
|||
{
|
||||
refreshParameterList();
|
||||
updateNumChannels();
|
||||
producesMidiMessages = canProduceMidiOutput();
|
||||
setPluginCallbacks();
|
||||
setPlayConfigDetails (numInputBusChannels * numInputBusses,
|
||||
numOutputBusChannels * numOutputBusses, 0, 0);
|
||||
|
|
@ -358,7 +364,7 @@ public:
|
|||
void* getPlatformSpecificData() { return audioUnit; }
|
||||
const String getName() const { return pluginName; }
|
||||
bool acceptsMidi() const { return wantsMidiMessages; }
|
||||
bool producesMidi() const { return false; }
|
||||
bool producesMidi() const { return producesMidiMessages; }
|
||||
|
||||
//==============================================================================
|
||||
// AudioProcessor methods:
|
||||
|
|
@ -462,6 +468,8 @@ public:
|
|||
currentBuffer = nullptr;
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
incomingMidi.clear();
|
||||
}
|
||||
|
||||
void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
|
||||
|
|
@ -519,6 +527,13 @@ public:
|
|||
for (int i = 0; i < getNumOutputChannels(); ++i)
|
||||
buffer.clear (i, 0, buffer.getNumSamples());
|
||||
}
|
||||
|
||||
if (producesMidiMessages)
|
||||
{
|
||||
const ScopedLock sl (midiInLock);
|
||||
midiMessages.swapWith (incomingMidi);
|
||||
incomingMidi.clear();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -799,6 +814,14 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void handleIncomingMidiMessage (void*, const MidiMessage& message)
|
||||
{
|
||||
const ScopedLock sl (midiInLock);
|
||||
incomingMidi.addEvent (message, 0);
|
||||
}
|
||||
|
||||
void handlePartialSysexMessage (void*, const uint8*, int, double) {}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class AudioUnitPluginWindowCarbon;
|
||||
|
|
@ -809,7 +832,7 @@ private:
|
|||
String pluginName, manufacturer, version;
|
||||
String fileOrIdentifier;
|
||||
CriticalSection lock;
|
||||
bool wantsMidiMessages, wasPlaying, prepared;
|
||||
bool wantsMidiMessages, producesMidiMessages, wasPlaying, prepared;
|
||||
|
||||
HeapBlock <AudioBufferList> outputBufferList;
|
||||
AudioTimeStamp timeStamp;
|
||||
|
|
@ -819,6 +842,10 @@ private:
|
|||
AudioUnit audioUnit;
|
||||
Array <int> parameterIds;
|
||||
|
||||
MidiDataConcatenator midiConcatenator;
|
||||
CriticalSection midiInLock;
|
||||
MidiBuffer incomingMidi;
|
||||
|
||||
void setPluginCallbacks()
|
||||
{
|
||||
if (audioUnit != 0)
|
||||
|
|
@ -833,6 +860,16 @@ private:
|
|||
kAudioUnitScope_Input, i, &info, sizeof (info));
|
||||
}
|
||||
|
||||
if (producesMidiMessages)
|
||||
{
|
||||
AUMIDIOutputCallbackStruct info = { 0 };
|
||||
info.userData = this;
|
||||
info.midiOutputCallback = renderMidiOutputCallback;
|
||||
|
||||
producesMidiMessages = (AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallback,
|
||||
kAudioUnitScope_Global, 0, &info, sizeof (info)) == noErr);
|
||||
}
|
||||
|
||||
{
|
||||
HostCallbackInfo info = { 0 };
|
||||
info.hostUserData = this;
|
||||
|
|
@ -840,13 +877,12 @@ private:
|
|||
info.musicalTimeLocationProc = getMusicalTimeLocationCallback;
|
||||
info.transportStateProc = getTransportStateCallback;
|
||||
|
||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks, kAudioUnitScope_Global,
|
||||
0, &info, sizeof (info));
|
||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_HostCallbacks,
|
||||
kAudioUnitScope_Global, 0, &info, sizeof (info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
OSStatus renderGetInput (AudioUnitRenderActionFlags* ioActionFlags,
|
||||
const AudioTimeStamp* inTimeStamp,
|
||||
|
|
@ -879,6 +915,23 @@ private:
|
|||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus renderMidiOutput (const MIDIPacketList* pktlist)
|
||||
{
|
||||
if (pktlist != nullptr && pktlist->numPackets)
|
||||
{
|
||||
const double time = Time::getMillisecondCounterHiRes() * 0.001;
|
||||
const MIDIPacket* packet = &pktlist->packet[0];
|
||||
|
||||
for (int i = 0; i < pktlist->numPackets; ++i)
|
||||
{
|
||||
midiConcatenator.pushMidiData (packet->data, (int) packet->length, time, (void*) nullptr, *this);
|
||||
packet = MIDIPacketNext (packet);
|
||||
}
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus getBeatAndTempo (Float64* outCurrentBeat, Float64* outCurrentTempo) const
|
||||
{
|
||||
AudioPlayHead* const ph = getPlayHead();
|
||||
|
|
@ -969,6 +1022,12 @@ private:
|
|||
->renderGetInput (ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
|
||||
}
|
||||
|
||||
static OSStatus renderMidiOutputCallback (void* userData, const AudioTimeStamp* timeStamp, UInt32 midiOutNum,
|
||||
const struct MIDIPacketList* pktlist)
|
||||
{
|
||||
return static_cast <AudioUnitPluginInstance*> (userData)->renderMidiOutput (pktlist);
|
||||
}
|
||||
|
||||
static OSStatus getBeatAndTempoCallback (void* inHostUserData, Float64* outCurrentBeat, Float64* outCurrentTempo)
|
||||
{
|
||||
return static_cast <AudioUnitPluginInstance*> (inHostUserData)
|
||||
|
|
@ -1075,6 +1134,29 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
bool canProduceMidiOutput()
|
||||
{
|
||||
UInt32 dataSize = 0;
|
||||
Boolean isWritable = false;
|
||||
|
||||
if (AudioUnitGetPropertyInfo (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo,
|
||||
kAudioUnitScope_Global, 0, &dataSize, &isWritable) == noErr
|
||||
&& dataSize != 0)
|
||||
{
|
||||
CFArrayRef midiArray;
|
||||
|
||||
if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MIDIOutputCallbackInfo,
|
||||
kAudioUnitScope_Global, 0, &midiArray, &dataSize) == noErr)
|
||||
{
|
||||
bool result = (CFArrayGetCount (midiArray) > 0);
|
||||
CFRelease (midiArray);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginInstance);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -233,10 +233,10 @@ extern XContext windowHandleXContext;
|
|||
|
||||
typedef void (*EventProcPtr) (XEvent* ev);
|
||||
|
||||
static bool xErrorTriggered;
|
||||
|
||||
namespace
|
||||
{
|
||||
static bool xErrorTriggered = false;
|
||||
|
||||
int temporaryErrorHandler (Display*, XErrorEvent*)
|
||||
{
|
||||
xErrorTriggered = true;
|
||||
|
|
@ -489,15 +489,13 @@ public:
|
|||
bool open()
|
||||
{
|
||||
bool ok = false;
|
||||
const String filename (file.getFullPathName());
|
||||
|
||||
if (file.hasFileExtension (".vst"))
|
||||
{
|
||||
const char* const utf8 = filename.toUTF8().getAddress();
|
||||
CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8,
|
||||
strlen (utf8), file.isDirectory());
|
||||
const char* const utf8 = file.getFullPathName().toUTF8().getAddress();
|
||||
|
||||
if (url != 0)
|
||||
if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) utf8,
|
||||
strlen (utf8), file.isDirectory()))
|
||||
{
|
||||
bundleRef = CFBundleCreate (kCFAllocatorDefault, url);
|
||||
CFRelease (url);
|
||||
|
|
@ -513,9 +511,7 @@ public:
|
|||
|
||||
if (moduleMain != 0)
|
||||
{
|
||||
CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName"));
|
||||
|
||||
if (name != 0)
|
||||
if (CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")))
|
||||
{
|
||||
if (CFGetTypeID (name) == CFStringGetTypeID())
|
||||
{
|
||||
|
|
@ -557,7 +553,7 @@ public:
|
|||
{
|
||||
FSRef fn;
|
||||
|
||||
if (FSPathMakeRef ((UInt8*) filename.toUTF8().getAddress(), &fn, 0) == noErr)
|
||||
if (FSPathMakeRef ((UInt8*) file.getFullPathName().toUTF8().getAddress(), &fn, 0) == noErr)
|
||||
{
|
||||
resFileId = FSOpenResFile (&fn, fsRdPerm);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue