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:
193
modules/juce_audio_devices/native/juce_MidiDataConcatenator.h
Normal file
193
modules/juce_audio_devices/native/juce_MidiDataConcatenator.h
Normal 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
|
503
modules/juce_audio_devices/native/juce_android_Audio.cpp
Normal file
503
modules/juce_audio_devices/native/juce_android_Audio.cpp
Normal 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
|
348
modules/juce_audio_devices/native/juce_android_Midi.cpp
Normal file
348
modules/juce_audio_devices/native/juce_android_Midi.cpp
Normal 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
|
1490
modules/juce_audio_devices/native/juce_android_Oboe.cpp
Normal file
1490
modules/juce_audio_devices/native/juce_android_Oboe.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1379
modules/juce_audio_devices/native/juce_android_OpenSL.cpp
Normal file
1379
modules/juce_audio_devices/native/juce_android_OpenSL.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1444
modules/juce_audio_devices/native/juce_ios_Audio.cpp
Normal file
1444
modules/juce_audio_devices/native/juce_ios_Audio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
93
modules/juce_audio_devices/native/juce_ios_Audio.h
Normal file
93
modules/juce_audio_devices/native/juce_ios_Audio.h
Normal 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
|
1307
modules/juce_audio_devices/native/juce_linux_ALSA.cpp
Normal file
1307
modules/juce_audio_devices/native/juce_linux_ALSA.cpp
Normal file
File diff suppressed because it is too large
Load Diff
373
modules/juce_audio_devices/native/juce_linux_Bela.cpp
Normal file
373
modules/juce_audio_devices/native/juce_linux_Bela.cpp
Normal 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
|
624
modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
Normal file
624
modules/juce_audio_devices/native/juce_linux_JackAudio.cpp
Normal 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
|
598
modules/juce_audio_devices/native/juce_linux_Midi.cpp
Normal file
598
modules/juce_audio_devices/native/juce_linux_Midi.cpp
Normal 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
|
2246
modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp
Normal file
2246
modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp
Normal file
File diff suppressed because it is too large
Load Diff
594
modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp
Normal file
594
modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp
Normal 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
|
1607
modules/juce_audio_devices/native/juce_win32_ASIO.cpp
Normal file
1607
modules/juce_audio_devices/native/juce_win32_ASIO.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1292
modules/juce_audio_devices/native/juce_win32_DirectSound.cpp
Normal file
1292
modules/juce_audio_devices/native/juce_win32_DirectSound.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1318
modules/juce_audio_devices/native/juce_win32_Midi.cpp
Normal file
1318
modules/juce_audio_devices/native/juce_win32_Midi.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1723
modules/juce_audio_devices/native/juce_win32_WASAPI.cpp
Normal file
1723
modules/juce_audio_devices/native/juce_win32_WASAPI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user