Sync macOS with API changes

This commit is contained in:
Jack Andersen 2018-08-27 17:46:33 -10:00
parent 261c06d746
commit 8ee04c8f1a
8 changed files with 340 additions and 231 deletions

View File

@ -123,8 +123,8 @@ elseif(APPLE)
PROPERTIES COMPILE_FLAGS -fobjc-arc) PROPERTIES COMPILE_FLAGS -fobjc-arc)
list(APPEND PLAT_HDRS list(APPEND PLAT_HDRS
lib/CFPointer.hpp
include/boo/graphicsdev/Metal.hpp include/boo/graphicsdev/Metal.hpp
lib/inputdev/CFPointer.hpp
lib/inputdev/IOKitPointer.hpp) lib/inputdev/IOKitPointer.hpp)
find_library(APPKIT_LIBRARY AppKit) find_library(APPKIT_LIBRARY AppKit)

View File

@ -296,7 +296,7 @@ public:
virtual bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz)=0; virtual bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz)=0;
virtual std::unique_ptr<uint8_t[]> clipboardPaste(EClipboardType type, size_t& sz)=0; virtual std::unique_ptr<uint8_t[]> clipboardPaste(EClipboardType type, size_t& sz)=0;
virtual void waitForRetrace(IAudioVoiceEngine* voxEngine=nullptr)=0; virtual void waitForRetrace()=0;
virtual uintptr_t getPlatformHandle() const=0; virtual uintptr_t getPlatformHandle() const=0;
virtual bool _incomingEvent(void* event) {(void)event; return false;} virtual bool _incomingEvent(void* event) {(void)event; return false;}

View File

@ -102,12 +102,6 @@ struct IAudioVoiceEngine
/** Get canonical count of frames for each 5ms output block */ /** Get canonical count of frames for each 5ms output block */
virtual size_t get5MsFrames() const=0; virtual size_t get5MsFrames() const=0;
/** IWindow::waitForRetrace() enter - for platforms that spend v-sync waits synchronously pumping audio */
virtual void _pumpAndMixVoicesRetrace() { pumpAndMixVoices(); }
/** IWindow::waitForRetrace() break - signal retrace event to break out of pumping cycles */
virtual void _retraceBreak() {}
}; };
/** Construct host platform's voice engine */ /** Construct host platform's voice engine */

View File

@ -44,12 +44,12 @@ public:
return *this; return *this;
} }
CFTypeRef* operator&() T* operator&()
{ {
if (CFTypeRef pointer = storage) { if (CFTypeRef pointer = storage) {
CFRelease(pointer); CFRelease(pointer);
} }
return &storage; return (T*)&storage;
} }
operator bool() const { return storage != nullptr; } operator bool() const { return storage != nullptr; }

View File

@ -1,5 +1,7 @@
#include "AudioVoiceEngine.hpp" #include "AudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp" #include "logvisor/logvisor.hpp"
#include "boo/IApplication.hpp"
#include "../CFPointer.hpp"
#include <AudioToolbox/AudioToolbox.h> #include <AudioToolbox/AudioToolbox.h>
#include <CoreMIDI/CoreMIDI.h> #include <CoreMIDI/CoreMIDI.h>
@ -12,6 +14,8 @@ namespace boo
{ {
static logvisor::Module Log("boo::AQS"); static logvisor::Module Log("boo::AQS");
#define AQS_NUM_BUFFERS 24
static AudioChannel AQSChannelToBooChannel(AudioChannelLabel ch) static AudioChannel AQSChannelToBooChannel(AudioChannelLabel ch)
{ {
switch (ch) switch (ch)
@ -38,105 +42,181 @@ static AudioChannel AQSChannelToBooChannel(AudioChannelLabel ch)
struct AQSAudioVoiceEngine : BaseAudioVoiceEngine struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
{ {
CFPointer<CFStringRef> m_runLoopMode;
CFPointer<CFStringRef> m_devName;
AudioQueueRef m_queue = nullptr; AudioQueueRef m_queue = nullptr;
AudioQueueBufferRef m_buffers[3]; AudioQueueBufferRef m_buffers[AQS_NUM_BUFFERS];
size_t m_frameBytes; size_t m_frameBytes;
MIDIClientRef m_midiClient = 0; 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; bool m_cbRunning = true;
bool m_needsRebuild = false;
static void Callback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) static void Callback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{ {
if (!engine->m_cbRunning) if (!engine->m_cbRunning)
return; 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, engine->_pumpAndMixVoices(engine->m_mixInfo.m_periodFrames,
reinterpret_cast<float*>(inBuffer->mAudioData)); reinterpret_cast<float*>(inBuffer->mAudioData));
inBuffer->mAudioDataByteSize = engine->m_frameBytes; inBuffer->mAudioDataByteSize = engine->m_frameBytes;
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr); AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr);
engine->m_engineLeaveCv.notify_one();
engine->m_inCb = false;
} }
static void DummyCallback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {} static void DummyCallback(AQSAudioVoiceEngine* engine, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {}
AudioChannelSet _getAvailableSet() std::pair<AudioChannelSet, Float64> _getAvailableSetAndRate()
{ {
const unsigned chCount = 8; AudioObjectPropertyAddress propertyAddress;
AudioStreamBasicDescription desc = {}; UInt32 argSize;
desc.mSampleRate = 96000; int numStreams;
desc.mFormatID = kAudioFormatLinearPCM; std::vector<AudioStreamID> streamIDs;
desc.mFormatFlags = kLinearPCMFormatFlagIsFloat;
desc.mBytesPerPacket = chCount * 4;
desc.mFramesPerPacket = 1;
desc.mBytesPerFrame = chCount * 4;
desc.mChannelsPerFrame = chCount;
desc.mBitsPerChannel = 32;
AudioQueueRef queue; CFStringRef devName = m_devName.get();
if (AudioQueueNewOutput(&desc, AudioQueueOutputCallback(DummyCallback), AudioObjectID devId;
this, nullptr, nullptr, 0, &queue)) propertyAddress.mSelector = kAudioHardwarePropertyTranslateUIDToDevice;
{ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
Log.report(logvisor::Error, "unable to create output audio queue"); propertyAddress.mElement = kAudioObjectPropertyElementMaster;
return AudioChannelSet::Unknown; argSize = sizeof(devId);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, sizeof(devName), &devName, &argSize, &devId) != noErr) {
Log.report(logvisor::Error, "unable to resolve audio device UID %s, using default",
CFStringGetCStringPtr(devName, kCFStringEncodingUTF8));
argSize = sizeof(devId);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &argSize, &devId) == noErr) {
argSize = sizeof(CFStringRef);
AudioObjectPropertyAddress deviceAddress;
deviceAddress.mSelector = kAudioDevicePropertyDeviceUID;
AudioObjectGetPropertyData(devId, &deviceAddress, 0, NULL, &argSize, &m_devName);
} else {
Log.report(logvisor::Fatal, "unable determine default audio device");
return {AudioChannelSet::Unknown, 48000.0};
}
} }
UInt32 hwChannels; propertyAddress.mSelector = kAudioDevicePropertyStreams;
UInt32 channelsSz = sizeof(UInt32); if (AudioObjectGetPropertyDataSize(devId, &propertyAddress, 0, NULL, &argSize) == noErr) {
if (AudioQueueGetProperty(queue, kAudioQueueDeviceProperty_NumberChannels, &hwChannels, &channelsSz)) numStreams = argSize / sizeof(AudioStreamID);
{ streamIDs.resize(numStreams);
Log.report(logvisor::Error, "unable to get channel count from audio queue");
AudioQueueDispose(queue, true); if (AudioObjectGetPropertyData(devId, &propertyAddress, 0, NULL, &argSize, &streamIDs[0]) == noErr) {
return AudioChannelSet::Unknown; propertyAddress.mSelector = kAudioStreamPropertyDirection;
for (int stm = 0; stm < numStreams; stm++) {
UInt32 streamDir;
argSize = sizeof(streamDir);
if (AudioObjectGetPropertyData(streamIDs[stm], &propertyAddress, 0, NULL, &argSize, &streamDir) == noErr) {
if (streamDir == 0) {
propertyAddress.mSelector = kAudioStreamPropertyPhysicalFormat;
AudioStreamBasicDescription asbd;
argSize = sizeof(asbd);
if (AudioObjectGetPropertyData(streamIDs[stm], &propertyAddress, 0, NULL, &argSize, &asbd) == noErr) {
switch (asbd.mChannelsPerFrame)
{
case 2:
return {AudioChannelSet::Stereo, asbd.mSampleRate};
case 4:
return {AudioChannelSet::Quad, asbd.mSampleRate};
case 6:
return {AudioChannelSet::Surround51, asbd.mSampleRate};
case 8:
return {AudioChannelSet::Surround71, asbd.mSampleRate};
default: break;
}
if (asbd.mChannelsPerFrame > 8)
return {AudioChannelSet::Surround71, asbd.mSampleRate};
}
break;
}
}
}
}
} }
AudioQueueDispose(queue, true); return {AudioChannelSet::Unknown, 48000.0};
switch (hwChannels)
{
case 2:
return AudioChannelSet::Stereo;
case 4:
return AudioChannelSet::Quad;
case 6:
return AudioChannelSet::Surround51;
case 8:
return AudioChannelSet::Surround71;
default: break;
}
if (hwChannels > 8)
return AudioChannelSet::Surround71;
return AudioChannelSet::Unknown;
} }
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const std::string getCurrentAudioOutput() const
{
return CFStringGetCStringPtr(m_devName.get(), kCFStringEncodingUTF8);
}
bool setCurrentAudioOutput(const char* name)
{
m_devName = CFPointer<CFStringRef>::adopt(CFStringCreateWithCString(nullptr, name, kCFStringEncodingUTF8));
_rebuildAudioQueue();
return true;
}
/*
* https://stackoverflow.com/questions/1983984/how-to-get-audio-device-uid-to-pass-into-nssounds-setplaybackdeviceidentifier
*/
std::vector<std::pair<std::string, std::string>> enumerateAudioOutputs() const
{
std::vector<std::pair<std::string, std::string>> ret;
AudioObjectPropertyAddress propertyAddress;
std::vector<AudioObjectID> deviceIDs;
UInt32 propertySize;
int numDevices;
std::vector<AudioStreamID> streamIDs;
int numStreams;
propertyAddress.mSelector = kAudioHardwarePropertyDevices;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize) == noErr) {
numDevices = propertySize / sizeof(AudioDeviceID);
ret.reserve(numDevices);
deviceIDs.resize(numDevices);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &deviceIDs[0]) == noErr) {
char deviceName[64];
for (int idx = 0; idx < numDevices; idx++) {
propertyAddress.mSelector = kAudioDevicePropertyStreams;
if (AudioObjectGetPropertyDataSize(deviceIDs[idx], &propertyAddress, 0, NULL, &propertySize) == noErr) {
numStreams = propertySize / sizeof(AudioStreamID);
streamIDs.resize(numStreams);
if (AudioObjectGetPropertyData(deviceIDs[idx], &propertyAddress, 0, NULL, &propertySize, &streamIDs[0]) == noErr) {
propertyAddress.mSelector = kAudioStreamPropertyDirection;
bool foundOutput = false;
for (int stm = 0; stm < numStreams; stm++) {
UInt32 streamDir;
propertySize = sizeof(streamDir);
if (AudioObjectGetPropertyData(streamIDs[stm], &propertyAddress, 0, NULL, &propertySize, &streamDir) == noErr) {
if (streamDir == 0) {
foundOutput = true;
break;
}
}
}
if (!foundOutput)
continue;
}
}
propertySize = sizeof(deviceName);
propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
if (AudioObjectGetPropertyData(deviceIDs[idx], &propertyAddress, 0, NULL, &propertySize, deviceName) == noErr) {
CFPointer<CFStringRef> uidString;
propertySize = sizeof(CFStringRef);
propertyAddress.mSelector = kAudioDevicePropertyDeviceUID;
if (AudioObjectGetPropertyData(deviceIDs[idx], &propertyAddress, 0, NULL, &propertySize, &uidString) == noErr) {
ret.emplace_back(CFStringGetCStringPtr(uidString.get(), kCFStringEncodingUTF8), deviceName);
}
}
}
}
}
return ret;
}
std::vector<std::pair<std::string, std::string>> enumerateMIDIInputs() const
{ {
if (!m_midiClient) if (!m_midiClient)
return {}; return {};
@ -151,32 +231,50 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!dev) if (!dev)
continue; continue;
bool isInput = false;
ItemCount numEnt = MIDIDeviceGetNumberOfEntities(dev);
for (ItemCount j=0 ; j<numEnt ; ++j)
{
MIDIEntityRef ent = MIDIDeviceGetEntity(dev, j);
if (ent)
{
ItemCount numSrc = MIDIEntityGetNumberOfSources(ent);
if (numSrc)
{
isInput = true;
break;
}
}
}
if (!isInput)
continue;
SInt32 idNum; SInt32 idNum;
if (MIDIObjectGetIntegerProperty(dev, kMIDIPropertyUniqueID, &idNum)) if (MIDIObjectGetIntegerProperty(dev, kMIDIPropertyUniqueID, &idNum))
continue; continue;
CFStringRef namestr; CFPointer<CFStringRef> namestr;
const char* nameCstr; const char* nameCstr;
if (MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &namestr)) if (MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &namestr))
continue; continue;
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8))) if (!(nameCstr = CFStringGetCStringPtr(namestr.get(), kCFStringEncodingUTF8)))
{
CFRelease(namestr);
continue; continue;
}
char idStr[9]; char idStr[9];
snprintf(idStr, 9, "%08X\n", idNum); snprintf(idStr, 9, "%08X\n", idNum);
ret.push_back(std::make_pair(std::string(idStr), ret.push_back(std::make_pair(std::string(idStr),
std::string(nameCstr))); std::string(nameCstr)));
CFRelease(namestr);
} }
return ret; return ret;
} }
bool supportsVirtualMIDIIn() const
{
return true;
}
static MIDIDeviceRef LookupMIDIDevice(const char* name) static MIDIDeviceRef LookupMIDIDevice(const char* name)
{ {
ItemCount numDevices = MIDIGetNumberOfDevices(); ItemCount numDevices = MIDIGetNumberOfDevices();
@ -269,8 +367,8 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
MIDIEndpointRef m_midi = 0; MIDIEndpointRef m_midi = 0;
MIDIPortRef m_midiPort = 0; MIDIPortRef m_midiPort = 0;
MIDIIn(bool virt, ReceiveFunctor&& receiver) MIDIIn(AQSAudioVoiceEngine* parent, bool virt, ReceiveFunctor&& receiver)
: IMIDIIn(virt, std::move(receiver)) {} : IMIDIIn(parent, virt, std::move(receiver)) {}
~MIDIIn() ~MIDIIn()
{ {
@ -282,18 +380,14 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
std::string description() const std::string description() const
{ {
CFStringRef namestr; CFPointer<CFStringRef> namestr;
const char* nameCstr; const char* nameCstr;
if (MIDIObjectGetStringProperty(m_midi, kMIDIPropertyName, &namestr)) if (MIDIObjectGetStringProperty(m_midi, kMIDIPropertyName, &namestr))
return {}; return {};
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8))) if (!(nameCstr = CFStringGetCStringPtr(namestr.get(), kCFStringEncodingUTF8)))
{
CFRelease(namestr);
return {}; return {};
}
CFRelease(namestr);
return nameCstr; return nameCstr;
} }
}; };
@ -303,8 +397,8 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
MIDIEndpointRef m_midi = 0; MIDIEndpointRef m_midi = 0;
MIDIPortRef m_midiPort = 0; MIDIPortRef m_midiPort = 0;
MIDIOut(bool virt) MIDIOut(AQSAudioVoiceEngine* parent, bool virt)
: IMIDIOut(virt) {} : IMIDIOut(parent, virt) {}
~MIDIOut() ~MIDIOut()
{ {
@ -316,18 +410,14 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
std::string description() const std::string description() const
{ {
CFStringRef namestr; CFPointer<CFStringRef> namestr;
const char* nameCstr; const char* nameCstr;
if (MIDIObjectGetStringProperty(m_midi, kMIDIPropertyName, &namestr)) if (MIDIObjectGetStringProperty(m_midi, kMIDIPropertyName, &namestr))
return {}; return {};
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8))) if (!(nameCstr = CFStringGetCStringPtr(namestr.get(), kCFStringEncodingUTF8)))
{
CFRelease(namestr);
return {}; return {};
}
CFRelease(namestr);
return nameCstr; return nameCstr;
} }
@ -359,8 +449,8 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
MIDIEndpointRef m_midiOut = 0; MIDIEndpointRef m_midiOut = 0;
MIDIPortRef m_midiPortOut = 0; MIDIPortRef m_midiPortOut = 0;
MIDIInOut(bool virt, ReceiveFunctor&& receiver) MIDIInOut(AQSAudioVoiceEngine* parent, bool virt, ReceiveFunctor&& receiver)
: IMIDIInOut(virt, std::move(receiver)) {} : IMIDIInOut(parent, virt, std::move(receiver)) {}
~MIDIInOut() ~MIDIInOut()
{ {
@ -376,18 +466,14 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
std::string description() const std::string description() const
{ {
CFStringRef namestr; CFPointer<CFStringRef> namestr;
const char* nameCstr; const char* nameCstr;
if (MIDIObjectGetStringProperty(m_midiIn, kMIDIPropertyName, &namestr)) if (MIDIObjectGetStringProperty(m_midiIn, kMIDIPropertyName, &namestr))
return {}; return {};
if (!(nameCstr = CFStringGetCStringPtr(namestr, kCFStringEncodingUTF8))) if (!(nameCstr = CFStringGetCStringPtr(namestr.get(), kCFStringEncodingUTF8)))
{
CFRelease(namestr);
return {}; return {};
}
CFRelease(namestr);
return nameCstr; return nameCstr;
} }
@ -420,19 +506,24 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!m_midiClient) if (!m_midiClient)
return {}; return {};
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(true, std::move(receiver)); std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(this, true, std::move(receiver));
if (!ret) if (!ret)
return {}; return {};
char name[256]; char name[256];
snprintf(name, 256, "Boo MIDI Virtual In %u", m_midiInCounter++); auto appName = APP->getFriendlyName();
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull); if (!m_midiInCounter)
snprintf(name, 256, "%s MIDI-In", appName.data());
else
snprintf(name, 256, "%s MIDI-In %u", appName.data(), m_midiInCounter);
m_midiInCounter++;
CFPointer<CFStringRef> midiName = CFPointer<CFStringRef>::adopt(
CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull));
OSStatus stat; OSStatus stat;
if ((stat = MIDIDestinationCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc), if ((stat = MIDIDestinationCreate(m_midiClient, midiName.get(), MIDIReadProc(MIDIReceiveProc),
static_cast<IMIDIReceiver*>(ret.get()), static_cast<IMIDIReceiver*>(ret.get()),
&static_cast<MIDIIn&>(*ret).m_midi))) &static_cast<MIDIIn&>(*ret).m_midi)))
ret.reset(); ret.reset();
CFRelease(midiName);
return ret; return ret;
} }
@ -442,16 +533,21 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!m_midiClient) if (!m_midiClient)
return {}; return {};
std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(true); std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(this, true);
if (!ret) if (!ret)
return {}; return {};
char name[256]; char name[256];
snprintf(name, 256, "Boo MIDI Virtual Out %u", m_midiOutCounter++); auto appName = APP->getFriendlyName();
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull); if (!m_midiOutCounter)
if (MIDISourceCreate(m_midiClient, midiName, &static_cast<MIDIOut&>(*ret).m_midi)) snprintf(name, 256, "%s MIDI-Out", appName.data());
else
snprintf(name, 256, "%s MIDI-Out %u", appName.data(), m_midiOutCounter);
m_midiOutCounter++;
CFPointer<CFStringRef> midiName = CFPointer<CFStringRef>::adopt(
CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDISourceCreate(m_midiClient, midiName.get(), &static_cast<MIDIOut&>(*ret).m_midi))
ret.reset(); ret.reset();
CFRelease(midiName);
return ret; return ret;
} }
@ -461,27 +557,36 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!m_midiClient) if (!m_midiClient)
return {}; return {};
std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(true, std::move(receiver)); std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(this, true, std::move(receiver));
if (!ret) if (!ret)
return {}; return {};
char name[256]; char name[256];
snprintf(name, 256, "Boo MIDI Virtual In %u", m_midiInCounter++); auto appName = APP->getFriendlyName();
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull); if (!m_midiInCounter)
if (MIDIDestinationCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc), snprintf(name, 256, "%s MIDI-In", appName.data());
else
snprintf(name, 256, "%s MIDI-In %u", appName.data(), m_midiInCounter);
m_midiInCounter++;
CFPointer<CFStringRef> midiName = CFPointer<CFStringRef>::adopt(
CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDIDestinationCreate(m_midiClient, midiName.get(), MIDIReadProc(MIDIReceiveProc),
static_cast<IMIDIReceiver*>(ret.get()), static_cast<IMIDIReceiver*>(ret.get()),
&static_cast<MIDIInOut&>(*ret).m_midiIn)) &static_cast<MIDIInOut&>(*ret).m_midiIn))
ret.reset(); ret.reset();
CFRelease(midiName);
if (!ret) if (!ret)
return {}; return {};
snprintf(name, 256, "Boo MIDI Virtual Out %u", m_midiOutCounter++); if (!m_midiOutCounter)
midiName = CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull); snprintf(name, 256, "%s MIDI-Out", appName.data());
if (MIDISourceCreate(m_midiClient, midiName, &static_cast<MIDIInOut&>(*ret).m_midiOut)) else
snprintf(name, 256, "%s MIDI-Out %u", appName.data(), m_midiOutCounter);
m_midiOutCounter++;
midiName = CFPointer<CFStringRef>::adopt(
CFStringCreateWithCStringNoCopy(nullptr, name, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDISourceCreate(m_midiClient, midiName.get(), &static_cast<MIDIInOut&>(*ret).m_midiOut))
ret.reset(); ret.reset();
CFRelease(midiName);
return ret; return ret;
} }
@ -495,20 +600,20 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!src) if (!src)
return {}; return {};
std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(false, std::move(receiver)); std::unique_ptr<IMIDIIn> ret = std::make_unique<MIDIIn>(this, false, std::move(receiver));
if (!ret) if (!ret)
return {}; return {};
char mname[256]; char mname[256];
snprintf(mname, 256, "Boo MIDI Real In %u", m_midiInCounter++); snprintf(mname, 256, "Boo MIDI Real In %u", m_midiInCounter++);
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull); CFPointer<CFStringRef> midiName = CFPointer<CFStringRef>::adopt(
if (MIDIInputPortCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc), CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDIInputPortCreate(m_midiClient, midiName.get(), MIDIReadProc(MIDIReceiveProc),
static_cast<IMIDIReceiver*>(ret.get()), static_cast<IMIDIReceiver*>(ret.get()),
&static_cast<MIDIIn&>(*ret).m_midiPort)) &static_cast<MIDIIn&>(*ret).m_midiPort))
ret.reset(); ret.reset();
else else
MIDIPortConnectSource(static_cast<MIDIIn&>(*ret).m_midiPort, src, nullptr); MIDIPortConnectSource(static_cast<MIDIIn&>(*ret).m_midiPort, src, nullptr);
CFRelease(midiName);
return ret; return ret;
} }
@ -522,18 +627,18 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!dst) if (!dst)
return {}; return {};
std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(false); std::unique_ptr<IMIDIOut> ret = std::make_unique<MIDIOut>(this, false);
if (!ret) if (!ret)
return {}; return {};
char mname[256]; char mname[256];
snprintf(mname, 256, "Boo MIDI Real Out %u", m_midiOutCounter++); snprintf(mname, 256, "Boo MIDI Real Out %u", m_midiOutCounter++);
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull); CFPointer<CFStringRef> midiName = CFPointer<CFStringRef>::adopt(
if (MIDIOutputPortCreate(m_midiClient, midiName, &static_cast<MIDIOut&>(*ret).m_midiPort)) CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDIOutputPortCreate(m_midiClient, midiName.get(), &static_cast<MIDIOut&>(*ret).m_midiPort))
ret.reset(); ret.reset();
else else
static_cast<MIDIOut&>(*ret).m_midi = dst; static_cast<MIDIOut&>(*ret).m_midi = dst;
CFRelease(midiName);
return ret; return ret;
} }
@ -551,44 +656,60 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
if (!dst) if (!dst)
return {}; return {};
std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(false, std::move(receiver)); std::unique_ptr<IMIDIInOut> ret = std::make_unique<MIDIInOut>(this, false, std::move(receiver));
if (!ret) if (!ret)
return {}; return {};
char mname[256]; char mname[256];
snprintf(mname, 256, "Boo MIDI Real In %u", m_midiInCounter++); snprintf(mname, 256, "Boo MIDI Real In %u", m_midiInCounter++);
CFStringRef midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull); CFPointer<CFStringRef> midiName = CFPointer<CFStringRef>::adopt(
if (MIDIInputPortCreate(m_midiClient, midiName, MIDIReadProc(MIDIReceiveProc), CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDIInputPortCreate(m_midiClient, midiName.get(), MIDIReadProc(MIDIReceiveProc),
static_cast<IMIDIReceiver*>(ret.get()), static_cast<IMIDIReceiver*>(ret.get()),
&static_cast<MIDIInOut&>(*ret).m_midiPortIn)) &static_cast<MIDIInOut&>(*ret).m_midiPortIn))
ret.reset(); ret.reset();
else else
MIDIPortConnectSource(static_cast<MIDIInOut&>(*ret).m_midiPortIn, src, nullptr); MIDIPortConnectSource(static_cast<MIDIInOut&>(*ret).m_midiPortIn, src, nullptr);
CFRelease(midiName);
if (!ret) if (!ret)
return {}; return {};
snprintf(mname, 256, "Boo MIDI Real Out %u", m_midiOutCounter++); snprintf(mname, 256, "Boo MIDI Real Out %u", m_midiOutCounter++);
midiName = CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull); midiName = CFPointer<CFStringRef>::adopt(
if (MIDIOutputPortCreate(m_midiClient, midiName, &static_cast<MIDIInOut&>(*ret).m_midiPortOut)) CFStringCreateWithCStringNoCopy(nullptr, mname, kCFStringEncodingUTF8, kCFAllocatorNull));
if (MIDIOutputPortCreate(m_midiClient, midiName.get(), &static_cast<MIDIInOut&>(*ret).m_midiPortOut))
ret.reset(); ret.reset();
else else
static_cast<MIDIInOut&>(*ret).m_midiOut = dst; static_cast<MIDIInOut&>(*ret).m_midiOut = dst;
CFRelease(midiName);
return ret; return ret;
} }
bool useMIDILock() const {return true;} bool useMIDILock() const {return true;}
AQSAudioVoiceEngine() static void SampleRateChanged(AQSAudioVoiceEngine* engine,
AudioQueueRef inAQ,
AudioQueuePropertyID inID)
{ {
m_mixInfo.m_channels = _getAvailableSet(); engine->m_needsRebuild = true;
}
void _rebuildAudioQueue()
{
if (m_queue)
{
m_cbRunning = false;
AudioQueueDispose(m_queue, true);
m_cbRunning = true;
m_queue = nullptr;
}
auto setAndRate = _getAvailableSetAndRate();
m_mixInfo.m_channels = setAndRate.first;
unsigned chCount = ChannelCount(m_mixInfo.m_channels); unsigned chCount = ChannelCount(m_mixInfo.m_channels);
AudioStreamBasicDescription desc = {}; AudioStreamBasicDescription desc = {};
desc.mSampleRate = 96000; desc.mSampleRate = setAndRate.second;
desc.mFormatID = kAudioFormatLinearPCM; desc.mFormatID = kAudioFormatLinearPCM;
desc.mFormatFlags = kLinearPCMFormatFlagIsFloat; desc.mFormatFlags = kLinearPCMFormatFlagIsFloat;
desc.mBytesPerPacket = chCount * 4; desc.mBytesPerPacket = chCount * 4;
@ -599,34 +720,26 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
OSStatus err; OSStatus err;
if ((err = AudioQueueNewOutput(&desc, AudioQueueOutputCallback(Callback), if ((err = AudioQueueNewOutput(&desc, AudioQueueOutputCallback(Callback),
this, nullptr, nullptr, 0, &m_queue))) this, CFRunLoopGetCurrent(), m_runLoopMode.get(), 0, &m_queue)))
{ {
Log.report(logvisor::Fatal, "unable to create output audio queue"); Log.report(logvisor::Fatal, "unable to create output audio queue");
return; return;
} }
Float64 actualSampleRate; CFStringRef devName = m_devName.get();
UInt32 argSize = 8; if ((err = AudioQueueSetProperty(m_queue, kAudioQueueProperty_CurrentDevice, &devName, sizeof(devName))))
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"); Log.report(logvisor::Fatal, "unable to set current device into audio queue");
return; return;
} }
desc.mSampleRate = actualSampleRate; AudioQueueAddPropertyListener(m_queue, kAudioQueueDeviceProperty_SampleRate,
if ((err = AudioQueueNewOutput(&desc, AudioQueueOutputCallback(Callback), AudioQueuePropertyListenerProc(SampleRateChanged), this);
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_sampleRate = desc.mSampleRate;
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I; m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
m_mixInfo.m_bitsPerSample = 32; m_mixInfo.m_bitsPerSample = 32;
m_5msFrames = actualSampleRate * 5 / 1000; m_5msFrames = desc.mSampleRate * 5 / 1000;
ChannelMap& chMapOut = m_mixInfo.m_channelMap; ChannelMap& chMapOut = m_mixInfo.m_channelMap;
chMapOut.m_channelCount = 0; chMapOut.m_channelCount = 0;
@ -741,8 +854,8 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
while (chMapOut.m_channelCount < chCount) while (chMapOut.m_channelCount < chCount)
chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::Unknown; chMapOut.m_channels[chMapOut.m_channelCount++] = AudioChannel::Unknown;
m_mixInfo.m_periodFrames = m_5msFrames * 3; m_mixInfo.m_periodFrames = m_5msFrames;
for (int i=0 ; i<3 ; ++i) for (int i=0 ; i<AQS_NUM_BUFFERS ; ++i)
if (AudioQueueAllocateBuffer(m_queue, m_mixInfo.m_periodFrames * chCount * 4, &m_buffers[i])) if (AudioQueueAllocateBuffer(m_queue, m_mixInfo.m_periodFrames * chCount * 4, &m_buffers[i]))
{ {
Log.report(logvisor::Fatal, "unable to create audio queue buffer"); Log.report(logvisor::Fatal, "unable to create audio queue buffer");
@ -753,7 +866,9 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4; m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4;
for (unsigned i=0 ; i<3 ; ++i) _resetSampleRate();
for (unsigned i=0 ; i<AQS_NUM_BUFFERS ; ++i)
{ {
memset(m_buffers[i]->mAudioData, 0, m_frameBytes); memset(m_buffers[i]->mAudioData, 0, m_frameBytes);
m_buffers[i]->mAudioDataByteSize = m_frameBytes; m_buffers[i]->mAudioDataByteSize = m_frameBytes;
@ -761,6 +876,51 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
} }
AudioQueuePrime(m_queue, 0, nullptr); AudioQueuePrime(m_queue, 0, nullptr);
AudioQueueStart(m_queue, nullptr); AudioQueueStart(m_queue, nullptr);
}
static OSStatus AudioDeviceChanged(AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress* inAddresses,
AQSAudioVoiceEngine* engine)
{
AudioObjectID defaultDeviceId;
UInt32 argSize = sizeof(defaultDeviceId);
if (AudioObjectGetPropertyData(inObjectID, inAddresses, 0, NULL, &argSize, &defaultDeviceId) == noErr) {
argSize = sizeof(CFStringRef);
AudioObjectPropertyAddress deviceAddress;
deviceAddress.mSelector = kAudioDevicePropertyDeviceUID;
AudioObjectGetPropertyData(defaultDeviceId, &deviceAddress, 0, NULL, &argSize, &engine->m_devName);
}
engine->m_needsRebuild = true;
return noErr;
}
AQSAudioVoiceEngine()
: m_runLoopMode(CFPointer<CFStringRef>::adopt(
CFStringCreateWithCStringNoCopy(nullptr, "BooAQSMode", kCFStringEncodingUTF8, kCFAllocatorNull)))
{
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
AudioObjectID defaultDeviceId;
UInt32 argSize = sizeof(defaultDeviceId);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &argSize, &defaultDeviceId) == noErr) {
argSize = sizeof(CFStringRef);
AudioObjectPropertyAddress deviceAddress;
deviceAddress.mSelector = kAudioDevicePropertyDeviceUID;
AudioObjectGetPropertyData(defaultDeviceId, &deviceAddress, 0, NULL, &argSize, &m_devName);
} else {
Log.report(logvisor::Fatal, "unable determine default audio device");
return;
}
propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propertyAddress,
AudioObjectPropertyListenerProc(AudioDeviceChanged), this);
_rebuildAudioQueue();
/* Also create shared MIDI client */ /* Also create shared MIDI client */
MIDIClientCreate(CFSTR("Boo MIDI"), nullptr, nullptr, &m_midiClient); MIDIClientCreate(CFSTR("Boo MIDI"), nullptr, nullptr, &m_midiClient);
@ -769,50 +929,20 @@ struct AQSAudioVoiceEngine : BaseAudioVoiceEngine
~AQSAudioVoiceEngine() ~AQSAudioVoiceEngine()
{ {
m_cbRunning = false; m_cbRunning = false;
if (m_inCb)
m_engineEnterCv.notify_one();
AudioQueueDispose(m_queue, true); AudioQueueDispose(m_queue, true);
if (m_midiClient) if (m_midiClient)
MIDIClientDispose(m_midiClient); MIDIClientDispose(m_midiClient);
} }
/* This is temperamental for AudioQueueServices
* (which has unpredictable buffering windows).
* _pumpAndMixVoicesRetrace() is highly recommended. */
void pumpAndMixVoices() void pumpAndMixVoices()
{ {
std::unique_lock<std::mutex> lk(m_engineMutex); while (CFRunLoopRunInMode(m_runLoopMode.get(), 0, true) == kCFRunLoopRunHandledSource) {}
if (m_inCb) if (m_needsRebuild)
{ {
/* Wake up callback */ _rebuildAudioQueue();
m_engineEnterCv.notify_one(); m_needsRebuild = false;
/* 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> NewAudioVoiceEngine()

View File

@ -9,7 +9,7 @@
#include <IOKit/IOCFPlugIn.h> #include <IOKit/IOCFPlugIn.h>
#include <sys/utsname.h> #include <sys/utsname.h>
#include "IOKitPointer.hpp" #include "IOKitPointer.hpp"
#include "CFPointer.hpp" #include "../CFPointer.hpp"
namespace boo namespace boo
{ {
@ -182,9 +182,9 @@ class HIDListenerIOKit : public IHIDListener
/* Game controllers only */ /* Game controllers only */
CFPointer<CFNumberRef> usagePage; CFPointer<CFNumberRef> usagePage;
dev->getProperty(dev.storage(), CFSTR(kIOHIDPrimaryUsagePageKey), &usagePage); dev->getProperty(dev.storage(), CFSTR(kIOHIDPrimaryUsagePageKey), (CFTypeRef*)&usagePage);
CFPointer<CFNumberRef> usage; CFPointer<CFNumberRef> usage;
dev->getProperty(dev.storage(), CFSTR(kIOHIDPrimaryUsageKey), &usage); dev->getProperty(dev.storage(), CFSTR(kIOHIDPrimaryUsageKey), (CFTypeRef*)&usage);
int usagePageV, usageV; int usagePageV, usageV;
CFNumberGetValue(usagePage.get(), kCFNumberIntType, &usagePageV); CFNumberGetValue(usagePage.get(), kCFNumberIntType, &usagePageV);
CFNumberGetValue(usage.get(), kCFNumberIntType, &usageV); CFNumberGetValue(usage.get(), kCFNumberIntType, &usageV);
@ -199,14 +199,14 @@ class HIDListenerIOKit : public IHIDListener
} }
CFPointer<CFNumberRef> vid, pid; CFPointer<CFNumberRef> vid, pid;
dev->getProperty(dev.storage(), CFSTR(kIOHIDVendorIDKey), &vid); dev->getProperty(dev.storage(), CFSTR(kIOHIDVendorIDKey), (CFTypeRef*)&vid);
dev->getProperty(dev.storage(), CFSTR(kIOHIDProductIDKey), &pid); dev->getProperty(dev.storage(), CFSTR(kIOHIDProductIDKey), (CFTypeRef*)&pid);
CFNumberGetValue(vid.get(), kCFNumberIntType, &vidv); CFNumberGetValue(vid.get(), kCFNumberIntType, &vidv);
CFNumberGetValue(pid.get(), kCFNumberIntType, &pidv); CFNumberGetValue(pid.get(), kCFNumberIntType, &pidv);
CFPointer<CFStringRef> vstridx, pstridx; CFPointer<CFStringRef> vstridx, pstridx;
dev->getProperty(dev.storage(), CFSTR(kIOHIDManufacturerKey), &vstridx); dev->getProperty(dev.storage(), CFSTR(kIOHIDManufacturerKey), (CFTypeRef*)&vstridx);
dev->getProperty(dev.storage(), CFSTR(kIOHIDProductKey), &pstridx); dev->getProperty(dev.storage(), CFSTR(kIOHIDProductKey), (CFTypeRef*)&pstridx);
if (vstridx) if (vstridx)
CFStringGetCString(vstridx.get(), vstr, 128, kCFStringEncodingUTF8); CFStringGetCString(vstridx.get(), vstr, 128, kCFStringEncodingUTF8);

View File

@ -1,7 +1,7 @@
#ifndef __IOKITPOINTER_HPP__ #ifndef __IOKITPOINTER_HPP__
#define __IOKITPOINTER_HPP__ #define __IOKITPOINTER_HPP__
#include "CFPointer.hpp" #include "../CFPointer.hpp"
#include <IOKit/IOTypes.h> #include <IOKit/IOTypes.h>
#include <IOKit/IOKitLib.h> #include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h> #include <IOKit/IOCFPlugIn.h>

View File

@ -123,7 +123,6 @@ protected:
std::mutex m_dlmt; std::mutex m_dlmt;
std::condition_variable m_dlcv; std::condition_variable m_dlcv;
IAudioVoiceEngine* m_voxEngine = nullptr;
static CVReturn DLCallback(CVDisplayLinkRef displayLink, static CVReturn DLCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp * inNow, const CVTimeStamp * inNow,
@ -133,10 +132,7 @@ protected:
GraphicsContextCocoa* ctx) GraphicsContextCocoa* ctx)
{ {
std::unique_lock<std::mutex> lk(ctx->m_dlmt); std::unique_lock<std::mutex> lk(ctx->m_dlmt);
if (ctx->m_voxEngine) ctx->m_dlcv.notify_one();
ctx->m_voxEngine->_retraceBreak();
else
ctx->m_dlcv.notify_one();
return kCVReturnSuccess; return kCVReturnSuccess;
} }
@ -152,21 +148,10 @@ public:
std::recursive_mutex m_callbackMutex; std::recursive_mutex m_callbackMutex;
IWindowCallback* m_callback = nullptr; IWindowCallback* m_callback = nullptr;
void waitForRetrace(IAudioVoiceEngine* voxEngine) void waitForRetrace()
{ {
std::unique_lock<std::mutex> lk(m_dlmt); std::unique_lock<std::mutex> lk(m_dlmt);
if (voxEngine) m_dlcv.wait(lk);
{
m_voxEngine = voxEngine;
lk.unlock();
voxEngine->_pumpAndMixVoicesRetrace();
lk.lock();
m_voxEngine = nullptr;
}
else
{
m_dlcv.wait(lk);
}
} }
virtual BooCocoaResponder* responder() const=0; virtual BooCocoaResponder* responder() const=0;
}; };
@ -1570,9 +1555,9 @@ public:
}); });
} }
void waitForRetrace(IAudioVoiceEngine* voxEngine) void waitForRetrace()
{ {
static_cast<GraphicsContextCocoa*>(m_gfxCtx)->waitForRetrace(voxEngine); static_cast<GraphicsContextCocoa*>(m_gfxCtx)->waitForRetrace();
} }
uintptr_t getPlatformHandle() const uintptr_t getPlatformHandle() const