#include "AudioPlatform_CoreAudio.hpp"
#include <chrono>
#include <iostream>
#include <mach/mach_time.h>
namespace ableton
{
namespace linkaudio
{
template <typename Link>
AudioPlatform<Link>::AudioPlatform(Link& link)
: mEngine(link)
{
initialize();
start();
}
template <typename Link>
AudioPlatform<Link>::~AudioPlatform()
{
stop();
uninitialize();
}
template <typename Link>
OSStatus AudioPlatform<Link>::audioCallback(void* inRefCon,
AudioUnitRenderActionFlags*,
const AudioTimeStamp* inTimeStamp,
UInt32,
UInt32 inNumberFrames,
AudioBufferList* ioData)
{
auto engine = static_cast<AudioEngine<Link>*>(inRefCon);
const auto bufferBeginAtOutput =
engine->mLink.clock().ticksToMicros(inTimeStamp->mHostTime)
+ engine->mOutputLatency.load();
engine->audioCallback(bufferBeginAtOutput, inNumberFrames);
for (std::size_t i = 0; i < inNumberFrames; ++i)
{
for (UInt32 j = 0; j < ioData->mNumberBuffers; ++j)
{
auto bufData = static_cast<SInt16*>(ioData->mBuffers[j].mData);
bufData[i] = static_cast<SInt16>(32761. * engine->mBuffers[j][i]);
}
}
return noErr;
}
template <typename Link>
void AudioPlatform<Link>::streamFormatCallback(void* inRefCon,
AudioUnit inUnit,
AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement)
{
#pragma unused(inID)
if (inScope == kAudioUnitScope_Output && inElement == 0)
{
AudioStreamBasicDescription asbd;
UInt32 dataSize = sizeof(asbd);
OSStatus result = AudioUnitGetProperty(inUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
0,
&asbd,
&dataSize);
if (result)
{
std::cerr << "Could not get Audio Unit stream format. " << result << std::endl;
}
auto pPlatform = static_cast<AudioPlatform<Link>*>(inRefCon);
const Float64 oldSampleRate = pPlatform->mEngine.mSampleRate;
if (fabs(oldSampleRate - asbd.mSampleRate) > 1.)
{
std::cout << "CORE AUDIO STREAM FORMAT CHANGED: " << asbd.mSampleRate << std::endl;
pPlatform->stop();
pPlatform->uninitialize();
pPlatform->mEngine.setSampleRate(asbd.mSampleRate);
pPlatform->initialize();
pPlatform->start();
}
}
}
template <typename Link>
void AudioPlatform<Link>::initialize()
{
AudioComponentDescription cd = {};
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_DefaultOutput;
AudioComponent component = AudioComponentFindNext(nullptr, &cd);
OSStatus result = AudioComponentInstanceNew(component, &mIoUnit);
if (result)
{
std::cerr << "Could not get Audio Unit. " << result << std::endl;
std::terminate();
}
UInt32 size = sizeof(mEngine.mSampleRate);
result = AudioUnitGetProperty(mIoUnit,
kAudioUnitProperty_SampleRate,
kAudioUnitScope_Output,
0,
&mEngine.mSampleRate,
&size);
if (result)
{
std::cerr << "Could not get sample rate. " << result << std::endl;
std::terminate();
}
std::clog << "SAMPLE RATE: " << mEngine.mSampleRate << std::endl;
AudioStreamBasicDescription asbd = {};
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsNonInterleaved;
asbd.mChannelsPerFrame = 2;
asbd.mBytesPerPacket = sizeof(SInt16);
asbd.mFramesPerPacket = 1;
asbd.mBytesPerFrame = sizeof(SInt16);
asbd.mBitsPerChannel = 8 * sizeof(SInt16);
asbd.mSampleRate = mEngine.mSampleRate;
result = AudioUnitSetProperty(mIoUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&asbd,
sizeof(asbd));
if (result)
{
std::cerr << "Could not set stream format. " << result << std::endl;
}
result = AudioUnitAddPropertyListener(
mIoUnit, kAudioUnitProperty_StreamFormat, streamFormatCallback, this);
if (result)
{
std::cerr << "Could not add listener to stream format changes. " << result
<< std::endl;
}
char deviceName[512];
size = sizeof(deviceName);
result = AudioUnitGetProperty(mIoUnit,
kAudioDevicePropertyDeviceName,
kAudioUnitScope_Global,
0,
&deviceName,
&size);
if (result)
{
std::cerr << "Could not get device name. " << result << std::endl;
std::terminate();
}
std::clog << "DEVICE NAME: " << deviceName << std::endl;
UInt32 numFrames = 512;
size = sizeof(numFrames);
result = AudioUnitSetProperty(mIoUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&numFrames,
size);
if (result)
{
std::cerr << "Could not set num frames. " << result << std::endl;
std::terminate();
}
mEngine.setNumFrames(numFrames);
UInt32 propertyResult = 0;
size = sizeof(propertyResult);
result = AudioUnitGetProperty(mIoUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&propertyResult,
&size);
if (result)
{
std::cerr << "Could not get buffer size. " << result << std::endl;
std::terminate();
}
std::clog << "BUFFER SIZE: " << propertyResult << " samples, "
<< propertyResult / mEngine.mSampleRate * 1e3 << " ms." << std::endl;
UInt32 deviceLatency = 0;
size = sizeof(deviceLatency);
result = AudioUnitGetProperty(mIoUnit,
kAudioDevicePropertyLatency,
kAudioUnitScope_Output,
0,
&deviceLatency,
&size);
if (result)
{
std::cerr << "Could not get output device latency. " << result << std::endl;
std::terminate();
}
std::clog << "OUTPUT DEVICE LATENCY: " << deviceLatency << " samples, "
<< deviceLatency / mEngine.mSampleRate * 1e3 << " ms." << std::endl;
using namespace std::chrono;
const auto latency = static_cast<double>(deviceLatency) / mEngine.mSampleRate;
mEngine.mOutputLatency.store(duration_cast<microseconds>(duration<double>{latency}));
AURenderCallbackStruct ioRemoteInput;
ioRemoteInput.inputProc = audioCallback;
ioRemoteInput.inputProcRefCon = &mEngine;
result = AudioUnitSetProperty(mIoUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&ioRemoteInput,
sizeof(ioRemoteInput));
if (result)
{
std::cerr << "Could not set render callback. " << result << std::endl;
}
result = AudioUnitInitialize(mIoUnit);
if (result)
{
std::cerr << "Could not initialize audio unit. " << result << std::endl;
}
}
template <typename Link>
void AudioPlatform<Link>::uninitialize()
{
OSStatus result = AudioUnitUninitialize(mIoUnit);
if (result)
{
std::cerr << "Could not uninitialize Audio Unit. " << result << std::endl;
}
}
template <typename Link>
void AudioPlatform<Link>::start()
{
OSStatus result = AudioOutputUnitStart(mIoUnit);
if (result)
{
std::cerr << "Could not start Audio Unit. " << result << std::endl;
std::terminate();
}
}
template <typename Link>
void AudioPlatform<Link>::stop()
{
OSStatus result = AudioOutputUnitStop(mIoUnit);
if (result)
{
std::cerr << "Could not stop Audio Unit. " << result << std::endl;
}
}
} }