#include <Comdef.h>
#include <chrono>
namespace ableton
{
namespace linkaudio
{
static const IID kMMDeviceEnumeratorId = __uuidof(MMDeviceEnumerator);
static const IID kIMMDeviceEnumeratorId = __uuidof(IMMDeviceEnumerator);
static const IID kAudioClientId = __uuidof(IAudioClient);
static const IID kAudioRenderClientId = __uuidof(IAudioRenderClient);
static const REFERENCE_TIME kBufferDuration = 1000000;
static const DWORD kWaitTimeoutInMs = 2000;
void fatalError(HRESULT result, LPCTSTR context)
{
if (result > 0)
{
_com_error error(result);
LPCTSTR errorMessage = error.ErrorMessage();
std::cerr << context << ": " << errorMessage << std::endl;
}
else
{
std::cerr << context << std::endl;
}
std::terminate();
}
template <typename Link>
DWORD renderAudioRunloop(LPVOID lpParam)
{
AudioPlatform<Link>* platform = static_cast<AudioPlatform<Link>*>(lpParam);
return platform->audioRunloop();
}
template <typename Link>
AudioPlatform<Link>::AudioPlatform(Link& link)
: mEngine(link)
, mSampleTime(0)
, mDevice(nullptr)
, mAudioClient(nullptr)
, mRenderClient(nullptr)
, mStreamFormat(nullptr)
, mEventHandle(nullptr)
, mAudioThreadHandle(nullptr)
, mIsRunning(false)
{
initialize();
mEngine.setNumFrames(bufferSize());
mEngine.setSampleRate(mStreamFormat->nSamplesPerSec);
start();
}
template <typename Link>
AudioPlatform<Link>::~AudioPlatform()
{
if (mDevice != nullptr)
{
mDevice->Release();
}
if (mAudioClient != nullptr)
{
mAudioClient->Release();
}
if (mRenderClient != nullptr)
{
mRenderClient->Release();
}
CoTaskMemFree(mStreamFormat);
}
template <typename Link>
UINT32 AudioPlatform<Link>::bufferSize()
{
UINT32 bufferSize;
HRESULT result = mAudioClient->GetBufferSize(&bufferSize);
if (FAILED(result))
{
fatalError(result, "Could not get buffer size");
return 0; }
return bufferSize;
}
template <typename Link>
void AudioPlatform<Link>::initialize()
{
HRESULT result = CoInitialize(nullptr);
if (FAILED(result))
{
fatalError(result, "Could not initialize COM library");
}
IMMDeviceEnumerator* enumerator = nullptr;
result = CoCreateInstance(kMMDeviceEnumeratorId,
nullptr,
CLSCTX_ALL,
kIMMDeviceEnumeratorId,
(void**)&enumerator);
if (FAILED(result))
{
fatalError(result, "Could not create device instance");
}
result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &(mDevice));
if (FAILED(result))
{
fatalError(result, "Could not get default audio endpoint");
}
else
{
enumerator->Release();
enumerator = nullptr;
}
result =
mDevice->Activate(kAudioClientId, CLSCTX_ALL, nullptr, (void**)&(mAudioClient));
if (FAILED(result))
{
fatalError(result, "Could not activate audio device");
}
result = mAudioClient->GetMixFormat(&(mStreamFormat));
if (FAILED(result))
{
fatalError(result, "Could not get mix format");
}
if (mStreamFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
WAVEFORMATEXTENSIBLE* streamFormatEx =
reinterpret_cast<WAVEFORMATEXTENSIBLE*>(mStreamFormat);
if (streamFormatEx->SubFormat != KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
{
fatalError(0, "Sorry, only IEEE floating point streams are supported");
}
}
else
{
fatalError(0, "Sorry, only extensible wave streams are supported");
}
result = mAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
kBufferDuration,
0,
mStreamFormat,
nullptr);
if (FAILED(result))
{
fatalError(result, "Could not initialize audio device");
}
mEventHandle = CreateEvent(nullptr, false, false, nullptr);
if (mEventHandle == nullptr)
{
fatalError(result, "Could not create event handle");
}
result = mAudioClient->GetService(kAudioRenderClientId, (void**)&(mRenderClient));
if (FAILED(result))
{
fatalError(result, "Could not get audio render service");
}
mIsRunning = true;
LPTHREAD_START_ROUTINE threadEntryPoint =
reinterpret_cast<LPTHREAD_START_ROUTINE>(renderAudioRunloop<Link>);
mAudioThreadHandle = CreateThread(nullptr, 0, threadEntryPoint, this, 0, nullptr);
if (mAudioThreadHandle == nullptr)
{
fatalError(GetLastError(), "Could not create audio thread");
}
}
template <typename Link>
void AudioPlatform<Link>::start()
{
UINT32 bufSize = bufferSize();
BYTE* buffer;
HRESULT result = mRenderClient->GetBuffer(bufSize, &buffer);
if (FAILED(result))
{
fatalError(result, "Could not get render client buffer (in start audio engine)");
}
result = mRenderClient->ReleaseBuffer(bufSize, 0);
if (FAILED(result))
{
fatalError(result, "Could not release buffer");
}
result = mAudioClient->SetEventHandle(mEventHandle);
if (FAILED(result))
{
fatalError(result, "Could not set event handle to audio client");
}
REFERENCE_TIME latency;
result = mAudioClient->GetStreamLatency(&latency);
if (FAILED(result))
{
fatalError(result, "Could not get stream latency");
}
result = mAudioClient->Start();
if (FAILED(result))
{
fatalError(result, "Could not start audio client");
}
}
template <typename Link>
DWORD AudioPlatform<Link>::audioRunloop()
{
while (mIsRunning)
{
DWORD wait = WaitForSingleObject(mEventHandle, kWaitTimeoutInMs);
if (wait != WAIT_OBJECT_0)
{
mIsRunning = false;
mAudioClient->Stop();
return wait;
}
UINT32 paddingFrames;
HRESULT result = mAudioClient->GetCurrentPadding(&paddingFrames);
if (FAILED(result))
{
fatalError(result, "Could not get number of padding frames");
}
const UINT32 numSamples = bufferSize() - paddingFrames;
BYTE* buffer;
result = mRenderClient->GetBuffer(numSamples, &buffer);
if (FAILED(result))
{
fatalError(result, "Could not get render client buffer (in callback)");
}
const double sampleRate = static_cast<double>(mStreamFormat->nSamplesPerSec);
using namespace std::chrono;
const auto bufferDuration =
duration_cast<microseconds>(duration<double>{numSamples / sampleRate});
const auto hostTime = mHostTimeFilter.sampleTimeToHostTime(mSampleTime);
mSampleTime += numSamples;
const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency.load();
mEngine.audioCallback(bufferBeginAtOutput, numSamples);
float* floatBuffer = reinterpret_cast<float*>(buffer);
for (WORD i = 0; i < numSamples; ++i)
{
if (i >= mEngine.mBuffers[0].size())
{
break;
}
for (WORD j = 0; j < mStreamFormat->nChannels; ++j)
{
floatBuffer[j + (i * mStreamFormat->nChannels)] =
static_cast<float>(mEngine.mBuffers[j][i]);
}
}
result = mRenderClient->ReleaseBuffer(numSamples, 0);
if (FAILED(result))
{
fatalError(result, "Error rendering data");
}
}
mIsRunning = false;
return 0;
}
} }