#pragma once
#if defined(LINK_AUDIO)
#include <ableton/LinkAudio.hpp>
#include <ableton/link_audio/Buffer.hpp>
#include <ableton/link_audio/Queue.hpp>
#include <ableton/util/FloatIntConversion.hpp>
namespace ableton
{
namespace linkaudio
{
namespace
{
template <typename T>
T cubicInterpolate(const std::array<T, 4>& p, double t)
{
double a = -0.5 * static_cast<double>(p[0]) + 1.5 * static_cast<double>(p[1])
- 1.5 * static_cast<double>(p[2]) + 0.5 * static_cast<double>(p[3]);
double b = static_cast<double>(p[0]) - 2.5 * static_cast<double>(p[1])
+ 2.0 * static_cast<double>(p[2]) - 0.5 * static_cast<double>(p[3]);
double c = -0.5 * static_cast<double>(p[0]) + 0.5 * static_cast<double>(p[2]);
auto d = static_cast<double>(p[1]);
return static_cast<T>(a * t * t * t + b * t * t + c * t + d);
}
template <typename T>
T linearInterpolate(T value, T inMin, T inMax, T outMin, T outMax)
{
return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
}
template <typename Link>
class LinkAudioRenderer
{
struct Buffer
{
std::array<double, 512> mSamples; LinkAudioSource::BufferHandle::Info mInfo;
};
using Queue = link_audio::Queue<Buffer>;
public:
LinkAudioRenderer(Link& link, double& sampleRate)
: mLink(link)
, mSink(mLink, "A Sink", 4096)
, mSampleRate(sampleRate)
{
auto queue = Queue(2048, {});
mpQueueWriter = std::make_shared<typename Queue::Writer>(std::move(queue.writer()));
mpQueueReader = std::make_shared<typename Queue::Reader>(std::move(queue.reader()));
}
~LinkAudioRenderer() { mpSource.reset(); }
void send(double* pSamples,
size_t numFrames,
typename Link::SessionState sessionState,
double sampleRate,
const std::chrono::microseconds hostTime,
double quantum)
{
auto buffer = LinkAudioSink::BufferHandle(mSink);
if (buffer)
{
for (auto i = 0u; i < numFrames; ++i)
{
buffer.samples[i] = ableton::util::floatToInt16(pSamples[i]);
}
const auto beatsAtBufferBegin = sessionState.beatAtTime(hostTime, quantum);
buffer.commit(sessionState,
beatsAtBufferBegin,
quantum,
static_cast<uint32_t>(numFrames),
1, static_cast<uint32_t>(sampleRate));
}
}
void receive(double* pRightSamples,
size_t numFrames,
typename Link::SessionState sessionState,
double sampleRate,
const std::chrono::microseconds hostTime,
double quantum)
{
while (mpQueueReader->retainSlot())
{
}
constexpr auto kLatencyInBeats = 4;
const auto targetBeatsAtBufferBegin =
sessionState.beatAtTime(hostTime, quantum) - kLatencyInBeats;
const auto targetBeatsAtBufferEnd =
sessionState.beatAtTime(
hostTime
+ std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::duration<double>((double(numFrames)) / sampleRate)),
quantum)
- kLatencyInBeats;
while (!moStartReadPos && mpQueueReader->numRetainedSlots() > 0)
{
if ((*mpQueueReader)[0]->mInfo.endBeats(sessionState, quantum)
< targetBeatsAtBufferBegin)
{
mpQueueReader->releaseSlot();
}
else
{
break;
}
}
if (mpQueueReader->numRetainedSlots() == 0)
{
moLastFrameIdx = std::nullopt;
moStartReadPos = std::nullopt;
mBuffered = 0;
return;
}
if (!moStartReadPos
&& (*mpQueueReader)[0]->mInfo.beginBeats(sessionState, quantum)
> targetBeatsAtBufferBegin)
{
moLastFrameIdx = std::nullopt;
moStartReadPos = std::nullopt;
mBuffered = 0;
return;
}
if (!moStartReadPos)
{
const auto& info = (*mpQueueReader)[0]->mInfo;
const auto startBufferBegin = *info.beginBeats(sessionState, quantum);
const auto startBufferEnd = *info.endBeats(sessionState, quantum);
moStartReadPos = linearInterpolate(targetBeatsAtBufferBegin,
startBufferBegin,
startBufferEnd,
0.0,
double(info.numFrames));
}
const auto startFramePos = *moStartReadPos;
auto totalFrames = 0.0;
auto foundEnd = false;
for (auto i = 0u; i < mpQueueReader->numRetainedSlots(); ++i)
{
const auto& info = (*mpQueueReader)[i]->mInfo;
const auto bufferBegin = *info.beginBeats(sessionState, quantum);
const auto bufferEnd = *info.endBeats(sessionState, quantum);
if (targetBeatsAtBufferEnd >= bufferBegin && targetBeatsAtBufferEnd < bufferEnd)
{
const auto targetBeatsFrame = linearInterpolate(
targetBeatsAtBufferEnd, bufferBegin, bufferEnd, 0.0, double(info.numFrames));
totalFrames += targetBeatsFrame;
foundEnd = true;
break;
}
else
{
totalFrames += double(info.numFrames);
}
}
if (!foundEnd)
{
moLastFrameIdx = std::nullopt;
moStartReadPos = std::nullopt;
mBuffered = 0;
return;
}
totalFrames -= startFramePos;
if (totalFrames <= 0.0)
{
moLastFrameIdx = std::nullopt;
moStartReadPos = std::nullopt;
mBuffered = 0;
return;
}
const auto frameIncrement = totalFrames / double(numFrames);
auto readPos = startFramePos;
auto getSample = [&](size_t idx) -> double
{
size_t bufferIdx = 0;
size_t currentIdx = idx;
while (bufferIdx < mpQueueReader->numRetainedSlots())
{
auto& currentBuffer = *((*mpQueueReader)[bufferIdx]);
if (currentIdx < currentBuffer.mInfo.numFrames)
{
return currentBuffer.mSamples[currentIdx];
}
currentIdx -= currentBuffer.mInfo.numFrames;
++bufferIdx;
}
return 0.0;
};
for (auto frame = 0u; frame < numFrames; ++frame)
{
const auto framePos = readPos + frame * frameIncrement;
const auto frameIdx = static_cast<size_t>(std::floor(framePos));
const auto t = framePos - std::floor(framePos);
while (!moLastFrameIdx || (moLastFrameIdx && frameIdx > *moLastFrameIdx))
{
mReceiverSampleCache[3] = mReceiverSampleCache[2];
mReceiverSampleCache[2] = mReceiverSampleCache[1];
mReceiverSampleCache[1] = mReceiverSampleCache[0];
mReceiverSampleCache[0] = (frameIdx > 0) ? getSample(frameIdx - 1) : getSample(0);
moLastFrameIdx = moLastFrameIdx ? (*moLastFrameIdx + 1) : frameIdx;
}
pRightSamples[frame] = cubicInterpolate(mReceiverSampleCache, t);
const auto& currentInfo = (*mpQueueReader)[0]->mInfo;
if (frameIdx >= currentInfo.numFrames)
{
readPos -= double(currentInfo.numFrames);
moLastFrameIdx = frameIdx - currentInfo.numFrames;
mpQueueReader->releaseSlot();
}
}
*moStartReadPos = readPos + double(numFrames) * frameIncrement;
auto buffered =
-static_cast<float>(*moStartReadPos) / float((*mpQueueReader)[0]->mInfo.sampleRate);
if (mpQueueReader->numRetainedSlots() > 0)
{
for (auto i = 1u; i < mpQueueReader->numRetainedSlots(); ++i)
{
const auto& info = (*mpQueueReader)[i]->mInfo;
buffered += float(info.numFrames) / float(info.sampleRate);
}
}
mBuffered = buffered;
}
void operator()(double* pLeftSamples,
double* pRightSamples,
size_t numFrames,
typename Link::SessionState sessionState,
double sampleRate,
const std::chrono::microseconds hostTime,
double quantum)
{
send(pLeftSamples, numFrames, sessionState, sampleRate, hostTime, quantum);
receive(pRightSamples, numFrames, sessionState, sampleRate, hostTime, quantum);
}
bool hasSource() const { return mpSource != nullptr; }
void removeSource()
{
if (mpSource)
{
mpSource.reset();
while (mpQueueReader->retainSlot())
{
}
while (mpQueueReader->numRetainedSlots() > 0)
{
mpQueueReader->releaseSlot();
}
moLastFrameIdx = std::nullopt;
moStartReadPos = std::nullopt;
}
}
float buffered() const { return mBuffered; }
void createSource(const ChannelId& channelId)
{
mpSource = std::make_unique<LinkAudioSource>(
mLink,
channelId,
[this](ableton::LinkAudioSource::BufferHandle bufferHandle)
{ onSourceBuffer(bufferHandle); });
}
void onSourceBuffer(const LinkAudioSource::BufferHandle bufferHandle)
{
if (mpQueueWriter->retainSlot())
{
auto& buffer = *((*mpQueueWriter)[0]);
buffer.mInfo = bufferHandle.info;
buffer.mInfo.numChannels = 1;
for (auto i = 0u; i < bufferHandle.info.numFrames; ++i)
{
buffer.mSamples[i] = util::int16ToFloat<double>(
bufferHandle.samples[i * bufferHandle.info.numChannels]);
}
mpQueueWriter->releaseSlot();
}
}
Link& mLink;
LinkAudioSink mSink;
std::unique_ptr<LinkAudioSource> mpSource;
double& mSampleRate;
std::optional<double> moStartReadPos;
std::atomic<float> mBuffered = 0;
private:
std::shared_ptr<typename Queue::Writer> mpQueueWriter;
std::shared_ptr<typename Queue::Reader> mpQueueReader;
std::array<double, 4> mReceiverSampleCache = {{0.0, 0.0, 0.0, 0.0}};
std::optional<size_t> moLastFrameIdx = std::nullopt;
};
} }
#else
namespace ableton
{
namespace linkaudio
{
template <typename Link>
class LinkAudioRenderer
{
public:
LinkAudioRenderer(Link&, double&) {}
void operator()(double* pLeftSamples,
double* pRightSamples,
size_t numFrames,
typename Link::SessionState,
double,
const std::chrono::microseconds,
double)
{
std::copy_n(pLeftSamples, numFrames, pRightSamples);
}
};
} }
#endif