rusty_link 0.4.9

Rust bindings for Ableton Link through the official C Wrapper (abl_link)
Documentation
/* 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 <http://www.gnu.org/licenses/>.
 *
 *  If you would like to incorporate Link into a proprietary software application,
 *  please contact <link-devs@ableton.com>.
 */

#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;

  // 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 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;
  }
}

} // namespace linkaudio
} // namespace ableton