fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).

This commit is contained in:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

View File

@ -0,0 +1,193 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Helper class that takes chunks of incoming midi bytes, packages them into
messages, and dispatches them to a midi callback.
@tags{Audio}
*/
class MidiDataConcatenator
{
public:
//==============================================================================
MidiDataConcatenator (int initialBufferSize)
: pendingData ((size_t) initialBufferSize)
{
}
void reset()
{
pendingBytes = 0;
runningStatus = 0;
pendingDataTime = 0;
}
template <typename UserDataType, typename CallbackType>
void pushMidiData (const void* inputData, int numBytes, double time,
UserDataType* input, CallbackType& callback)
{
const uint8* d = static_cast<const uint8*> (inputData);
while (numBytes > 0)
{
if (pendingBytes > 0 || d[0] == 0xf0)
{
processSysex (d, numBytes, time, input, callback);
runningStatus = 0;
}
else
{
int len = 0;
uint8 data[3];
while (numBytes > 0)
{
// If there's a realtime message embedded in the middle of
// the normal message, handle it now..
if (*d >= 0xf8 && *d <= 0xfe)
{
callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time));
--numBytes;
}
else
{
if (len == 0 && *d < 0x80 && runningStatus >= 0x80)
data[len++] = runningStatus;
data[len++] = *d++;
--numBytes;
const uint8 firstByte = data[0];
if (firstByte < 0x80 || firstByte == 0xf7)
{
len = 0;
break; // ignore this malformed MIDI message..
}
if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte))
break;
}
}
if (len > 0)
{
int used = 0;
const MidiMessage m (data, len, used, 0, time);
if (used <= 0)
break; // malformed message..
jassert (used == len);
callback.handleIncomingMidiMessage (input, m);
runningStatus = data[0];
}
}
}
}
private:
template <typename UserDataType, typename CallbackType>
void processSysex (const uint8*& d, int& numBytes, double time,
UserDataType* input, CallbackType& callback)
{
if (*d == 0xf0)
{
pendingBytes = 0;
pendingDataTime = time;
}
pendingData.ensureSize ((size_t) (pendingBytes + numBytes), false);
uint8* totalMessage = static_cast<uint8*> (pendingData.getData());
uint8* dest = totalMessage + pendingBytes;
do
{
if (pendingBytes > 0 && *d >= 0x80)
{
if (*d == 0xf7)
{
*dest++ = *d++;
++pendingBytes;
--numBytes;
break;
}
if (*d >= 0xfa || *d == 0xf8)
{
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time));
++d;
--numBytes;
}
else
{
pendingBytes = 0;
int used = 0;
const MidiMessage m (d, numBytes, used, 0, time);
if (used > 0)
{
callback.handleIncomingMidiMessage (input, m);
numBytes -= used;
d += used;
}
break;
}
}
else
{
*dest++ = *d++;
++pendingBytes;
--numBytes;
}
}
while (numBytes > 0);
if (pendingBytes > 0)
{
if (totalMessage [pendingBytes - 1] == 0xf7)
{
callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingBytes, pendingDataTime));
pendingBytes = 0;
}
else
{
callback.handlePartialSysexMessage (input, totalMessage, pendingBytes, pendingDataTime);
}
}
}
MemoryBlock pendingData;
double pendingDataTime = 0;
int pendingBytes = 0;
uint8 runningStatus = 0;
JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator)
};
} // namespace juce

View File

@ -0,0 +1,503 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \
METHOD (constructor, "<init>", "(IIIIII)V") \
METHOD (getState, "getState", "()I") \
METHOD (play, "play", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (release, "release", "()V") \
METHOD (flush, "flush", "()V") \
METHOD (write, "write", "([SII)I") \
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack");
#undef JNI_CLASS_MEMBERS
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
METHOD (constructor, "<init>", "(IIIII)V") \
METHOD (getState, "getState", "()I") \
METHOD (startRecording, "startRecording", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (read, "read", "([SII)I") \
METHOD (release, "release", "()V") \
DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord");
#undef JNI_CLASS_MEMBERS
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICFIELD (SDK_INT, "SDK_INT", "I") \
DECLARE_JNI_CLASS (AndroidBuildVersion, "android/os/Build$VERSION");
#undef JNI_CLASS_MEMBERS
//==============================================================================
enum
{
CHANNEL_OUT_STEREO = 12,
CHANNEL_IN_STEREO = 12,
CHANNEL_IN_MONO = 16,
ENCODING_PCM_16BIT = 2,
STREAM_MUSIC = 3,
MODE_STREAM = 1,
STATE_UNINITIALIZED = 0
};
const char* const javaAudioTypeName = "Android Audio";
//==============================================================================
class AndroidAudioIODevice : public AudioIODevice,
public Thread
{
public:
//==============================================================================
AndroidAudioIODevice (const String& deviceName)
: AudioIODevice (deviceName, javaAudioTypeName),
Thread ("audio"),
minBufferSizeOut (0), minBufferSizeIn (0), callback (0), sampleRate (0),
numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2),
numClientOutputChannels (0), numDeviceOutputChannels (0),
actualBufferSize (0), isRunning (false),
inputChannelBuffer (1, 1),
outputChannelBuffer (1, 1)
{
JNIEnv* env = getEnv();
sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM);
minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT);
minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT);
if (minBufferSizeIn <= 0)
{
minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT);
if (minBufferSizeIn > 0)
numDeviceInputChannelsAvailable = 1;
else
numDeviceInputChannelsAvailable = 0;
}
DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; "
<< sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable);
}
~AndroidAudioIODevice()
{
close();
}
StringArray getOutputChannelNames() override
{
StringArray s;
s.add ("Left");
s.add ("Right");
return s;
}
StringArray getInputChannelNames() override
{
StringArray s;
if (numDeviceInputChannelsAvailable == 2)
{
s.add ("Left");
s.add ("Right");
}
else if (numDeviceInputChannelsAvailable == 1)
{
s.add ("Audio Input");
}
return s;
}
Array<double> getAvailableSampleRates() override
{
Array<double> r;
r.add ((double) sampleRate);
return r;
}
Array<int> getAvailableBufferSizes() override
{
Array<int> b;
int n = 16;
for (int i = 0; i < 50; ++i)
{
b.add (n);
n += n < 64 ? 16
: (n < 512 ? 32
: (n < 1024 ? 64
: (n < 2048 ? 128 : 256)));
}
return b;
}
int getDefaultBufferSize() override { return 2048; }
String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double requestedSampleRate,
int bufferSize) override
{
close();
if (sampleRate != (int) requestedSampleRate)
return "Sample rate not allowed";
lastError.clear();
int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
numDeviceInputChannels = 0;
numDeviceOutputChannels = 0;
activeOutputChans = outputChannels;
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
numClientOutputChannels = activeOutputChans.countNumberOfSetBits();
activeInputChans = inputChannels;
activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
numClientInputChannels = activeInputChans.countNumberOfSetBits();
actualBufferSize = preferredBufferSize;
inputChannelBuffer.setSize (2, actualBufferSize);
inputChannelBuffer.clear();
outputChannelBuffer.setSize (2, actualBufferSize);
outputChannelBuffer.clear();
JNIEnv* env = getEnv();
if (numClientOutputChannels > 0)
{
numDeviceOutputChannels = 2;
outputDevice = GlobalRef (env->NewObject (AudioTrack, AudioTrack.constructor,
STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT,
(jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast<int> (sizeof (int16))), MODE_STREAM));
const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24);
getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0;
int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState);
if (outputDeviceState > 0)
{
isRunning = true;
}
else
{
// failed to open the device
outputDevice.clear();
lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState);
}
}
if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0)
{
if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
{
// If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
// before trying to open an audio input device. This is not going to work!
jassertfalse;
inputDevice.clear();
lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO";
}
else
{
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable);
inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor,
0 /* (default audio source) */, sampleRate,
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO,
ENCODING_PCM_16BIT,
(jint) (minBufferSizeIn * numDeviceInputChannels * static_cast<int> (sizeof (int16)))));
int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState);
if (inputDeviceState > 0)
{
isRunning = true;
}
else
{
// failed to open the device
inputDevice.clear();
lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState);
}
}
}
if (isRunning)
{
if (outputDevice != nullptr)
env->CallVoidMethod (outputDevice, AudioTrack.play);
if (inputDevice != nullptr)
env->CallVoidMethod (inputDevice, AudioRecord.startRecording);
startThread (8);
}
else
{
closeDevices();
}
return lastError;
}
void close() override
{
if (isRunning)
{
stopThread (2000);
isRunning = false;
closeDevices();
}
}
int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; }
int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; }
bool isOpen() override { return isRunning; }
int getCurrentBufferSizeSamples() override { return actualBufferSize; }
int getCurrentBitDepth() override { return 16; }
double getCurrentSampleRate() override { return sampleRate; }
BigInteger getActiveOutputChannels() const override { return activeOutputChans; }
BigInteger getActiveInputChannels() const override { return activeInputChans; }
String getLastError() override { return lastError; }
bool isPlaying() override { return isRunning && callback != 0; }
int getXRunCount() const noexcept override
{
if (outputDevice != nullptr && getUnderrunCount != 0)
return getEnv()->CallIntMethod (outputDevice, getUnderrunCount);
return -1;
}
void start (AudioIODeviceCallback* newCallback) override
{
if (isRunning && callback != newCallback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
const ScopedLock sl (callbackLock);
callback = newCallback;
}
}
void stop() override
{
if (isRunning)
{
AudioIODeviceCallback* lastCallback;
{
const ScopedLock sl (callbackLock);
lastCallback = callback;
callback = nullptr;
}
if (lastCallback != nullptr)
lastCallback->audioDeviceStopped();
}
}
void run() override
{
JNIEnv* env = getEnv();
jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels));
while (! threadShouldExit())
{
if (inputDevice != nullptr)
{
jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels);
if (numRead < actualBufferSize * numDeviceInputChannels)
{
DBG ("Audio read under-run! " << numRead);
}
jshort* const src = env->GetShortArrayElements (audioBuffer, 0);
for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan)
{
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan));
if (chan < numDeviceInputChannels)
{
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels);
d.convertSamples (s, actualBufferSize);
}
else
{
d.clearSamples (actualBufferSize);
}
}
env->ReleaseShortArrayElements (audioBuffer, src, 0);
}
if (threadShouldExit())
break;
{
const ScopedLock sl (callbackLock);
if (callback != nullptr)
{
callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels,
outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels,
actualBufferSize);
}
else
{
outputChannelBuffer.clear();
}
}
if (outputDevice != nullptr)
{
if (threadShouldExit())
break;
jshort* const dest = env->GetShortArrayElements (audioBuffer, 0);
for (int chan = 0; chan < numDeviceOutputChannels; ++chan)
{
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels);
const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1));
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData);
d.convertSamples (s, actualBufferSize);
}
env->ReleaseShortArrayElements (audioBuffer, dest, 0);
jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels);
if (numWritten < actualBufferSize * numDeviceOutputChannels)
{
DBG ("Audio write underrun! " << numWritten);
}
}
}
}
int minBufferSizeOut, minBufferSizeIn;
private:
//==============================================================================
CriticalSection callbackLock;
AudioIODeviceCallback* callback;
jint sampleRate;
int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable;
int numClientOutputChannels, numDeviceOutputChannels;
int actualBufferSize;
bool isRunning;
String lastError;
BigInteger activeOutputChans, activeInputChans;
GlobalRef outputDevice, inputDevice;
AudioBuffer<float> inputChannelBuffer, outputChannelBuffer;
jmethodID getUnderrunCount = 0;
void closeDevices()
{
if (outputDevice != nullptr)
{
outputDevice.callVoidMethod (AudioTrack.stop);
outputDevice.callVoidMethod (AudioTrack.release);
outputDevice.clear();
}
if (inputDevice != nullptr)
{
inputDevice.callVoidMethod (AudioRecord.stop);
inputDevice.callVoidMethod (AudioRecord.release);
inputDevice.clear();
}
}
JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice)
};
//==============================================================================
class AndroidAudioIODeviceType : public AudioIODeviceType
{
public:
AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {}
//==============================================================================
void scanForDevices() {}
StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); }
int getDefaultDeviceIndex (bool) const { return 0; }
int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; }
bool hasSeparateInputsAndOutputs() const { return false; }
AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName)
{
std::unique_ptr<AndroidAudioIODevice> dev;
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
{
dev.reset (new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
: inputDeviceName));
if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0)
dev = nullptr;
}
return dev.release();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType)
};
//==============================================================================
extern bool isOboeAvailable();
extern bool isOpenSLAvailable();
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android()
{
#if JUCE_USE_ANDROID_OBOE
if (isOboeAvailable())
return nullptr;
#endif
#if JUCE_USE_ANDROID_OPENSLES
if (isOpenSLAvailable())
return nullptr;
#endif
return new AndroidAudioIODeviceType();
}
} // namespace juce

View File

@ -0,0 +1,348 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \
METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \
METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \
METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \
METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;")
DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (start, "start", "()V" )\
METHOD (stop, "stop", "()V") \
METHOD (close, "close", "()V") \
METHOD (sendMidi, "sendMidi", "([BII)V")
DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort")
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidMidiInput
{
public:
AndroidMidiInput (MidiInput* midiInput, int portIdx,
juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
: juceMidiInput (midiInput),
callback (midiInputCallback),
midiConcatenator (2048),
javaMidiDevice (getEnv()->CallObjectMethod (deviceManager,
MidiDeviceManager.openMidiInputPortWithJuceIndex,
(jint) portIdx,
(jlong) this))
{
}
~AndroidMidiInput()
{
if (jobject d = javaMidiDevice.get())
{
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
javaMidiDevice.clear();
}
}
bool isOpen() const noexcept
{
return javaMidiDevice != nullptr;
}
void start()
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d, JuceMidiPort.start);
}
void stop()
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d, JuceMidiPort.stop);
callback = nullptr;
}
void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
{
jassert (byteArray != nullptr);
jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr);
HeapBlock<uint8> buffer (static_cast<size_t> (len));
std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len));
midiConcatenator.pushMidiData (buffer.get(),
len, static_cast<double> (timestamp) * 1.0e-9,
juceMidiInput, *callback);
getEnv()->ReleaseByteArrayElements (byteArray, data, 0);
}
private:
MidiInput* juceMidiInput;
MidiInputCallback* callback;
MidiDataConcatenator midiConcatenator;
GlobalRef javaMidiDevice;
};
//==============================================================================
class AndroidMidiOutput
{
public:
AndroidMidiOutput (jobject midiDevice)
: javaMidiDevice (midiDevice)
{
}
~AndroidMidiOutput()
{
if (jobject d = javaMidiDevice.get())
{
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
javaMidiDevice.clear();
}
}
void send (jbyteArray byteArray, jint offset, jint len)
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d,
JuceMidiPort.sendMidi,
byteArray, offset, len);
}
private:
GlobalRef javaMidiDevice;
};
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive,
void, (JNIEnv* env, jobject, jlong host, jbyteArray byteArray,
jint offset, jint count, jlong timestamp))
{
// Java may create a Midi thread which JUCE doesn't know about and this callback may be
// received on this thread. Java will have already created a JNI Env for this new thread,
// which we need to tell JUCE about
setEnv (env);
reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp);
}
//==============================================================================
class AndroidMidiDeviceManager
{
public:
AndroidMidiDeviceManager()
: deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager))
{
}
String getInputPortNameForJuceIndex (int idx)
{
if (jobject dm = deviceManager.get())
{
LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx));
return juceString (string);
}
return {};
}
String getOutputPortNameForJuceIndex (int idx)
{
if (jobject dm = deviceManager.get())
{
LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx));
return juceString (string);
}
return {};
}
StringArray getDevices (bool input)
{
if (jobject dm = deviceManager.get())
{
jobjectArray jDevices
= (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices
: MidiDeviceManager.getJuceAndroidMidiOutputDevices);
// Create a local reference as converting this
// to a JUCE string will call into JNI
LocalRef<jobjectArray> devices (jDevices);
return javaStringArrayToJuce (devices);
}
return {};
}
AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
{
if (jobject dm = deviceManager.get())
{
std::unique_ptr<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm));
if (androidMidiInput->isOpen())
return androidMidiInput.release();
}
return nullptr;
}
AndroidMidiOutput* openMidiOutputPortWithIndex (int idx)
{
if (jobject dm = deviceManager.get())
if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx))
return new AndroidMidiOutput (javaMidiPort);
return nullptr;
}
private:
GlobalRef deviceManager;
};
//==============================================================================
StringArray MidiOutput::getDevices()
{
AndroidMidiDeviceManager manager;
return manager.getDevices (false);
}
int MidiOutput::getDefaultDeviceIndex()
{
return 0;
}
MidiOutput* MidiOutput::openDevice (int index)
{
if (index < 0)
return nullptr;
AndroidMidiDeviceManager manager;
String midiOutputName = manager.getOutputPortNameForJuceIndex (index);
if (midiOutputName.isEmpty())
{
// you supplied an invalid device index!
jassertfalse;
return nullptr;
}
if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index))
{
MidiOutput* retval = new MidiOutput (midiOutputName);
retval->internal = midiOutput;
return retval;
}
return nullptr;
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
delete reinterpret_cast<AndroidMidiOutput*> (internal);
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal))
{
JNIEnv* env = getEnv();
const int messageSize = message.getRawDataSize();
LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize));
jbyteArray content = messageContent.get();
jbyte* rawBytes = env->GetByteArrayElements (content, nullptr);
std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize));
env->ReleaseByteArrayElements (content, rawBytes, 0);
androidMidi->send (content, (jint) 0, (jint) messageSize);
}
}
//==============================================================================
MidiInput::MidiInput (const String& nm) : name (nm)
{
}
StringArray MidiInput::getDevices()
{
AndroidMidiDeviceManager manager;
return manager.getDevices (true);
}
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback)
{
if (index < 0)
return nullptr;
AndroidMidiDeviceManager manager;
String midiInputName (manager.getInputPortNameForJuceIndex (index));
if (midiInputName.isEmpty())
{
// you supplied an invalid device index!
jassertfalse;
return nullptr;
}
std::unique_ptr<MidiInput> midiInput (new MidiInput (midiInputName));
midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput.get(), callback);
return midiInput->internal != nullptr ? midiInput.release()
: nullptr;
}
void MidiInput::start()
{
if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
mi->start();
}
void MidiInput::stop()
{
if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal))
mi->stop();
}
MidiInput::~MidiInput()
{
delete reinterpret_cast<AndroidMidiInput*> (internal);
}
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class iOSAudioIODeviceType;
class iOSAudioIODevice : public AudioIODevice
{
public:
//==============================================================================
String open (const BigInteger&, const BigInteger&, double, int) override;
void close() override;
void start (AudioIODeviceCallback*) override;
void stop() override;
Array<double> getAvailableSampleRates() override;
Array<int> getAvailableBufferSizes() override;
bool setAudioPreprocessingEnabled (bool) override;
//==============================================================================
bool isPlaying() override;
bool isOpen() override;
String getLastError() override;
//==============================================================================
StringArray getOutputChannelNames() override;
StringArray getInputChannelNames() override;
int getDefaultBufferSize() override;
int getCurrentBufferSizeSamples() override;
double getCurrentSampleRate() override;
int getCurrentBitDepth() override;
BigInteger getActiveOutputChannels() const override;
BigInteger getActiveInputChannels() const override;
int getOutputLatencyInSamples() override;
int getInputLatencyInSamples() override;
int getXRunCount() const noexcept override;
//==============================================================================
void setMidiMessageCollector (MidiMessageCollector*);
AudioPlayHead* getAudioPlayHead() const;
//==============================================================================
bool isInterAppAudioConnected() const;
#if JUCE_MODULE_AVAILABLE_juce_graphics
Image getIcon (int size);
#endif
void switchApplication();
private:
//==============================================================================
iOSAudioIODevice (iOSAudioIODeviceType&, const String&, const String&);
//==============================================================================
friend class iOSAudioIODeviceType;
friend struct AudioSessionHolder;
struct Pimpl;
friend struct Pimpl;
std::unique_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice)
};
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,373 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class BelaAudioIODevice : public AudioIODevice
{
public:
BelaAudioIODevice() : AudioIODevice (BelaAudioIODevice::belaTypeName,
BelaAudioIODevice::belaTypeName)
{
Bela_defaultSettings (&defaultSettings);
}
~BelaAudioIODevice() {}
//==============================================================================
StringArray getOutputChannelNames() override { return { "Out #1", "Out #2" }; }
StringArray getInputChannelNames() override { return { "In #1", "In #2" }; }
Array<double> getAvailableSampleRates() override { return { 44100.0 }; }
Array<int> getAvailableBufferSizes() override { /* TODO: */ return { getDefaultBufferSize() }; }
int getDefaultBufferSize() override { return defaultSettings.periodSize; }
//==============================================================================
String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double sampleRate,
int bufferSizeSamples) override
{
if (sampleRate != 44100.0 && sampleRate != 0.0)
{
lastError = "Bela audio outputs only support 44.1 kHz sample rate";
return lastError;
}
settings = defaultSettings;
auto numIns = getNumContiguousSetBits (inputChannels);
auto numOuts = getNumContiguousSetBits (outputChannels);
settings.useAnalog = 0;
settings.useDigital = 0;
settings.numAudioInChannels = numIns;
settings.numAudioOutChannels = numOuts;
settings.detectUnderruns = 1;
settings.setup = setupCallback;
settings.render = renderCallback;
settings.cleanup = cleanupCallback;
settings.interleave = 1;
if (bufferSizeSamples > 0)
settings.periodSize = bufferSizeSamples;
isBelaOpen = false;
isRunning = false;
callback = nullptr;
underruns = 0;
if (Bela_initAudio (&settings, this) != 0 || ! isBelaOpen)
{
lastError = "Bela_initAutio failed";
return lastError;
}
actualNumberOfInputs = jmin (numIns, actualNumberOfInputs);
actualNumberOfOutputs = jmin (numOuts, actualNumberOfOutputs);
audioInBuffer.setSize (actualNumberOfInputs, actualBufferSize);
channelInBuffer.calloc (actualNumberOfInputs);
audioOutBuffer.setSize (actualNumberOfOutputs, actualBufferSize);
channelOutBuffer.calloc (actualNumberOfOutputs);
return {};
}
void close() override
{
stop();
if (isBelaOpen)
{
Bela_cleanupAudio();
isBelaOpen = false;
callback = nullptr;
underruns = 0;
actualBufferSize = 0;
actualNumberOfInputs = 0;
actualNumberOfOutputs = 0;
audioInBuffer.setSize (0, 0);
channelInBuffer.free();
audioOutBuffer.setSize (0, 0);
channelOutBuffer.free();
}
}
bool isOpen() override { return isBelaOpen; }
void start (AudioIODeviceCallback* newCallback) override
{
if (! isBelaOpen)
return;
if (isRunning)
{
if (newCallback != callback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
{
ScopedLock lock (callbackLock);
std::swap (callback, newCallback);
}
if (newCallback != nullptr)
newCallback->audioDeviceStopped();
}
}
else
{
audioInBuffer.clear();
audioOutBuffer.clear();
callback = newCallback;
isRunning = (Bela_startAudio() == 0);
if (callback != nullptr)
{
if (isRunning)
{
callback->audioDeviceAboutToStart (this);
}
else
{
lastError = "Bela_StartAudio failed";
callback->audioDeviceError (lastError);
}
}
}
}
void stop() override
{
AudioIODeviceCallback* oldCallback = nullptr;
if (callback != nullptr)
{
ScopedLock lock (callbackLock);
std::swap (callback, oldCallback);
}
isRunning = false;
Bela_stopAudio();
if (oldCallback != nullptr)
oldCallback->audioDeviceStopped();
}
bool isPlaying() override { return isRunning; }
String getLastError() override { return lastError; }
//==============================================================================
int getCurrentBufferSizeSamples() override { return actualBufferSize; }
double getCurrentSampleRate() override { return 44100.0; }
int getCurrentBitDepth() override { return 24; }
BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; }
BigInteger getActiveInputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfInputs, true); return b; }
int getOutputLatencyInSamples() override { /* TODO */ return 0; }
int getInputLatencyInSamples() override { /* TODO */ return 0; }
int getXRunCount() const noexcept { return underruns; }
//==============================================================================
static const char* const belaTypeName;
private:
//==============================================================================
bool setup (BelaContext& context)
{
actualBufferSize = context.audioFrames;
actualNumberOfInputs = context.audioInChannels;
actualNumberOfOutputs = context.audioOutChannels;
isBelaOpen = true;
firstCallback = true;
ScopedLock lock (callbackLock);
if (callback != nullptr)
callback->audioDeviceAboutToStart (this);
return true;
}
void render (BelaContext& context)
{
// check for xruns
calculateXruns (context.audioFramesElapsed, context.audioFrames);
ScopedLock lock (callbackLock);
if (callback != nullptr)
{
jassert (context.audioFrames <= actualBufferSize);
auto numSamples = jmin (context.audioFrames, actualBufferSize);
auto interleaved = ((context.flags & BELA_FLAG_INTERLEAVED) != 0);
auto numIns = jmin (actualNumberOfInputs, (int) context.audioInChannels);
auto numOuts = jmin (actualNumberOfOutputs, (int) context.audioOutChannels);
int ch;
if (interleaved && context.audioInChannels > 1)
{
for (ch = 0; ch < numIns; ++ch)
{
using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const>;
channelInBuffer[ch] = audioInBuffer.getWritePointer (ch);
DstSampleType dstData (audioInBuffer.getWritePointer (ch));
SrcSampleType srcData (context.audioIn + ch, context.audioInChannels);
dstData.convertSamples (srcData, numSamples);
}
}
else
{
for (ch = 0; ch < numIns; ++ch)
channelInBuffer[ch] = context.audioIn + (ch * numSamples);
}
for (; ch < actualNumberOfInputs; ++ch)
{
channelInBuffer[ch] = audioInBuffer.getWritePointer(ch);
zeromem (audioInBuffer.getWritePointer (ch), sizeof (float) * numSamples);
}
for (int i = 0; i < actualNumberOfOutputs; ++i)
channelOutBuffer[i] = ((interleaved && context.audioOutChannels > 1) || i >= context.audioOutChannels ? audioOutBuffer.getWritePointer (i)
: context.audioOut + (i * numSamples));
callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs,
channelOutBuffer.getData(), actualNumberOfOutputs,
numSamples);
if (interleaved && context.audioOutChannels > 1)
{
for (int i = 0; i < numOuts; ++i)
{
using DstSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst>;
using SrcSampleType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
SrcSampleType srcData (channelOutBuffer[i]);
DstSampleType dstData (context.audioOut + i, context.audioOutChannels);
dstData.convertSamples (srcData, numSamples);
}
}
}
}
void cleanup (BelaContext&)
{
ScopedLock lock (callbackLock);
if (callback != nullptr)
callback->audioDeviceStopped();
}
//==============================================================================
uint64_t expectedElapsedAudioSamples = 0;
int underruns = 0;
bool firstCallback = false;
void calculateXruns (uint64_t audioFramesElapsed, uint32_t numSamples)
{
if (audioFramesElapsed > expectedElapsedAudioSamples && ! firstCallback)
++underruns;
firstCallback = false;
expectedElapsedAudioSamples = audioFramesElapsed + numSamples;
}
//==============================================================================
static int getNumContiguousSetBits (const BigInteger& value) noexcept
{
int bit = 0;
while (value[bit])
++bit;
return bit;
}
//==============================================================================
static bool setupCallback (BelaContext* context, void* userData) noexcept { return static_cast<BelaAudioIODevice*> (userData)->setup (*context); }
static void renderCallback (BelaContext* context, void* userData) noexcept { static_cast<BelaAudioIODevice*> (userData)->render (*context); }
static void cleanupCallback (BelaContext* context, void* userData) noexcept { static_cast<BelaAudioIODevice*> (userData)->cleanup (*context); }
//==============================================================================
BelaInitSettings defaultSettings, settings;
bool isBelaOpen = false, isRunning = false;
CriticalSection callbackLock;
AudioIODeviceCallback* callback = nullptr;
String lastError;
uint32_t actualBufferSize = 0;
int actualNumberOfInputs = 0, actualNumberOfOutputs = 0;
AudioBuffer<float> audioInBuffer, audioOutBuffer;
HeapBlock<const float*> channelInBuffer;
HeapBlock<float*> channelOutBuffer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODevice)
};
const char* const BelaAudioIODevice::belaTypeName = "Bela Analog";
//==============================================================================
struct BelaAudioIODeviceType : public AudioIODeviceType
{
BelaAudioIODeviceType() : AudioIODeviceType ("Bela") {}
// TODO: support analog outputs
StringArray getDeviceNames (bool) const override { return StringArray (BelaAudioIODevice::belaTypeName); }
void scanForDevices() override {}
int getDefaultDeviceIndex (bool) const override { return 0; }
int getIndexOfDevice (AudioIODevice* device, bool) const override { return device != nullptr ? 0 : -1; }
bool hasSeparateInputsAndOutputs() const override { return false; }
AudioIODevice* createDevice (const String& outputName, const String& inputName) override
{
if (outputName == BelaAudioIODevice::belaTypeName || inputName == BelaAudioIODevice::belaTypeName)
return new BelaAudioIODevice();
return nullptr;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType)
};
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela()
{
return new BelaAudioIODeviceType();
}
} // namespace juce

View File

@ -0,0 +1,624 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
static void* juce_libjackHandle = nullptr;
static void* juce_loadJackFunction (const char* const name)
{
if (juce_libjackHandle == nullptr)
return nullptr;
return dlsym (juce_libjackHandle, name);
}
#define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \
return_type fn_name argument_types \
{ \
typedef return_type (*fn_type) argument_types; \
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \
return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \
}
#define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \
void fn_name argument_types \
{ \
typedef void (*fn_type) argument_types; \
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \
if (fn != nullptr) (*fn) arguments; \
}
//==============================================================================
JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status));
JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client));
JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client));
JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client));
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client));
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client));
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg));
JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes));
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port));
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size));
JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func));
JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg));
JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags));
JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port));
JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port));
JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg));
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id));
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port));
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name));
JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg));
#if JUCE_DEBUG
#define JACK_LOGGING_ENABLED 1
#endif
#if JACK_LOGGING_ENABLED
namespace
{
void jack_Log (const String& s)
{
std::cerr << s << std::endl;
}
const char* getJackErrorMessage (const jack_status_t status)
{
if (status & JackServerFailed
|| status & JackServerError) return "Unable to connect to JACK server";
if (status & JackVersionError) return "Client's protocol version does not match";
if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option";
if (status & JackNameNotUnique) return "The desired client name was not unique";
if (status & JackNoSuchClient) return "Requested client does not exist";
if (status & JackInitFailure) return "Unable to initialize client";
return nullptr;
}
}
#define JUCE_JACK_LOG_STATUS(x) { if (const char* m = getJackErrorMessage (x)) jack_Log (m); }
#define JUCE_JACK_LOG(x) jack_Log(x)
#else
#define JUCE_JACK_LOG_STATUS(x) {}
#define JUCE_JACK_LOG(x) {}
#endif
//==============================================================================
#ifndef JUCE_JACK_CLIENT_NAME
#define JUCE_JACK_CLIENT_NAME "JUCEJack"
#endif
struct JackPortIterator
{
JackPortIterator (jack_client_t* const client, const bool forInput)
: ports (nullptr), index (-1)
{
if (client != nullptr)
ports = juce::jack_get_ports (client, nullptr, nullptr,
forInput ? JackPortIsOutput : JackPortIsInput);
// (NB: This looks like it's the wrong way round, but it is correct!)
}
~JackPortIterator()
{
::free (ports);
}
bool next()
{
if (ports == nullptr || ports [index + 1] == nullptr)
return false;
name = CharPointer_UTF8 (ports[++index]);
clientName = name.upToFirstOccurrenceOf (":", false, false);
return true;
}
const char** ports;
int index;
String name;
String clientName;
};
class JackAudioIODeviceType;
static Array<JackAudioIODeviceType*> activeDeviceTypes;
//==============================================================================
class JackAudioIODevice : public AudioIODevice
{
public:
JackAudioIODevice (const String& deviceName,
const String& inId,
const String& outId)
: AudioIODevice (deviceName, "JACK"),
inputId (inId),
outputId (outId),
deviceIsOpen (false),
callback (nullptr),
totalNumberOfInputChannels (0),
totalNumberOfOutputChannels (0)
{
jassert (deviceName.isNotEmpty());
jack_status_t status;
client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status);
if (client == nullptr)
{
JUCE_JACK_LOG_STATUS (status);
}
else
{
juce::jack_set_error_function (errorCallback);
// open input ports
const StringArray inputChannels (getInputChannelNames());
for (int i = 0; i < inputChannels.size(); ++i)
{
String inputName;
inputName << "in_" << ++totalNumberOfInputChannels;
inputPorts.add (juce::jack_port_register (client, inputName.toUTF8(),
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0));
}
// open output ports
const StringArray outputChannels (getOutputChannelNames());
for (int i = 0; i < outputChannels.size(); ++i)
{
String outputName;
outputName << "out_" << ++totalNumberOfOutputChannels;
outputPorts.add (juce::jack_port_register (client, outputName.toUTF8(),
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0));
}
inChans.calloc (totalNumberOfInputChannels + 2);
outChans.calloc (totalNumberOfOutputChannels + 2);
}
}
~JackAudioIODevice()
{
close();
if (client != nullptr)
{
juce::jack_client_close (client);
client = nullptr;
}
}
StringArray getChannelNames (bool forInput) const
{
StringArray names;
for (JackPortIterator i (client, forInput); i.next();)
if (i.clientName == getName())
names.add (i.name.fromFirstOccurrenceOf (":", false, false));
return names;
}
StringArray getOutputChannelNames() override { return getChannelNames (false); }
StringArray getInputChannelNames() override { return getChannelNames (true); }
Array<double> getAvailableSampleRates() override
{
Array<double> rates;
if (client != nullptr)
rates.add (juce::jack_get_sample_rate (client));
return rates;
}
Array<int> getAvailableBufferSizes() override
{
Array<int> sizes;
if (client != nullptr)
sizes.add (juce::jack_get_buffer_size (client));
return sizes;
}
int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); }
int getCurrentBufferSizeSamples() override { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; }
double getCurrentSampleRate() override { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; }
String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
double /* sampleRate */, int /* bufferSizeSamples */) override
{
if (client == nullptr)
{
lastError = "No JACK client running";
return lastError;
}
lastError.clear();
close();
xruns = 0;
juce::jack_set_process_callback (client, processCallback, this);
juce::jack_set_port_connect_callback (client, portConnectCallback, this);
juce::jack_on_shutdown (client, shutdownCallback, this);
juce::jack_set_xrun_callback (client, xrunCallback, this);
juce::jack_activate (client);
deviceIsOpen = true;
if (! inputChannels.isZero())
{
for (JackPortIterator i (client, true); i.next();)
{
if (inputChannels [i.index] && i.clientName == getName())
{
int error = juce::jack_connect (client, i.ports[i.index], juce::jack_port_name ((jack_port_t*) inputPorts[i.index]));
if (error != 0)
JUCE_JACK_LOG ("Cannot connect input port " + String (i.index) + " (" + i.name + "), error " + String (error));
}
}
}
if (! outputChannels.isZero())
{
for (JackPortIterator i (client, false); i.next();)
{
if (outputChannels [i.index] && i.clientName == getName())
{
int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i.index]), i.ports[i.index]);
if (error != 0)
JUCE_JACK_LOG ("Cannot connect output port " + String (i.index) + " (" + i.name + "), error " + String (error));
}
}
}
updateActivePorts();
return lastError;
}
void close() override
{
stop();
if (client != nullptr)
{
juce::jack_deactivate (client);
juce::jack_set_xrun_callback (client, xrunCallback, nullptr);
juce::jack_set_process_callback (client, processCallback, nullptr);
juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr);
juce::jack_on_shutdown (client, shutdownCallback, nullptr);
}
deviceIsOpen = false;
}
void start (AudioIODeviceCallback* newCallback) override
{
if (deviceIsOpen && newCallback != callback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
AudioIODeviceCallback* const oldCallback = callback;
{
const ScopedLock sl (callbackLock);
callback = newCallback;
}
if (oldCallback != nullptr)
oldCallback->audioDeviceStopped();
}
}
void stop() override
{
start (nullptr);
}
bool isOpen() override { return deviceIsOpen; }
bool isPlaying() override { return callback != nullptr; }
int getCurrentBitDepth() override { return 32; }
String getLastError() override { return lastError; }
int getXRunCount() const noexcept override { return xruns; }
BigInteger getActiveOutputChannels() const override { return activeOutputChannels; }
BigInteger getActiveInputChannels() const override { return activeInputChannels; }
int getOutputLatencyInSamples() override
{
int latency = 0;
for (int i = 0; i < outputPorts.size(); i++)
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i]));
return latency;
}
int getInputLatencyInSamples() override
{
int latency = 0;
for (int i = 0; i < inputPorts.size(); i++)
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i]));
return latency;
}
String inputId, outputId;
private:
void process (const int numSamples)
{
int numActiveInChans = 0, numActiveOutChans = 0;
for (int i = 0; i < totalNumberOfInputChannels; ++i)
{
if (activeInputChannels[i])
if (jack_default_audio_sample_t* in
= (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples))
inChans [numActiveInChans++] = (float*) in;
}
for (int i = 0; i < totalNumberOfOutputChannels; ++i)
{
if (activeOutputChannels[i])
if (jack_default_audio_sample_t* out
= (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples))
outChans [numActiveOutChans++] = (float*) out;
}
const ScopedLock sl (callbackLock);
if (callback != nullptr)
{
if ((numActiveInChans + numActiveOutChans) > 0)
callback->audioDeviceIOCallback (const_cast<const float**> (inChans.getData()), numActiveInChans,
outChans, numActiveOutChans, numSamples);
}
else
{
for (int i = 0; i < numActiveOutChans; ++i)
zeromem (outChans[i], sizeof (float) * numSamples);
}
}
static int processCallback (jack_nframes_t nframes, void* callbackArgument)
{
if (callbackArgument != nullptr)
((JackAudioIODevice*) callbackArgument)->process (nframes);
return 0;
}
static int xrunCallback (void* callbackArgument)
{
if (callbackArgument != nullptr)
((JackAudioIODevice*) callbackArgument)->xruns++;
return 0;
}
void updateActivePorts()
{
BigInteger newOutputChannels, newInputChannels;
for (int i = 0; i < outputPorts.size(); ++i)
if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(i)))
newOutputChannels.setBit (i);
for (int i = 0; i < inputPorts.size(); ++i)
if (juce::jack_port_connected ((jack_port_t*) inputPorts.getUnchecked(i)))
newInputChannels.setBit (i);
if (newOutputChannels != activeOutputChannels
|| newInputChannels != activeInputChannels)
{
AudioIODeviceCallback* const oldCallback = callback;
stop();
activeOutputChannels = newOutputChannels;
activeInputChannels = newInputChannels;
if (oldCallback != nullptr)
start (oldCallback);
sendDeviceChangedCallback();
}
}
static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg)
{
if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg))
device->updateActivePorts();
}
static void threadInitCallback (void* /* callbackArgument */)
{
JUCE_JACK_LOG ("JackAudioIODevice::initialise");
}
static void shutdownCallback (void* callbackArgument)
{
JUCE_JACK_LOG ("JackAudioIODevice::shutdown");
if (JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument)
{
device->client = nullptr;
device->close();
}
}
static void errorCallback (const char* msg)
{
JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg));
}
static void sendDeviceChangedCallback();
bool deviceIsOpen;
jack_client_t* client;
String lastError;
AudioIODeviceCallback* callback;
CriticalSection callbackLock;
HeapBlock<float*> inChans, outChans;
int totalNumberOfInputChannels;
int totalNumberOfOutputChannels;
Array<void*> inputPorts, outputPorts;
BigInteger activeInputChannels, activeOutputChannels;
int xruns;
};
//==============================================================================
class JackAudioIODeviceType : public AudioIODeviceType
{
public:
JackAudioIODeviceType()
: AudioIODeviceType ("JACK"),
hasScanned (false)
{
activeDeviceTypes.add (this);
}
~JackAudioIODeviceType()
{
activeDeviceTypes.removeFirstMatchingValue (this);
}
void scanForDevices()
{
hasScanned = true;
inputNames.clear();
inputIds.clear();
outputNames.clear();
outputIds.clear();
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY);
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY);
if (juce_libjackHandle == nullptr) return;
jack_status_t status;
// open a dummy client
if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status))
{
// scan for output devices
for (JackPortIterator i (client, false); i.next();)
{
if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.clientName))
{
inputNames.add (i.clientName);
inputIds.add (i.ports [i.index]);
}
}
// scan for input devices
for (JackPortIterator i (client, true); i.next();)
{
if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.clientName))
{
outputNames.add (i.clientName);
outputIds.add (i.ports [i.index]);
}
}
juce::jack_client_close (client);
}
else
{
JUCE_JACK_LOG_STATUS (status);
}
}
StringArray getDeviceNames (bool wantInputNames) const
{
jassert (hasScanned); // need to call scanForDevices() before doing this
return wantInputNames ? inputNames : outputNames;
}
int getDefaultDeviceIndex (bool /* forInput */) const
{
jassert (hasScanned); // need to call scanForDevices() before doing this
return 0;
}
bool hasSeparateInputsAndOutputs() const { return true; }
int getIndexOfDevice (AudioIODevice* device, bool asInput) const
{
jassert (hasScanned); // need to call scanForDevices() before doing this
if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device))
return asInput ? inputIds.indexOf (d->inputId)
: outputIds.indexOf (d->outputId);
return -1;
}
AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName)
{
jassert (hasScanned); // need to call scanForDevices() before doing this
const int inputIndex = inputNames.indexOf (inputDeviceName);
const int outputIndex = outputNames.indexOf (outputDeviceName);
if (inputIndex >= 0 || outputIndex >= 0)
return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName
: inputDeviceName,
inputIds [inputIndex],
outputIds [outputIndex]);
return nullptr;
}
void portConnectionChange() { callDeviceChangeListeners(); }
private:
StringArray inputNames, outputNames, inputIds, outputIds;
bool hasScanned;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType)
};
void JackAudioIODevice::sendDeviceChangedCallback()
{
for (int i = activeDeviceTypes.size(); --i >= 0;)
if (JackAudioIODeviceType* d = activeDeviceTypes[i])
d->portConnectionChange();
}
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK()
{
return new JackAudioIODeviceType();
}
} // namespace juce

View File

@ -0,0 +1,598 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_ALSA
// You can define these strings in your app if you want to override the default names:
#ifndef JUCE_ALSA_MIDI_NAME
#define JUCE_ALSA_MIDI_NAME JUCEApplicationBase::getInstance()->getApplicationName().toUTF8()
#endif
//==============================================================================
namespace
{
//==============================================================================
class AlsaClient : public ReferenceCountedObject
{
public:
using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
//==============================================================================
// represents an input or output port of the supplied AlsaClient
struct Port
{
Port (AlsaClient& c, bool forInput) noexcept
: client (c), isInput (forInput)
{}
~Port()
{
if (isValid())
{
if (isInput)
enableCallback (false);
else
snd_midi_event_free (midiParser);
snd_seq_delete_simple_port (client.get(), portId);
}
}
void connectWith (int sourceClient, int sourcePort) const noexcept
{
if (isInput)
snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort);
else
snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort);
}
bool isValid() const noexcept
{
return client.get() != nullptr && portId >= 0;
}
void setupInput (MidiInput* input, MidiInputCallback* cb)
{
jassert (cb != nullptr && input != nullptr);
callback = cb;
midiInput = input;
}
void setupOutput()
{
jassert (! isInput);
snd_midi_event_new ((size_t) maxEventSize, &midiParser);
}
void enableCallback (bool enable)
{
if (callbackEnabled != enable)
{
callbackEnabled = enable;
if (enable)
client.registerCallback();
else
client.unregisterCallback();
}
}
bool sendMessageNow (const MidiMessage& message)
{
if (message.getRawDataSize() > maxEventSize)
{
maxEventSize = message.getRawDataSize();
snd_midi_event_free (midiParser);
snd_midi_event_new ((size_t) maxEventSize, &midiParser);
}
snd_seq_event_t event;
snd_seq_ev_clear (&event);
auto numBytes = (long) message.getRawDataSize();
const uint8* data = message.getRawData();
auto* seqHandle = client.get();
bool success = true;
while (numBytes > 0)
{
auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
if (numSent <= 0)
{
success = numSent == 0;
break;
}
numBytes -= numSent;
data += numSent;
snd_seq_ev_set_source (&event, (unsigned char) portId);
snd_seq_ev_set_subs (&event);
snd_seq_ev_set_direct (&event);
if (snd_seq_event_output_direct (seqHandle, &event) < 0)
{
success = false;
break;
}
}
snd_midi_event_reset_encode (midiParser);
return success;
}
bool operator== (const Port& lhs) const noexcept
{
return portId != -1 && portId == lhs.portId;
}
void createPort (const String& name, bool enableSubscription)
{
if (auto* seqHandle = client.get())
{
const unsigned int caps =
isInput
? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
: (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
portId = snd_seq_create_simple_port (seqHandle, name.toUTF8(), caps,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION);
}
}
void handleIncomingMidiMessage (const MidiMessage& message) const
{
callback->handleIncomingMidiMessage (midiInput, message);
}
void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
{
callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
}
AlsaClient& client;
MidiInputCallback* callback = nullptr;
snd_midi_event_t* midiParser = nullptr;
MidiInput* midiInput = nullptr;
int maxEventSize = 4096;
int portId = -1;
bool callbackEnabled = false;
bool isInput = false;
};
static Ptr getInstance()
{
if (instance == nullptr)
instance = new AlsaClient();
return instance;
}
void registerCallback()
{
if (inputThread == nullptr)
inputThread.reset (new MidiInputThread (*this));
if (++activeCallbacks == 1)
inputThread->startThread();
}
void unregisterCallback()
{
jassert (activeCallbacks.get() > 0);
if (--activeCallbacks == 0 && inputThread->isThreadRunning())
inputThread->signalThreadShouldExit();
}
void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
{
if (event->dest.port < ports.size() && ports[event->dest.port]->callbackEnabled)
ports[event->dest.port]->handleIncomingMidiMessage (message);
}
void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
{
if (event->dest.port < ports.size()
&& ports[event->dest.port]->callbackEnabled)
ports[event->dest.port]->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
}
snd_seq_t* get() const noexcept { return handle; }
int getId() const noexcept { return clientId; }
Port* createPort (const String& name, bool forInput, bool enableSubscription)
{
auto port = new Port (*this, forInput);
port->createPort (name, enableSubscription);
ports.set (port->portId, port);
incReferenceCount();
return port;
}
void deletePort (Port* port)
{
ports.remove (port->portId);
decReferenceCount();
}
private:
snd_seq_t* handle = nullptr;
int clientId = 0;
OwnedArray<Port> ports;
Atomic<int> activeCallbacks;
CriticalSection callbackLock;
static AlsaClient* instance;
//==============================================================================
friend class ReferenceCountedObjectPtr<AlsaClient>;
friend struct ContainerDeletePolicy<AlsaClient>;
AlsaClient()
{
jassert (instance == nullptr);
snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
snd_seq_set_client_name (handle, JUCE_ALSA_MIDI_NAME);
clientId = snd_seq_client_id(handle);
// It's good idea to pre-allocate a good number of elements
ports.ensureStorageAllocated (32);
}
~AlsaClient()
{
jassert (instance != nullptr);
instance = nullptr;
if (handle != nullptr)
snd_seq_close (handle);
jassert (activeCallbacks.get() == 0);
if (inputThread)
inputThread->stopThread (3000);
}
//==============================================================================
class MidiInputThread : public Thread
{
public:
MidiInputThread (AlsaClient& c)
: Thread ("JUCE MIDI Input"), client (c)
{
jassert (client.get() != nullptr);
}
void run() override
{
const int maxEventSize = 16 * 1024;
snd_midi_event_t* midiParser;
snd_seq_t* seqHandle = client.get();
if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
{
auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
HeapBlock<pollfd> pfd (numPfds);
snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN);
HeapBlock<uint8> buffer (maxEventSize);
while (! threadShouldExit())
{
if (poll (pfd, (nfds_t) numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call
{
if (threadShouldExit())
break;
do
{
snd_seq_event_t* inputEvent = nullptr;
if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
{
// xxx what about SYSEXes that are too big for the buffer?
const long numBytes = snd_midi_event_decode (midiParser, buffer,
maxEventSize, inputEvent);
snd_midi_event_reset_decode (midiParser);
concatenator.pushMidiData (buffer, (int) numBytes,
Time::getMillisecondCounter() * 0.001,
inputEvent, client);
snd_seq_free_event (inputEvent);
}
}
while (snd_seq_event_input_pending (seqHandle, 0) > 0);
}
}
snd_midi_event_free (midiParser);
}
}
private:
AlsaClient& client;
MidiDataConcatenator concatenator { 2048 };
};
std::unique_ptr<MidiInputThread> inputThread;
};
AlsaClient* AlsaClient::instance = nullptr;
//==============================================================================
static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
snd_seq_client_info_t* clientInfo,
bool forInput,
StringArray& deviceNamesFound,
int deviceIndexToOpen)
{
AlsaClient::Port* port = nullptr;
snd_seq_t* seqHandle = client->get();
snd_seq_port_info_t* portInfo = nullptr;
snd_seq_port_info_alloca (&portInfo);
jassert (portInfo);
auto numPorts = snd_seq_client_info_get_num_ports (clientInfo);
auto sourceClient = snd_seq_client_info_get_client (clientInfo);
snd_seq_port_info_set_client (portInfo, sourceClient);
snd_seq_port_info_set_port (portInfo, -1);
while (--numPorts >= 0)
{
if (snd_seq_query_next_port (seqHandle, portInfo) == 0
&& (snd_seq_port_info_get_capability (portInfo)
& (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0)
{
String portName = snd_seq_port_info_get_name(portInfo);
deviceNamesFound.add (portName);
if (deviceNamesFound.size() == deviceIndexToOpen + 1)
{
auto sourcePort = snd_seq_port_info_get_port (portInfo);
if (sourcePort != -1)
{
port = client->createPort (portName, forInput, false);
jassert (port->isValid());
port->connectWith (sourceClient, sourcePort);
break;
}
}
}
}
return port;
}
static AlsaClient::Port* iterateMidiDevices (bool forInput,
StringArray& deviceNamesFound,
int deviceIndexToOpen)
{
AlsaClient::Port* port = nullptr;
auto client = AlsaClient::getInstance();
if (auto* seqHandle = client->get())
{
snd_seq_system_info_t* systemInfo = nullptr;
snd_seq_client_info_t* clientInfo = nullptr;
snd_seq_system_info_alloca (&systemInfo);
jassert (systemInfo != nullptr);
if (snd_seq_system_info (seqHandle, systemInfo) == 0)
{
snd_seq_client_info_alloca (&clientInfo);
jassert (clientInfo != nullptr);
auto numClients = snd_seq_system_info_get_cur_clients (systemInfo);
while (--numClients >= 0)
{
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
{
auto sourceClient = snd_seq_client_info_get_client (clientInfo);
if (sourceClient != client->getId() && sourceClient != SND_SEQ_CLIENT_SYSTEM)
{
port = iterateMidiClient (client, clientInfo, forInput,
deviceNamesFound, deviceIndexToOpen);
if (port != nullptr)
break;
}
}
}
}
}
deviceNamesFound.appendNumbersToDuplicates (true, true);
return port;
}
} // namespace
StringArray MidiOutput::getDevices()
{
StringArray devices;
iterateMidiDevices (false, devices, -1);
return devices;
}
int MidiOutput::getDefaultDeviceIndex()
{
return 0;
}
MidiOutput* MidiOutput::openDevice (int deviceIndex)
{
MidiOutput* newDevice = nullptr;
StringArray devices;
auto* port = iterateMidiDevices (false, devices, deviceIndex);
if (port == nullptr)
return nullptr;
jassert (port->isValid());
newDevice = new MidiOutput (devices [deviceIndex]);
port->setupOutput();
newDevice->internal = port;
return newDevice;
}
MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
{
MidiOutput* newDevice = nullptr;
auto client = AlsaClient::getInstance();
auto* port = client->createPort (deviceName, false, true);
jassert (port != nullptr && port->isValid());
newDevice = new MidiOutput (deviceName);
port->setupOutput();
newDevice->internal = port;
return newDevice;
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal));
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message);
}
//==============================================================================
MidiInput::MidiInput (const String& nm)
: name (nm), internal (nullptr)
{
}
MidiInput::~MidiInput()
{
stop();
AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal));
}
void MidiInput::start()
{
static_cast<AlsaClient::Port*> (internal)->enableCallback (true);
}
void MidiInput::stop()
{
static_cast<AlsaClient::Port*> (internal)->enableCallback (false);
}
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
StringArray MidiInput::getDevices()
{
StringArray devices;
iterateMidiDevices (true, devices, -1);
return devices;
}
MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
{
MidiInput* newDevice = nullptr;
StringArray devices;
auto* port = iterateMidiDevices (true, devices, deviceIndex);
if (port == nullptr)
return nullptr;
jassert (port->isValid());
newDevice = new MidiInput (devices [deviceIndex]);
port->setupInput (newDevice, callback);
newDevice->internal = port;
return newDevice;
}
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
{
MidiInput* newDevice = nullptr;
auto client = AlsaClient::getInstance();
auto* port = client->createPort (deviceName, true, true);
jassert (port->isValid());
newDevice = new MidiInput (deviceName);
port->setupInput (newDevice, callback);
newDevice->internal = port;
return newDevice;
}
//==============================================================================
#else
// (These are just stub functions if ALSA is unavailable...)
StringArray MidiOutput::getDevices() { return {}; }
int MidiOutput::getDefaultDeviceIndex() { return 0; }
MidiOutput* MidiOutput::openDevice (int) { return nullptr; }
MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; }
MidiOutput::~MidiOutput() {}
void MidiOutput::sendMessageNow (const MidiMessage&) {}
MidiInput::MidiInput (const String& nm) : name (nm), internal (nullptr) {}
MidiInput::~MidiInput() {}
void MidiInput::start() {}
void MidiInput::stop() {}
int MidiInput::getDefaultDeviceIndex() { return 0; }
StringArray MidiInput::getDevices() { return {}; }
MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; }
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; }
#endif
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,594 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. 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 permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#ifndef JUCE_LOG_COREMIDI_ERRORS
#define JUCE_LOG_COREMIDI_ERRORS 1
#endif
namespace CoreMidiHelpers
{
static bool checkError (OSStatus err, int lineNum)
{
if (err == noErr)
return true;
#if JUCE_LOG_COREMIDI_ERRORS
Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err));
#endif
ignoreUnused (lineNum);
return false;
}
#undef CHECK_ERROR
#define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__)
//==============================================================================
struct ScopedCFString
{
ScopedCFString() noexcept {}
~ScopedCFString() noexcept { if (cfString != nullptr) CFRelease (cfString); }
CFStringRef cfString = {};
};
static String getMidiObjectName (MIDIObjectRef entity)
{
String result;
CFStringRef str = nullptr;
MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str);
if (str != nullptr)
{
result = String::fromCFString (str);
CFRelease (str);
}
return result;
}
static void enableSimulatorMidiSession()
{
#if TARGET_OS_SIMULATOR
static bool hasEnabledNetworkSession = false;
if (! hasEnabledNetworkSession)
{
MIDINetworkSession* session = [MIDINetworkSession defaultSession];
session.enabled = YES;
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone;
hasEnabledNetworkSession = true;
}
#endif
}
static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal)
{
auto result = getMidiObjectName (endpoint);
MIDIEntityRef entity = 0; // NB: don't attempt to use nullptr for refs - it fails in some types of build.
MIDIEndpointGetEntity (endpoint, &entity);
if (entity == 0)
return result; // probably virtual
if (result.isEmpty())
result = getMidiObjectName (entity); // endpoint name is empty - try the entity
// now consider the device's name
MIDIDeviceRef device = 0;
MIDIEntityGetDevice (entity, &device);
if (device != 0)
{
auto deviceName = getMidiObjectName (device);
if (deviceName.isNotEmpty())
{
// if an external device has only one entity, throw away
// the endpoint name and just use the device name
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
{
result = deviceName;
}
else if (! result.startsWithIgnoreCase (deviceName))
{
// prepend the device name to the entity name
result = (deviceName + " " + result).trimEnd();
}
}
}
return result;
}
static String getConnectedEndpointName (MIDIEndpointRef endpoint)
{
String result;
// Does the endpoint have connections?
CFDataRef connections = nullptr;
int numConnections = 0;
MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections);
if (connections != nullptr)
{
numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID);
if (numConnections > 0)
{
auto pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections));
for (int i = 0; i < numConnections; ++i, ++pid)
{
auto uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid);
MIDIObjectRef connObject;
MIDIObjectType connObjectType;
auto err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType);
if (err == noErr)
{
String s;
if (connObjectType == kMIDIObjectType_ExternalSource
|| connObjectType == kMIDIObjectType_ExternalDestination)
{
// Connected to an external device's endpoint (10.3 and later).
s = getEndpointName (static_cast<MIDIEndpointRef> (connObject), true);
}
else
{
// Connected to an external device (10.2) (or something else, catch-all)
s = getMidiObjectName (connObject);
}
if (s.isNotEmpty())
{
if (result.isNotEmpty())
result += ", ";
result += s;
}
}
}
}
CFRelease (connections);
}
if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them.
result = getEndpointName (endpoint, false);
return result;
}
static void setUniqueIdForMidiPort (MIDIObjectRef device, const String& portName, bool isInput)
{
String portUniqueId;
#if defined (JucePlugin_CFBundleIdentifier)
portUniqueId = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier);
#else
auto appBundle = File::getSpecialLocation (File::currentApplicationFile);
if (auto bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, appBundle.getFullPathName().toCFString(),
kCFURLPOSIXPathStyle, true))
{
auto bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL);
CFRelease (bundleURL);
if (bundleRef != nullptr)
{
if (auto bundleId = CFBundleGetIdentifier (bundleRef))
portUniqueId = String::fromCFString (bundleId);
CFRelease (bundleRef);
}
}
#endif
if (portUniqueId.isNotEmpty())
{
portUniqueId += "." + portName + (isInput ? ".input" : ".output");
CHECK_ERROR (MIDIObjectSetStringProperty (device, kMIDIPropertyUniqueID, portUniqueId.toCFString()));
}
}
static StringArray findDevices (bool forInput)
{
// It seems that OSX can be a bit picky about the thread that's first used to
// search for devices. It's safest to use the message thread for calling this.
jassert (MessageManager::getInstance()->isThisTheMessageThread());
StringArray s;
enableSimulatorMidiSession();
auto num = forInput ? MIDIGetNumberOfSources()
: MIDIGetNumberOfDestinations();
for (ItemCount i = 0; i < num; ++i)
{
String name;
if (auto dest = forInput ? MIDIGetSource (i)
: MIDIGetDestination (i))
name = getConnectedEndpointName (dest);
if (name.isEmpty())
name = "<error>";
s.add (name);
}
return s;
}
static void globalSystemChangeCallback (const MIDINotification*, void*)
{
// TODO.. Should pass-on this notification..
}
static String getGlobalMidiClientName()
{
if (auto* app = JUCEApplicationBase::getInstance())
return app->getApplicationName();
return "JUCE";
}
static MIDIClientRef getGlobalMidiClient()
{
static MIDIClientRef globalMidiClient = 0;
if (globalMidiClient == 0)
{
// Since OSX 10.6, the MIDIClientCreate function will only work
// correctly when called from the message thread!
jassert (MessageManager::getInstance()->isThisTheMessageThread());
enableSimulatorMidiSession();
CoreMidiHelpers::ScopedCFString name;
name.cfString = getGlobalMidiClientName().toCFString();
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient));
}
return globalMidiClient;
}
//==============================================================================
class MidiPortAndEndpoint
{
public:
MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept
: port (p), endPoint (ep)
{
}
~MidiPortAndEndpoint() noexcept
{
if (port != 0)
MIDIPortDispose (port);
if (port == 0 && endPoint != 0) // if port == nullptr, it means we created the endpoint, so it's safe to delete it
MIDIEndpointDispose (endPoint);
}
void send (const MIDIPacketList* packets) noexcept
{
if (port != 0)
MIDISend (port, endPoint, packets);
else
MIDIReceived (endPoint, packets);
}
MIDIPortRef port;
MIDIEndpointRef endPoint;
};
//==============================================================================
struct MidiPortAndCallback;
CriticalSection callbackLock;
Array<MidiPortAndCallback*> activeCallbacks;
struct MidiPortAndCallback
{
MidiPortAndCallback (MidiInputCallback& cb) : callback (cb) {}
~MidiPortAndCallback()
{
active = false;
{
const ScopedLock sl (callbackLock);
activeCallbacks.removeFirstMatchingValue (this);
}
if (portAndEndpoint != nullptr && portAndEndpoint->port != 0)
CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint));
}
void handlePackets (const MIDIPacketList* pktlist)
{
auto time = Time::getMillisecondCounterHiRes() * 0.001;
const ScopedLock sl (callbackLock);
if (activeCallbacks.contains (this) && active)
{
auto* packet = &pktlist->packet[0];
for (unsigned int i = 0; i < pktlist->numPackets; ++i)
{
concatenator.pushMidiData (packet->data, (int) packet->length, time,
input, callback);
packet = MIDIPacketNext (packet);
}
}
}
MidiInput* input = nullptr;
std::unique_ptr<MidiPortAndEndpoint> portAndEndpoint;
std::atomic<bool> active { false };
private:
MidiInputCallback& callback;
MidiDataConcatenator concatenator { 2048 };
};
static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/)
{
static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist);
}
}
//==============================================================================
StringArray MidiOutput::getDevices() { return CoreMidiHelpers::findDevices (false); }
int MidiOutput::getDefaultDeviceIndex() { return 0; }
MidiOutput* MidiOutput::openDevice (int index)
{
MidiOutput* mo = nullptr;
if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations()))
{
auto endPoint = MIDIGetDestination ((ItemCount) index);
CoreMidiHelpers::ScopedCFString pname;
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
{
auto client = CoreMidiHelpers::getGlobalMidiClient();
MIDIPortRef port;
auto deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint);
if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port)))
{
mo = new MidiOutput (deviceName);
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
}
}
}
return mo;
}
MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
{
MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient();
MIDIEndpointRef endPoint;
CoreMidiHelpers::ScopedCFString name;
name.cfString = deviceName.toCFString();
if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint)))
{
CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false);
auto mo = new MidiOutput (deviceName);
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
return mo;
}
return nullptr;
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal);
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
#if JUCE_IOS
const MIDITimeStamp timeStamp = mach_absolute_time();
#else
const MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
#endif
HeapBlock<MIDIPacketList> allocatedPackets;
MIDIPacketList stackPacket;
auto* packetToSend = &stackPacket;
auto dataSize = (size_t) message.getRawDataSize();
if (message.isSysEx())
{
const int maxPacketSize = 256;
int pos = 0, bytesLeft = (int) dataSize;
const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize;
allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1);
packetToSend = allocatedPackets;
packetToSend->numPackets = (UInt32) numPackets;
auto* p = packetToSend->packet;
for (int i = 0; i < numPackets; ++i)
{
p->timeStamp = timeStamp;
p->length = (UInt16) jmin (maxPacketSize, bytesLeft);
memcpy (p->data, message.getRawData() + pos, p->length);
pos += p->length;
bytesLeft -= p->length;
p = MIDIPacketNext (p);
}
}
else if (dataSize < 65536) // max packet size
{
auto stackCapacity = sizeof (stackPacket.packet->data);
if (dataSize > stackCapacity)
{
allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1);
packetToSend = allocatedPackets;
}
packetToSend->numPackets = 1;
auto& p = *(packetToSend->packet);
p.timeStamp = timeStamp;
p.length = (UInt16) dataSize;
memcpy (p.data, message.getRawData(), dataSize);
}
else
{
jassertfalse; // packet too large to send!
return;
}
static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend);
}
//==============================================================================
StringArray MidiInput::getDevices() { return CoreMidiHelpers::findDevices (true); }
int MidiInput::getDefaultDeviceIndex() { return 0; }
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
{
jassert (callback != nullptr);
using namespace CoreMidiHelpers;
MidiInput* newInput = nullptr;
if (isPositiveAndBelow (index, MIDIGetNumberOfSources()))
{
if (auto endPoint = MIDIGetSource ((ItemCount) index))
{
ScopedCFString name;
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString)))
{
if (auto client = getGlobalMidiClient())
{
MIDIPortRef port;
std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback));
if (CHECK_ERROR (MIDIInputPortCreate (client, name.cfString, midiInputProc, mpc.get(), &port)))
{
if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr)))
{
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (port, endPoint));
newInput = new MidiInput (getDevices() [index]);
mpc->input = newInput;
newInput->internal = mpc.get();
const ScopedLock sl (callbackLock);
activeCallbacks.add (mpc.release());
}
else
{
CHECK_ERROR (MIDIPortDispose (port));
}
}
}
}
}
}
return newInput;
}
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
{
jassert (callback != nullptr);
using namespace CoreMidiHelpers;
if (auto client = getGlobalMidiClient())
{
std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback));
mpc->active = false;
MIDIEndpointRef endPoint;
ScopedCFString name;
name.cfString = deviceName.toCFString();
if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endPoint)))
{
setUniqueIdForMidiPort (endPoint, deviceName, true);
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (0, endPoint));
auto mi = new MidiInput (deviceName);
mpc->input = mi;
mi->internal = mpc.get();
const ScopedLock sl (callbackLock);
activeCallbacks.add (mpc.release());
return mi;
}
}
return nullptr;
}
MidiInput::MidiInput (const String& nm) : name (nm)
{
}
MidiInput::~MidiInput()
{
delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal);
}
void MidiInput::start()
{
const ScopedLock sl (CoreMidiHelpers::callbackLock);
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true;
}
void MidiInput::stop()
{
const ScopedLock sl (CoreMidiHelpers::callbackLock);
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false;
}
#undef CHECK_ERROR
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff