1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Redesigned CoreAudio for better handling of separate in/out devices.

This commit is contained in:
jules 2013-12-18 22:48:26 +00:00
parent 374b389890
commit dcb7e61ed5
2 changed files with 709 additions and 129 deletions

View file

@ -190,8 +190,7 @@ private:
testSound.clear();
float* s = testSound.getSampleData (0, 0);
Random rand (0);
rand.setSeedRandomly();
Random rand;
for (int i = 0; i < length; ++i)
s[i] = (rand.nextFloat() - rand.nextFloat() + rand.nextFloat() - rand.nextFloat()) * 0.06f;

View file

@ -140,7 +140,7 @@ class CoreAudioIODevice;
class CoreAudioInternal : private Timer
{
public:
CoreAudioInternal (CoreAudioIODevice& d, AudioDeviceID id, bool isSlave)
CoreAudioInternal (CoreAudioIODevice& d, AudioDeviceID id)
: owner (d),
inputLatency (0),
outputLatency (0),
@ -148,7 +148,6 @@ public:
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
audioProcID (0),
#endif
isSlaveDevice (isSlave),
deviceID (id),
started (false),
sampleRate (0),
@ -232,15 +231,13 @@ public:
for (unsigned int j = 0; j < b.mNumberChannels; ++j)
{
String name;
NSString* nameNSString = nil;
size = sizeof (nameNSString);
if (AudioDeviceGetProperty (deviceID, chanNum + 1, input, kAudioDevicePropertyChannelNameCFString, &size, &nameNSString) == noErr)
{
char channelName [256] = { 0 };
UInt32 nameSize = sizeof (channelName);
UInt32 channelNum = (UInt32) chanNum + 1;
pa.mSelector = kAudioDevicePropertyChannelName;
if (AudioObjectGetPropertyData (deviceID, &pa, sizeof (channelNum), &channelNum, &nameSize, channelName) == noErr)
name = String::fromUTF8 (channelName, (int) nameSize);
name = nsStringToJuce (nameNSString);
[nameNSString release];
}
if ((input ? activeInputChans : activeOutputChans) [chanNum])
@ -436,10 +433,7 @@ public:
pa.mElement = kAudioObjectPropertyElementMaster;
if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &transSize, &avt)))
{
DBG (buffer);
s.add (buffer);
}
}
return s;
@ -557,9 +551,6 @@ public:
error = "Device has no available sample-rates";
else if (bufferSizes.size() == 0)
error = "Device has no available buffer-sizes";
else if (inputDevice != nullptr)
error = inputDevice->reopen (inputChannels, outputChannels,
newSampleRate, bufferSizeSamples);
}
}
@ -569,9 +560,6 @@ public:
bool start()
{
if (inputDevice != nullptr && ! inputDevice->start())
return false;
if (! started)
{
callback = nullptr;
@ -606,18 +594,8 @@ public:
void setCallback (AudioIODeviceCallback* cb)
{
if (inputDevice == nullptr)
{
const ScopedLock sl (callbackLock);
callback = cb;
}
else
{
const ScopedLock sl1 (callbackLock);
const ScopedLock sl2 (inputDevice->callbackLock);
callback = cb;
inputDevice->callback = cb;
}
const ScopedLock sl (callbackLock);
callback = cb;
}
void stop (bool leaveInterruptRunning)
@ -665,9 +643,6 @@ public:
const ScopedLock sl (callbackLock);
}
if (inputDevice != nullptr)
inputDevice->stop (leaveInterruptRunning);
}
double getSampleRate() const { return sampleRate; }
@ -680,68 +655,44 @@ public:
if (callback != nullptr)
{
if (inputDevice == 0)
for (int i = numInputChans; --i >= 0;)
{
for (int i = numInputChans; --i >= 0;)
{
const CallbackDetailsForChannel& info = inputChannelInfo.getReference(i);
float* dest = tempInputBuffers [i];
const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData)
+ info.dataOffsetSamples;
const int stride = info.dataStrideSamples;
const CallbackDetailsForChannel& info = inputChannelInfo.getReference(i);
float* dest = tempInputBuffers [i];
const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData)
+ info.dataOffsetSamples;
const int stride = info.dataStrideSamples;
if (stride != 0) // if this is zero, info is invalid
if (stride != 0) // if this is zero, info is invalid
{
for (int j = bufferSize; --j >= 0;)
{
for (int j = bufferSize; --j >= 0;)
{
*dest++ = *src;
src += stride;
}
*dest++ = *src;
src += stride;
}
}
}
if (! isSlaveDevice)
callback->audioDeviceIOCallback (const_cast<const float**> (tempInputBuffers.getData()),
numInputChans,
tempOutputBuffers,
numOutputChans,
bufferSize);
for (int i = numOutputChans; --i >= 0;)
{
if (inputDevice == 0)
const CallbackDetailsForChannel& info = outputChannelInfo.getReference(i);
const float* src = tempOutputBuffers [i];
float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData)
+ info.dataOffsetSamples;
const int stride = info.dataStrideSamples;
if (stride != 0) // if this is zero, info is invalid
{
callback->audioDeviceIOCallback (const_cast<const float**> (tempInputBuffers.getData()),
numInputChans,
tempOutputBuffers,
numOutputChans,
bufferSize);
}
else
{
jassert (inputDevice->bufferSize == bufferSize);
// Sometimes the two linked devices seem to get their callbacks in
// parallel, so we need to lock both devices to stop the input data being
// changed while inside our callback..
const ScopedLock sl2 (inputDevice->callbackLock);
callback->audioDeviceIOCallback (const_cast<const float**> (inputDevice->tempInputBuffers.getData()),
inputDevice->numInputChans,
tempOutputBuffers,
numOutputChans,
bufferSize);
}
for (int i = numOutputChans; --i >= 0;)
{
const CallbackDetailsForChannel& info = outputChannelInfo.getReference(i);
const float* src = tempOutputBuffers [i];
float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData)
+ info.dataOffsetSamples;
const int stride = info.dataStrideSamples;
if (stride != 0) // if this is zero, info is invalid
for (int j = bufferSize; --j >= 0;)
{
for (int j = bufferSize; --j >= 0;)
{
*dest = *src++;
dest += stride;
}
*dest = *src++;
dest += stride;
}
}
}
@ -786,9 +737,6 @@ public:
AudioDeviceIOProcID audioProcID;
#endif
ScopedPointer<CoreAudioInternal> inputDevice;
bool isSlaveDevice;
private:
CriticalSection callbackLock;
AudioDeviceID deviceID;
@ -899,15 +847,11 @@ public:
if (outputDeviceId == 0 || outputDeviceId == inputDeviceId)
{
jassert (inputDeviceId != 0);
device = new CoreAudioInternal (*this, inputDeviceId, false);
device = new CoreAudioInternal (*this, inputDeviceId);
}
else
{
device = new CoreAudioInternal (*this, outputDeviceId, false);
if (inputDeviceId != 0)
device->inputDevice = new CoreAudioInternal (*this, inputDeviceId, true);
device = new CoreAudioInternal (*this, outputDeviceId);
}
internal = device;
@ -933,18 +877,8 @@ public:
AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal);
}
StringArray getOutputChannelNames() override
{
return internal->outChanNames;
}
StringArray getInputChannelNames() override
{
if (internal->inputDevice != nullptr)
return internal->inputDevice->inChanNames;
return internal->inChanNames;
}
StringArray getOutputChannelNames() override { return internal->outChanNames; }
StringArray getInputChannelNames() override { return internal->inChanNames; }
bool isOpen() override { return isOpen_; }
@ -1000,32 +934,20 @@ public:
start (oldCallback);
}
BigInteger getActiveOutputChannels() const override
{
return internal->activeOutputChans;
}
BigInteger getActiveInputChannels() const override
{
BigInteger chans (internal->activeInputChans);
if (internal->inputDevice != nullptr)
chans |= internal->inputDevice->activeInputChans;
return chans;
}
BigInteger getActiveOutputChannels() const override { return internal->activeOutputChans; }
BigInteger getActiveInputChannels() const override { return internal->activeInputChans; }
int getOutputLatencyInSamples() override
{
// this seems like a good guess at getting the latency right - comparing
// this with a round-trip measurement, it gets it to within a few millisecs
// for the built-in mac soundcard
return internal->outputLatency + internal->getBufferSize() * 2;
return internal->outputLatency;
}
int getInputLatencyInSamples() override
{
return internal->inputLatency + internal->getBufferSize() * 2;
return internal->inputLatency;
}
void start (AudioIODeviceCallback* callback) override
@ -1096,6 +1018,643 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODevice)
};
//==============================================================================
class AudioIODeviceCombiner : public AudioIODevice,
private Thread
{
public:
AudioIODeviceCombiner (const String& deviceName)
: AudioIODevice (deviceName, "CoreAudio"),
Thread (deviceName), callback (nullptr),
currentSampleRate (0), currentBufferSize (0), active (false),
fifos (1, 1)
{
}
~AudioIODeviceCombiner()
{
close();
devices.clear();
}
void addDevice (AudioIODevice* device)
{
jassert (device != nullptr);
jassert (! isOpen());
jassert (! device->isOpen());
devices.add (new DeviceWrapper (*this, device));
}
Array<AudioIODevice*> getDevices() const
{
Array<AudioIODevice*> devs;
for (int i = 0; i < devices.size(); ++i)
devs.add (devices.getUnchecked(i)->device);
return devs;
}
StringArray getOutputChannelNames() override
{
StringArray names;
for (int i = 0; i < devices.size(); ++i)
names.addArray (devices.getUnchecked(i)->device->getOutputChannelNames());
names.appendNumbersToDuplicates (false, true);
return names;
}
StringArray getInputChannelNames() override
{
StringArray names;
for (int i = 0; i < devices.size(); ++i)
names.addArray (devices.getUnchecked(i)->device->getInputChannelNames());
names.appendNumbersToDuplicates (false, true);
return names;
}
Array<double> getAvailableSampleRates() override
{
Array<double> commonRates;
for (int i = 0; i < devices.size(); ++i)
{
Array<double> rates (devices.getUnchecked(i)->device->getAvailableSampleRates());
if (i == 0)
commonRates = rates;
else
commonRates.removeValuesNotIn (rates);
}
return commonRates;
}
Array<int> getAvailableBufferSizes() override
{
Array<int> commonSizes;
for (int i = 0; i < devices.size(); ++i)
{
Array<int> sizes (devices.getUnchecked(i)->device->getAvailableBufferSizes());
if (i == 0)
commonSizes = sizes;
else
commonSizes.removeValuesNotIn (sizes);
}
return commonSizes;
}
bool isOpen() override { return active; }
bool isPlaying() override { return callback != nullptr; }
double getCurrentSampleRate() override { return currentSampleRate; }
int getCurrentBufferSizeSamples() override { return currentBufferSize; }
int getCurrentBitDepth() override
{
int depth = 32;
for (int i = 0; i < devices.size(); ++i)
depth = jmin (depth, devices.getUnchecked(i)->device->getCurrentBitDepth());
return depth;
}
int getDefaultBufferSize() override
{
int size = 0;
for (int i = 0; i < devices.size(); ++i)
size = jmax (size, devices.getUnchecked(i)->device->getDefaultBufferSize());
return size;
}
String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double sampleRate, int bufferSize) override
{
close();
active = true;
if (bufferSize <= 0)
bufferSize = getDefaultBufferSize();
if (sampleRate <= 0)
{
Array<double> rates (getAvailableSampleRates());
for (int i = 0; i < rates.size() && sampleRate < 44100.0; ++i)
sampleRate = rates.getUnchecked(i);
}
currentSampleRate = sampleRate;
currentBufferSize = bufferSize;
const int fifoSize = bufferSize * 3 + 1;
int totalInputChanIndex = 0, totalOutputChanIndex = 0;
int chanIndex = 0;
for (int i = 0; i < devices.size(); ++i)
{
DeviceWrapper& d = *devices.getUnchecked(i);
BigInteger ins (inputChannels >> totalInputChanIndex);
BigInteger outs (outputChannels >> totalOutputChanIndex);
int numIns = d.device->getInputChannelNames().size();
int numOuts = d.device->getOutputChannelNames().size();
totalInputChanIndex += numIns;
totalOutputChanIndex += numOuts;
String err = d.open (ins, outs, sampleRate, bufferSize,
chanIndex, fifoSize);
if (err.isNotEmpty())
{
close();
lastError = err;
return err;
}
chanIndex += d.numInputChans + d.numOutputChans;
}
fifos.setSize (chanIndex, fifoSize);
fifos.clear();
startThread (9);
return String();
}
void close() override
{
stop();
stopThread (10000);
fifos.clear();
active = false;
for (int i = 0; i < devices.size(); ++i)
devices.getUnchecked(i)->close();
}
BigInteger getActiveOutputChannels() const override
{
BigInteger chans;
int start = 0;
for (int i = 0; i < devices.size(); ++i)
{
const int numChans = devices.getUnchecked(i)->device->getOutputChannelNames().size();
chans |= (devices.getUnchecked(i)->device->getActiveOutputChannels() << start);
start += numChans;
}
return chans;
}
BigInteger getActiveInputChannels() const override
{
BigInteger chans;
int start = 0;
for (int i = 0; i < devices.size(); ++i)
{
const int numChans = devices.getUnchecked(i)->device->getInputChannelNames().size();
chans |= (devices.getUnchecked(i)->device->getActiveInputChannels() << start);
start += numChans;
}
return chans;
}
int getOutputLatencyInSamples() override
{
int lat = 0;
for (int i = 0; i < devices.size(); ++i)
lat = jmax (lat, devices.getUnchecked(i)->device->getOutputLatencyInSamples());
return lat + currentBufferSize * 2;
}
int getInputLatencyInSamples() override
{
int lat = 0;
for (int i = 0; i < devices.size(); ++i)
lat = jmax (lat, devices.getUnchecked(i)->device->getInputLatencyInSamples());
return lat + currentBufferSize * 2;
}
void start (AudioIODeviceCallback* newCallback) override
{
if (callback != newCallback)
{
stop();
fifos.clear();
for (int i = 0; i < devices.size(); ++i)
devices.getUnchecked(i)->start();
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
const ScopedLock sl (callbackLock);
callback = newCallback;
}
}
void stop() override
{
AudioIODeviceCallback* lastCallback = nullptr;
{
const ScopedLock sl (callbackLock);
std::swap (callback, lastCallback);
}
for (int i = 0; i < devices.size(); ++i)
devices.getUnchecked(i)->device->stop();
if (lastCallback != nullptr)
lastCallback->audioDeviceStopped();
}
String getLastError() override
{
return lastError;
}
private:
CriticalSection callbackLock;
AudioIODeviceCallback* callback;
double currentSampleRate;
int currentBufferSize;
bool active;
String lastError;
AudioSampleBuffer fifos;
void run() override
{
const int numSamples = currentBufferSize;
AudioSampleBuffer buffer (fifos.getNumChannels(), numSamples);
buffer.clear();
Array<float*> inputChans, outputChans;
for (int i = 0; i < devices.size(); ++i)
{
DeviceWrapper& d = *devices.getUnchecked(i);
for (int j = 0; j < d.numInputChans; ++j) inputChans.add (buffer.getSampleData (d.inputIndex + j));
for (int j = 0; j < d.numOutputChans; ++j) outputChans.add (buffer.getSampleData (d.outputIndex + j));
}
const int numInputChans = inputChans.size();
const int numOutputChans = outputChans.size();
inputChans.add (nullptr);
outputChans.add (nullptr);
const int blockSizeMs = jmax (1, (int) (numSamples / currentSampleRate));
jassert (numInputChans + numOutputChans == buffer.getNumChannels());
while (! threadShouldExit())
{
readInput (buffer, numSamples, blockSizeMs);
bool didCallback = true;
{
const ScopedLock sl (callbackLock);
if (callback != nullptr)
callback->audioDeviceIOCallback ((const float**) inputChans.getRawDataPointer(), numInputChans,
outputChans.getRawDataPointer(), numOutputChans, numSamples);
else
didCallback = false;
}
if (didCallback)
{
pushOutputData (buffer, numSamples, blockSizeMs);
}
else
{
for (int i = 0; i < numOutputChans; ++i)
FloatVectorOperations::clear (outputChans[i], numSamples);
reset();
}
}
}
void reset()
{
for (int i = 0; i < devices.size(); ++i)
devices.getUnchecked(i)->reset();
}
void underrun()
{
}
void readInput (AudioSampleBuffer& buffer, const int numSamples, const int blockSizeMs)
{
for (int i = 0; i < devices.size(); ++i)
{
DeviceWrapper& d = *devices.getUnchecked(i);
d.done = (d.numInputChans == 0);
}
for (int tries = 3;;)
{
bool anyRemaining = false;
for (int i = 0; i < devices.size(); ++i)
{
DeviceWrapper& d = *devices.getUnchecked(i);
if (! d.done)
{
if (d.isInputReady (numSamples))
{
d.readInput (buffer, numSamples);
d.done = true;
}
else
anyRemaining = true;
}
}
if (! anyRemaining)
return;
if (--tries == 0)
break;
wait (blockSizeMs);
}
for (int j = 0; j < devices.size(); ++j)
{
DeviceWrapper& d = *devices.getUnchecked(j);
if (! d.done)
for (int i = 0; i < d.numInputChans; ++i)
buffer.clear (d.inputIndex + i, 0, numSamples);
}
}
void pushOutputData (AudioSampleBuffer& buffer, const int numSamples, const int blockSizeMs)
{
for (int i = 0; i < devices.size(); ++i)
{
DeviceWrapper& d = *devices.getUnchecked(i);
d.done = (d.numOutputChans == 0);
}
for (int tries = 3;;)
{
bool anyRemaining = false;
for (int i = 0; i < devices.size(); ++i)
{
DeviceWrapper& d = *devices.getUnchecked(i);
if (! d.done)
{
if (d.isOutputReady (numSamples))
{
d.pushOutputData (buffer, numSamples);
d.done = true;
}
else
anyRemaining = true;
}
}
if ((! anyRemaining) || --tries == 0)
return;
wait (blockSizeMs);
}
}
//==============================================================================
struct DeviceWrapper : private AudioIODeviceCallback
{
DeviceWrapper (AudioIODeviceCombiner& cd, AudioIODevice* d)
: owner (cd), device (d), inputIndex (0), outputIndex (0),
inputFifo (32), outputFifo (32), done (false)
{
}
~DeviceWrapper()
{
close();
}
String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
double sampleRate, int bufferSize,
int channelIndex,
int fifoSize)
{
inputFifo.setTotalSize (fifoSize);
outputFifo.setTotalSize (fifoSize);
inputFifo.reset();
outputFifo.reset();
String err (device->open (inputChannels, outputChannels, sampleRate, bufferSize));
numInputChans = device->getActiveInputChannels().countNumberOfSetBits();
numOutputChans = device->getActiveOutputChannels().countNumberOfSetBits();
inputIndex = channelIndex;
outputIndex = channelIndex + numInputChans;
return err;
}
void close()
{
device->close();
}
void start()
{
reset();
device->start (this);
}
void reset()
{
inputFifo.reset();
outputFifo.reset();
}
bool isInputReady (int numSamples) const noexcept
{
return numInputChans == 0 || inputFifo.getNumReady() >= numSamples;
}
void readInput (AudioSampleBuffer& destBuffer, int numSamples)
{
if (numInputChans == 0)
return;
int start1, size1, start2, size2;
inputFifo.prepareToRead (numSamples, start1, size1, start2, size2);
for (int i = 0; i < numInputChans; ++i)
{
const int index = inputIndex + i;
float* const dest = destBuffer.getSampleData (index);
const float* const src = owner.fifos.getSampleData (index);
if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1);
if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2);
}
inputFifo.finishedRead (size1 + size2);
}
bool isOutputReady (int numSamples) const noexcept
{
return numOutputChans == 0 || outputFifo.getFreeSpace() >= numSamples;
}
void pushOutputData (AudioSampleBuffer& srcBuffer, int numSamples)
{
if (numOutputChans == 0)
return;
int start1, size1, start2, size2;
outputFifo.prepareToWrite (numSamples, start1, size1, start2, size2);
for (int i = 0; i < numOutputChans; ++i)
{
const int index = outputIndex + i;
float* const dest = owner.fifos.getSampleData (index);
const float* const src = srcBuffer.getSampleData (index);
if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1);
if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2);
}
outputFifo.finishedWrite (size1 + size2);
}
void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels,
float** outputChannelData, int numOutputChannels,
int numSamples) override
{
AudioSampleBuffer& buf = owner.fifos;
if (numInputChannels > 0)
{
int start1, size1, start2, size2;
inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2);
if (size1 + size2 < numSamples)
{
inputFifo.reset();
inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2);
}
for (int i = 0; i < numInputChannels; ++i)
{
float* const dest = buf.getSampleData (inputIndex + i);
const float* const src = inputChannelData[i];
if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1);
if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2);
}
inputFifo.finishedWrite (size1 + size2);
if (numSamples > size1 + size2)
{
for (int i = 0; i < numInputChans; ++i)
buf.clear (inputIndex + i, size1 + size2, numSamples - (size1 + size2));
owner.underrun();
}
}
if (numOutputChannels > 0)
{
int start1, size1, start2, size2;
outputFifo.prepareToRead (numSamples, start1, size1, start2, size2);
if (size1 + size2 < numSamples)
{
Thread::sleep (1);
outputFifo.prepareToRead (numSamples, start1, size1, start2, size2);
}
for (int i = 0; i < numOutputChannels; ++i)
{
float* const dest = outputChannelData[i];
const float* const src = buf.getSampleData (outputIndex + i);
if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1);
if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2);
}
outputFifo.finishedRead (size1 + size2);
if (numSamples > size1 + size2)
{
for (int i = 0; i < numOutputChannels; ++i)
FloatVectorOperations::clear (outputChannelData[i] + (size1 + size2), numSamples - (size1 + size2));
owner.underrun();
}
}
owner.notify();
}
void audioDeviceAboutToStart (AudioIODevice*) override {}
void audioDeviceStopped() override {}
void audioDeviceError (const String& errorMessage) override
{
const ScopedLock sl (owner.callbackLock);
if (owner.callback != nullptr)
owner.callback->audioDeviceError (errorMessage);
}
AudioIODeviceCombiner& owner;
ScopedPointer<AudioIODevice> device;
int inputIndex, numInputChans, outputIndex, numOutputChans;
AbstractFifo inputFifo, outputFifo;
bool done;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DeviceWrapper)
};
OwnedArray<DeviceWrapper> devices;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioIODeviceCombiner)
};
//==============================================================================
class CoreAudioIODeviceType : public AudioIODeviceType
{
@ -1230,6 +1789,19 @@ public:
return asInput ? d->inputIndex
: d->outputIndex;
if (AudioIODeviceCombiner* const d = dynamic_cast<AudioIODeviceCombiner*> (device))
{
const Array<AudioIODevice*> devs (d->getDevices());
for (int i = 0; i < devs.size(); ++i)
{
const int index = getIndexOfDevice (devs.getUnchecked(i), asInput);
if (index >= 0)
return index;
}
}
return -1;
}
@ -1247,12 +1819,21 @@ public:
if (deviceName.isEmpty())
deviceName = inputDeviceName;
if (index >= 0)
return new CoreAudioIODevice (deviceName,
inputIds [inputIndex], inputIndex,
outputIds [outputIndex], outputIndex);
ScopedPointer<CoreAudioIODevice> in, out;
return nullptr;
if (inputIndex >= 0)
in = new CoreAudioIODevice (outputDeviceName, inputIds [inputIndex], inputIndex, 0, -1);
if (outputIndex >= 0)
out = new CoreAudioIODevice (inputDeviceName, 0, -1, outputIds [outputIndex], outputIndex);
if (in == nullptr) return out.release();
if (out == nullptr) return in.release();
ScopedPointer<AudioIODeviceCombiner> combo (new AudioIODeviceCombiner (deviceName));
combo->addDevice (in.release());
combo->addDevice (out.release());
return combo.release();
}
//==============================================================================