/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_CoreAudio.hpp" #include #include #include namespace ableton { namespace linkaudio { AudioPlatform::AudioPlatform(Link& link) : mEngine(link) { initialize(); start(); } AudioPlatform::~AudioPlatform() { stop(); uninitialize(); } OSStatus AudioPlatform::audioCallback(void* inRefCon, AudioUnitRenderActionFlags*, const AudioTimeStamp* inTimeStamp, UInt32, UInt32 inNumberFrames, AudioBufferList* ioData) { AudioEngine* engine = static_cast(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) { SInt16* bufData = static_cast(ioData->mBuffers[j].mData); bufData[i] = static_cast(32761. * engine->mBuffer[i]); } } return noErr; } void AudioPlatform::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; } 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 bufferSize = 512; size = sizeof(bufferSize); result = AudioUnitSetProperty(mIoUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &bufferSize, size); if (result) { std::cerr << "Could not set buffer size. " << result << std::endl; std::terminate(); } mEngine.setBufferSize(bufferSize); 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; // the buffer, stream and safety-offset latencies are part of inTimeStamp->mHostTime // within the audio callback. 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 double latency = static_cast(deviceLatency) / mEngine.mSampleRate; mEngine.mOutputLatency.store(duration_cast(duration{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; } } void AudioPlatform::uninitialize() { OSStatus result = AudioUnitUninitialize(mIoUnit); if (result) { std::cerr << "Could not uninitialize Audio Unit. " << result << std::endl; } } void AudioPlatform::start() { OSStatus result = AudioOutputUnitStart(mIoUnit); if (result) { std::cerr << "Could not start Audio Unit. " << result << std::endl; std::terminate(); } } void AudioPlatform::stop() { OSStatus result = AudioOutputUnitStop(mIoUnit); if (result) { std::cerr << "Could not stop Audio Unit. " << result << std::endl; } } } // namespace linkaudio } // namespace ableton