mirror of https://github.com/AxioDL/boo.git
790 lines
27 KiB
C++
790 lines
27 KiB
C++
#include "AudioVoiceEngine.hpp"
|
|
#include "logvisor/logvisor.hpp"
|
|
|
|
#include <AudioToolbox/AudioToolbox.h>
|
|
#include <CoreMIDI/CoreMIDI.h>
|
|
#include <CoreAudio/HostTime.h>
|
|
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
|
|
namespace boo
|
|
{
|
|
static logvisor::Module Log("boo::AQS");
|
|
|
|
static AudioChannel AQSChannelToBooChannel(AudioChannelLabel ch)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case kAudioChannelLabel_Left:
|
|
return AudioChannel::FrontLeft;
|
|
case kAudioChannelLabel_Right:
|
|
return AudioChannel::FrontRight;
|
|
case kAudioChannelLabel_LeftSurround:
|
|
return AudioChannel::RearLeft;
|
|
case kAudioChannelLabel_RightSurround:
|
|
return AudioChannel::RearRight;
|
|
case kAudioChannelLabel_Center:
|
|
return AudioChannel::FrontCenter;
|
|
case kAudioChannelLabel_LFEScreen:
|
|
return AudioChannel::LFE;
|
|
case kAudioChannelLabel_LeftSurroundDirect:
|
|
return AudioChannel::RearLeft;
|
|
case kAudioChannelLabel_RightSurroundDirect:
|
|
return AudioChannel::SideRight;
|
|
}
|
|
return AudioChannel::Unknown;
|
|
}
|
|
|
|
struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
|
|
{
|
|
AudioQueueRef m_queue = nullptr;
|
|
AudioQueueBufferRef m_buffers[3];
|
|
size_t m_frameBytes;
|
|
|
|
MIDIClientRef m_midiClient = 0;
|
|
|
|
std::mutex m_engineMutex;
|
|
std::condition_variable m_engineEnterCv;
|
|
std::condition_variable m_engineLeaveCv;
|
|
bool m_inRetrace = false;
|
|
bool m_inCb = false;
|
|
bool m_cbRunning = true;
|
|
|
|
static void Callback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
|
|
{
|
|
if (!engine->m_cbRunning)
|
|
return;
|
|
|
|
std::unique_lock<std::mutex> lk(engine->m_engineMutex);
|
|
engine->m_inCb = true;
|
|
if (!engine->m_inRetrace)
|
|
{
|
|
if (engine->m_engineEnterCv.wait_for(lk,
|
|
std::chrono::nanoseconds(engine->m_mixInfo.m_periodFrames * 1000000000 /
|
|
size_t(engine->m_mixInfo.m_sampleRate))) == std::cv_status::timeout ||
|
|
!engine->m_inRetrace)
|
|
{
|
|
inBuffer->mAudioDataByteSize = engine->m_frameBytes;
|
|
memset(inBuffer->mAudioData, 0, engine->m_frameBytes);
|
|
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr);
|
|
engine->m_engineLeaveCv.notify_one();
|
|
engine->m_inCb = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
engine->_pumpAndMixVoices(engine->m_mixInfo.m_periodFrames,
|
|
reinterpret_cast<float*>(inBuffer->mAudioData));
|
|
inBuffer->mAudioDataByteSize = engine->m_frameBytes;
|
|
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr);
|
|
|
|
engine->m_engineLeaveCv.notify_one();
|
|
engine->m_inCb = false;
|
|
}
|
|
|
|
static void DummyCallback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {}
|
|
|
|
AudioChannelSet _getAvailableSet()
|
|
{
|
|
const unsigned chCount = 8;
|
|
AudioStreamBasicDescription desc = {};
|
|
desc.mSampleRate = 96000;
|
|
desc.mFormatID = kAudioFormatLinearPCM;
|
|
desc.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
|
desc.mBytesPerPacket = chCount * 4;
|
|
desc.mFramesPerPacket = 1;
|
|
desc.mBytesPerFrame = chCount * 4;
|
|
desc.mChannelsPerFrame = chCount;
|
|
desc.mBitsPerChannel = 32;
|
|
|
|
AudioQueueRef queue;
|
|
if (AudioQueueNewOutput(&desc, AudioQueueOutputCallback(DummyCallback),
|
|
this, nullptr, nullptr, 0, &queue))
|
|
{
|
|
Log.report(logvisor::Error, "unable to create output audio queue");
|
|
return AudioChannelSet::Unknown;
|
|
}
|
|
|
|
UInt32 hwChannels;
|
|
UInt32 channelsSz = sizeof(UInt32);
|
|
if (AudioQueueGetProperty(queue, kAudioQueueDeviceProperty_NumberChannels, &hwChannels, &channelsSz))
|
|
{
|
|
Log.report(logvisor::Error, "unable to get channel count from audio queue");
|
|
AudioQueueDispose(queue, true);
|
|
return AudioChannelSet::Unknown;
|
|
}
|
|
|
|
AudioQueueDispose(queue, true);
|
|
|
|
switch (hwChannels)
|
|
{
|
|
case 2:
|
|
return AudioChannelSet::Stereo;
|
|
case 4:
|
|
return AudioChannelSet::Quad;
|
|
case 6:
|
|
return AudioChannelSet::Surround51;
|
|
case 8:
|
|
return AudioChannelSet::Surround71;
|
|
default: break;
|
|
}
|
|
|
|
return AudioChannelSet::Unknown;
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
std::vector<std::pair<std::string, std::string>> ret;
|
|
|
|
ItemCount numDevices = MIDIGetNumberOfDevices();
|
|
ret.reserve(numDevices);
|
|
for (int i=int(numDevices)-1 ; i>=0 ; --i)
|
|
{
|
|
MIDIDeviceRef dev = MIDIGetDevice(i);
|
|
if (!dev)
|
|
continue;
|
|
|
|
SInt32 idNum;
|
|
if (MIDIObjectGetIntegerProperty(dev, kMIDIPropertyUniqueID, &idNum))
|
|
continue;
|
|
|
|
CFStringRef namestr;
|
|
const char* nameCstr;
|
|
if (MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &namestr))
|
|
continue;
|
|
|
|
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8)))
|
|
{
|
|
CFRelease(namestr);
|
|
continue;
|
|
}
|
|
|
|
char idStr[9];
|
|
snprintf(idStr, 9, "%08X\n", idNum);
|
|
ret.push_back(std::make_pair(std::string(idStr),
|
|
std::string(nameCstr)));
|
|
|
|
CFRelease(namestr);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static MIDIDeviceRef LookupMIDIDevice(const char* name)
|
|
{
|
|
ItemCount numDevices = MIDIGetNumberOfDevices();
|
|
for (ItemCount i=0 ; i<numDevices ; ++i)
|
|
{
|
|
MIDIDeviceRef dev = MIDIGetDevice(i);
|
|
if (!dev)
|
|
continue;
|
|
|
|
SInt32 idNum;
|
|
if (MIDIObjectGetIntegerProperty(dev, kMIDIPropertyUniqueID, &idNum))
|
|
continue;
|
|
|
|
char idStr[9];
|
|
snprintf(idStr, 9, "%08X\n", idNum);
|
|
if (strcmp(idStr, name))
|
|
continue;
|
|
|
|
return dev;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static MIDIEndpointRef LookupMIDISource(const char* name)
|
|
{
|
|
MIDIDeviceRef dev = LookupMIDIDevice(name);
|
|
if (!dev)
|
|
return {};
|
|
|
|
ItemCount numEnt = MIDIDeviceGetNumberOfEntities(dev);
|
|
for (ItemCount i=0 ; i<numEnt ; ++i)
|
|
{
|
|
MIDIEntityRef ent = MIDIDeviceGetEntity(dev, i);
|
|
if (ent)
|
|
{
|
|
ItemCount numSrc = MIDIEntityGetNumberOfSources(ent);
|
|
for (ItemCount s=0 ; s<numSrc ; ++s)
|
|
{
|
|
MIDIEndpointRef src = MIDIEntityGetSource(ent, s);
|
|
if (src)
|
|
return src;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static MIDIEndpointRef LookupMIDIDest(const char* name)
|
|
{
|
|
MIDIDeviceRef dev = LookupMIDIDevice(name);
|
|
if (!dev)
|
|
return {};
|
|
|
|
ItemCount numEnt = MIDIDeviceGetNumberOfEntities(dev);
|
|
for (ItemCount i=0 ; i<numEnt ; ++i)
|
|
{
|
|
MIDIEntityRef ent = MIDIDeviceGetEntity(dev, i);
|
|
if (ent)
|
|
{
|
|
ItemCount numDest = MIDIEntityGetNumberOfDestinations(ent);
|
|
for (ItemCount d=0 ; d<numDest ; ++d)
|
|
{
|
|
MIDIEndpointRef dst = MIDIEntityGetDestination(ent, d);
|
|
if (dst)
|
|
return dst;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static void MIDIReceiveProc(const MIDIPacketList* pktlist,
|
|
IMIDIReceiver* readProcRefCon,
|
|
void*)
|
|
{
|
|
const MIDIPacket* packet = &pktlist->packet[0];
|
|
for (int i=0 ; i<pktlist->numPackets ; ++i)
|
|
{
|
|
std::vector<uint8_t> bytes(std::cbegin(packet->data), std::cbegin(packet->data) + packet->length);
|
|
readProcRefCon->m_receiver(std::move(bytes), AudioConvertHostTimeToNanos(packet->timeStamp) / 1.0e9);
|
|
packet = MIDIPacketNext(packet);
|
|
}
|
|
}
|
|
|
|
struct MIDIIn : public IMIDIIn
|
|
{
|
|
MIDIEndpointRef m_midi = 0;
|
|
MIDIPortRef m_midiPort = 0;
|
|
|
|
MIDIIn(bool virt, ReceiveFunctor&& receiver)
|
|
: IMIDIIn(virt, std::move(receiver)) {}
|
|
|
|
~MIDIIn()
|
|
{
|
|
if (m_midi)
|
|
MIDIEndpointDispose(m_midi);
|
|
if (m_midiPort)
|
|
MIDIPortDispose(m_midiPort);
|
|
}
|
|
|
|
std::string description() const
|
|
{
|
|
CFStringRef namestr;
|
|
const char* nameCstr;
|
|
if (MIDIObjectGetStringProperty(m_midi, kMIDIPropertyName, &namestr))
|
|
return {};
|
|
|
|
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8)))
|
|
{
|
|
CFRelease(namestr);
|
|
return {};
|
|
}
|
|
|
|
CFRelease(namestr);
|
|
return nameCstr;
|
|
}
|
|
};
|
|
|
|
struct MIDIOut : public IMIDIOut
|
|
{
|
|
MIDIEndpointRef m_midi = 0;
|
|
MIDIPortRef m_midiPort = 0;
|
|
|
|
MIDIOut(bool virt)
|
|
: IMIDIOut(virt) {}
|
|
|
|
~MIDIOut()
|
|
{
|
|
if (m_midi)
|
|
MIDIEndpointDispose(m_midi);
|
|
if (m_midiPort)
|
|
MIDIPortDispose(m_midiPort);
|
|
}
|
|
|
|
std::string description() const
|
|
{
|
|
CFStringRef namestr;
|
|
const char* nameCstr;
|
|
if (MIDIObjectGetStringProperty(m_midi, kMIDIPropertyName, &namestr))
|
|
return {};
|
|
|
|
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8)))
|
|
{
|
|
CFRelease(namestr);
|
|
return {};
|
|
}
|
|
|
|
CFRelease(namestr);
|
|
return nameCstr;
|
|
}
|
|
|
|
size_t send(const void* buf, size_t len) const
|
|
{
|
|
union
|
|
{
|
|
MIDIPacketList head;
|
|
Byte storage[512];
|
|
} list;
|
|
MIDIPacket* curPacket = MIDIPacketListInit(&list.head);
|
|
if (MIDIPacketListAdd(&list.head, sizeof(list), curPacket, AudioGetCurrentHostTime(),
|
|
len, reinterpret_cast<const Byte*>(buf)))
|
|
{
|
|
if (m_midiPort)
|
|
MIDISend(m_midiPort, m_midi, &list.head);
|
|
else
|
|
MIDIReceived(m_midi, &list.head);
|
|
return len;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct MIDIInOut : public IMIDIInOut
|
|
{
|
|
MIDIEndpointRef m_midiIn = 0;
|
|
MIDIPortRef m_midiPortIn = 0;
|
|
MIDIEndpointRef m_midiOut = 0;
|
|
MIDIPortRef m_midiPortOut = 0;
|
|
|
|
MIDIInOut(bool virt, ReceiveFunctor&& receiver)
|
|
: IMIDIInOut(virt, std::move(receiver)) {}
|
|
|
|
~MIDIInOut()
|
|
{
|
|
if (m_midiIn)
|
|
MIDIEndpointDispose(m_midiIn);
|
|
if (m_midiPortIn)
|
|
MIDIPortDispose(m_midiPortIn);
|
|
if (m_midiOut)
|
|
MIDIEndpointDispose(m_midiOut);
|
|
if (m_midiPortOut)
|
|
MIDIPortDispose(m_midiPortOut);
|
|
}
|
|
|
|
std::string description() const
|
|
{
|
|
CFStringRef namestr;
|
|
const char* nameCstr;
|
|
if (MIDIObjectGetStringProperty(m_midiIn, kMIDIPropertyName, &namestr))
|
|
return {};
|
|
|
|
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8)))
|
|
{
|
|
CFRelease(namestr);
|
|
return {};
|
|
}
|
|
|
|
CFRelease(namestr);
|
|
return nameCstr;
|
|
}
|
|
|
|
size_t send(const void* buf, size_t len) const
|
|
{
|
|
union
|
|
{
|
|
MIDIPacketList head;
|
|
Byte storage[512];
|
|
} list;
|
|
MIDIPacket* curPacket = MIDIPacketListInit(&list.head);
|
|
if (MIDIPacketListAdd(&list.head, sizeof(list), curPacket, AudioGetCurrentHostTime(),
|
|
len, reinterpret_cast<const Byte*>(buf)))
|
|
{
|
|
if (m_midiPortOut)
|
|
MIDISend(m_midiPortOut, m_midiOut, &list.head);
|
|
else
|
|
MIDIReceived(m_midiOut, &list.head);
|
|
return len;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
unsigned m_midiInCounter = 0;
|
|
unsigned m_midiOutCounter = 0;
|
|
|
|
std::unique_ptr<IMIDIIn> newVirtualMIDIIn(ReceiveFunctor&& receiver)
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(true, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
char name[256];
|
|
snprintf(name, 256, "Boo MIDI Virtual In %u", m_midiInCounter++);
|
|
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
OSStatus stat;
|
|
if ((stat = MIDIDestinationCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc),
|
|
static_cast<IMIDIReceiver*>(ret.get()),
|
|
&static_cast<MIDIIn&>(*ret).m_midi)))
|
|
ret.reset();
|
|
CFRelease(midiName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIOut> newVirtualMIDIOut()
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(true);
|
|
if (!ret)
|
|
return {};
|
|
|
|
char name[256];
|
|
snprintf(name, 256, "Boo MIDI Virtual Out %u", m_midiOutCounter++);
|
|
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDISourceCreate(m_midiClient, midiName, &static_cast<MIDIOut&>(*ret).m_midi))
|
|
ret.reset();
|
|
CFRelease(midiName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut(ReceiveFunctor&& receiver)
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(true, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
char name[256];
|
|
snprintf(name, 256, "Boo MIDI Virtual In %u", m_midiInCounter++);
|
|
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDIDestinationCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc),
|
|
static_cast<IMIDIReceiver*>(ret.get()),
|
|
&static_cast<MIDIInOut&>(*ret).m_midiIn))
|
|
ret.reset();
|
|
CFRelease(midiName);
|
|
|
|
if (!ret)
|
|
return {};
|
|
|
|
snprintf(name, 256, "Boo MIDI Virtual Out %u", m_midiOutCounter++);
|
|
midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDISourceCreate(m_midiClient, midiName, &static_cast<MIDIInOut&>(*ret).m_midiOut))
|
|
ret.reset();
|
|
CFRelease(midiName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name, ReceiveFunctor&& receiver)
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
MIDIEndpointRef src = LookupMIDISource(name);
|
|
if (!src)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(false, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
char mname[256];
|
|
snprintf(mname, 256, "Boo MIDI Real In %u", m_midiInCounter++);
|
|
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDIInputPortCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc),
|
|
static_cast<IMIDIReceiver*>(ret.get()),
|
|
&static_cast<MIDIIn&>(*ret).m_midiPort))
|
|
ret.reset();
|
|
else
|
|
MIDIPortConnectSource(static_cast<MIDIIn&>(*ret).m_midiPort, src, nullptr);
|
|
CFRelease(midiName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name)
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
MIDIEndpointRef dst = LookupMIDIDest(name);
|
|
if (!dst)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(false);
|
|
if (!ret)
|
|
return {};
|
|
|
|
char mname[256];
|
|
snprintf(mname, 256, "Boo MIDI Real Out %u", m_midiOutCounter++);
|
|
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDIOutputPortCreate(m_midiClient, midiName, &static_cast<MIDIOut&>(*ret).m_midiPort))
|
|
ret.reset();
|
|
else
|
|
static_cast<MIDIOut&>(*ret).m_midi = dst;
|
|
CFRelease(midiName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver)
|
|
{
|
|
if (!m_midiClient)
|
|
return {};
|
|
|
|
MIDIEndpointRef src = LookupMIDISource(name);
|
|
if (!src)
|
|
return {};
|
|
|
|
MIDIEndpointRef dst = LookupMIDIDest(name);
|
|
if (!dst)
|
|
return {};
|
|
|
|
std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(false, std::move(receiver));
|
|
if (!ret)
|
|
return {};
|
|
|
|
char mname[256];
|
|
snprintf(mname, 256, "Boo MIDI Real In %u", m_midiInCounter++);
|
|
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDIInputPortCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc),
|
|
static_cast<IMIDIReceiver*>(ret.get()),
|
|
&static_cast<MIDIInOut&>(*ret).m_midiPortIn))
|
|
ret.reset();
|
|
else
|
|
MIDIPortConnectSource(static_cast<MIDIInOut&>(*ret).m_midiPortIn, src, nullptr);
|
|
CFRelease(midiName);
|
|
|
|
if (!ret)
|
|
return {};
|
|
|
|
snprintf(mname, 256, "Boo MIDI Real Out %u", m_midiOutCounter++);
|
|
midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull);
|
|
if (MIDIOutputPortCreate(m_midiClient, midiName, &static_cast<MIDIInOut&>(*ret).m_midiPortOut))
|
|
ret.reset();
|
|
else
|
|
static_cast<MIDIInOut&>(*ret).m_midiOut = dst;
|
|
CFRelease(midiName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool useMIDILock() const {return true;}
|
|
|
|
AQSAudioVoiceEngine()
|
|
{
|
|
m_mixInfo.m_channels = _getAvailableSet();
|
|
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
|
|
|
|
AudioStreamBasicDescription desc = {};
|
|
desc.mSampleRate = 96000;
|
|
desc.mFormatID = kAudioFormatLinearPCM;
|
|
desc.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
|
desc.mBytesPerPacket = chCount * 4;
|
|
desc.mFramesPerPacket = 1;
|
|
desc.mBytesPerFrame = chCount * 4;
|
|
desc.mChannelsPerFrame = chCount;
|
|
desc.mBitsPerChannel = 32;
|
|
|
|
OSStatus err;
|
|
if ((err = AudioQueueNewOutput(&desc, AudioQueueOutputCallback(Callback),
|
|
this, nullptr, nullptr, 0, &m_queue)))
|
|
{
|
|
Log.report(logvisor::Fatal, "unable to create output audio queue");
|
|
return;
|
|
}
|
|
|
|
Float64 actualSampleRate;
|
|
UInt32 argSize = 8;
|
|
err = AudioQueueGetProperty(m_queue, kAudioQueueDeviceProperty_SampleRate, &actualSampleRate, &argSize);
|
|
AudioQueueDispose(m_queue, true);
|
|
if (err)
|
|
{
|
|
Log.report(logvisor::Fatal, "unable to get native sample rate from audio queue");
|
|
return;
|
|
}
|
|
|
|
desc.mSampleRate = actualSampleRate;
|
|
if ((err = AudioQueueNewOutput(&desc, AudioQueueOutputCallback(Callback),
|
|
this, nullptr, nullptr, 0, &m_queue)))
|
|
{
|
|
Log.report(logvisor::Fatal, "unable to create output audio queue");
|
|
return;
|
|
}
|
|
|
|
m_mixInfo.m_sampleRate = actualSampleRate;
|
|
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
|
|
m_mixInfo.m_bitsPerSample = 32;
|
|
m_5msFrames = actualSampleRate * 5 / 1000;
|
|
|
|
ChannelMap& chMapOut = m_mixInfo.m_channelMap;
|
|
if (chCount > 2)
|
|
{
|
|
AudioChannelLayout layout;
|
|
UInt32 layoutSz = sizeof(layout);
|
|
if (AudioQueueGetProperty(m_queue, kAudioQueueProperty_ChannelLayout, &layout, &layoutSz))
|
|
{
|
|
Log.report(logvisor::Fatal, "unable to get channel layout from audio queue");
|
|
return;
|
|
}
|
|
|
|
switch (layout.mChannelLayoutTag)
|
|
{
|
|
case kAudioChannelLayoutTag_UseChannelDescriptions:
|
|
chMapOut.m_channelCount = layout.mNumberChannelDescriptions;
|
|
for (int i=0 ; i<layout.mNumberChannelDescriptions ; ++i)
|
|
{
|
|
AudioChannel ch = AQSChannelToBooChannel(layout.mChannelDescriptions[i].mChannelLabel);
|
|
chMapOut.m_channels[i] = ch;
|
|
}
|
|
break;
|
|
case kAudioChannelLayoutTag_UseChannelBitmap:
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_Left) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::FrontLeft;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_Right) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::FrontRight;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_Center) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::FrontCenter;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_LFEScreen) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::LFE;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_LeftSurround) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::RearLeft;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_RightSurround) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::RearRight;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_LeftSurroundDirect) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::SideLeft;
|
|
if ((layout.mChannelBitmap & kAudioChannelBit_RightSurroundDirect) != 0)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::SideRight;
|
|
break;
|
|
case kAudioChannelLayoutTag_Stereo:
|
|
case kAudioChannelLayoutTag_StereoHeadphones:
|
|
chMapOut.m_channelCount = 2;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
break;
|
|
case kAudioChannelLayoutTag_Quadraphonic:
|
|
chMapOut.m_channelCount = 4;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
chMapOut.m_channels[2] = AudioChannel::RearLeft;
|
|
chMapOut.m_channels[3] = AudioChannel::RearRight;
|
|
break;
|
|
case kAudioChannelLayoutTag_Pentagonal:
|
|
chMapOut.m_channelCount = 5;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
chMapOut.m_channels[2] = AudioChannel::RearLeft;
|
|
chMapOut.m_channels[3] = AudioChannel::RearRight;
|
|
chMapOut.m_channels[4] = AudioChannel::FrontCenter;
|
|
break;
|
|
default:
|
|
Log.report(logvisor::Fatal,
|
|
"unknown channel layout %u; using stereo",
|
|
layout.mChannelLayoutTag);
|
|
chMapOut.m_channelCount = 2;
|
|
chMapOut.m_channels[0] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[1] = AudioChannel::FrontRight;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::FrontLeft;
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::FrontRight;
|
|
}
|
|
|
|
while (chMapOut.m_channelCount < chCount)
|
|
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::Unknown;
|
|
|
|
m_mixInfo.m_periodFrames = m_5msFrames * 3;
|
|
for (int i=0 ; i<3 ; ++i)
|
|
if (AudioQueueAllocateBuffer(m_queue, m_mixInfo.m_periodFrames * chCount * 4, &m_buffers[i]))
|
|
{
|
|
Log.report(logvisor::Fatal, "unable to create audio queue buffer");
|
|
AudioQueueDispose(m_queue, false);
|
|
m_queue = nullptr;
|
|
return;
|
|
}
|
|
|
|
m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4;
|
|
|
|
for (unsigned i=0 ; i<3 ; ++i)
|
|
{
|
|
memset(m_buffers[i]->mAudioData, 0, m_frameBytes);
|
|
m_buffers[i]->mAudioDataByteSize = m_frameBytes;
|
|
AudioQueueEnqueueBuffer(m_queue, m_buffers[i], 0, nullptr);
|
|
}
|
|
AudioQueuePrime(m_queue, 0, nullptr);
|
|
AudioQueueStart(m_queue, nullptr);
|
|
|
|
/* Also create shared MIDI client */
|
|
MIDIClientCreate(CFSTR("Boo MIDI"), nullptr, nullptr, &m_midiClient);
|
|
}
|
|
|
|
~AQSAudioVoiceEngine()
|
|
{
|
|
m_cbRunning = false;
|
|
if (m_inCb)
|
|
m_engineEnterCv.notify_one();
|
|
AudioQueueDispose(m_queue, true);
|
|
if (m_midiClient)
|
|
MIDIClientDispose(m_midiClient);
|
|
}
|
|
|
|
/* This is temperamental for AudioQueueServices
|
|
* (which has unpredictable buffering windows).
|
|
* _pumpAndMixVoicesRetrace() is highly recommended. */
|
|
void pumpAndMixVoices()
|
|
{
|
|
std::unique_lock<std::mutex> lk(m_engineMutex);
|
|
if (m_inCb)
|
|
{
|
|
/* Wake up callback */
|
|
m_engineEnterCv.notify_one();
|
|
/* Wait for callback completion */
|
|
m_engineLeaveCv.wait(lk);
|
|
}
|
|
}
|
|
|
|
void _pumpAndMixVoicesRetrace()
|
|
{
|
|
std::unique_lock<std::mutex> lk(m_engineMutex);
|
|
m_inRetrace = true;
|
|
while (m_inRetrace)
|
|
{
|
|
if (m_inCb) /* Wake up callback */
|
|
m_engineEnterCv.notify_one();
|
|
/* Wait for callback completion */
|
|
m_engineLeaveCv.wait(lk);
|
|
}
|
|
}
|
|
|
|
void _retraceBreak()
|
|
{
|
|
std::unique_lock<std::mutex> lk(m_engineMutex);
|
|
m_inRetrace = false;
|
|
if (m_inCb) /* Break out of callback */
|
|
m_engineEnterCv.notify_one();
|
|
else /* Break out of client */
|
|
m_engineLeaveCv.notify_one();
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()
|
|
{
|
|
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<AQSAudioVoiceEngine>();
|
|
if (!static_cast<AQSAudioVoiceEngine&>(*ret).m_queue)
|
|
return {};
|
|
return ret;
|
|
}
|
|
|
|
}
|