#pragma once
#ifdef LINK_PLATFORM_WINDOWS
#define _USE_MATH_DEFINES
#endif
#include <cmath>
#include <iostream>
namespace ableton
{
namespace linkaudio
{
template <typename Link>
AudioEngine<Link>::AudioEngine(Link& link)
: mLink(link)
, mSampleRate(44100.)
, mOutputLatency(std::chrono::microseconds{0})
, mSharedEngineData({0., false, false, 4., false})
, mLockfreeEngineData(mSharedEngineData)
, mTimeAtLastClick{}
, mIsPlaying(false)
, mLinkAudioRenderer(mLink, mSampleRate)
{
if (!mOutputLatency.is_lock_free())
{
std::cout << "WARNING: AudioEngine::mOutputLatency is not lock free!" << std::endl;
}
}
template <typename Link>
void AudioEngine<Link>::startPlaying()
{
std::lock_guard<std::mutex> lock(mEngineDataGuard);
mSharedEngineData.requestStart = true;
}
template <typename Link>
void AudioEngine<Link>::stopPlaying()
{
std::lock_guard<std::mutex> lock(mEngineDataGuard);
mSharedEngineData.requestStop = true;
}
template <typename Link>
bool AudioEngine<Link>::isPlaying() const
{
return mLink.captureAppSessionState().isPlaying();
}
template <typename Link>
double AudioEngine<Link>::beatTime() const
{
const auto sessionState = mLink.captureAppSessionState();
return sessionState.beatAtTime(mLink.clock().micros(), mSharedEngineData.quantum);
}
template <typename Link>
void AudioEngine<Link>::setTempo(double tempo)
{
std::lock_guard<std::mutex> lock(mEngineDataGuard);
mSharedEngineData.requestedTempo = tempo;
}
template <typename Link>
double AudioEngine<Link>::quantum() const
{
return mSharedEngineData.quantum;
}
template <typename Link>
void AudioEngine<Link>::setQuantum(double quantum)
{
std::lock_guard<std::mutex> lock(mEngineDataGuard);
mSharedEngineData.quantum = quantum;
}
template <typename Link>
bool AudioEngine<Link>::isStartStopSyncEnabled() const
{
return mLink.isStartStopSyncEnabled();
}
template <typename Link>
void AudioEngine<Link>::setStartStopSyncEnabled(const bool enabled)
{
mLink.enableStartStopSync(enabled);
}
template <typename Link>
void AudioEngine<Link>::setNumFrames(std::size_t size)
{
mBuffers[0] = std::vector<double>(size, 0.);
mBuffers[1] = std::vector<double>(size, 0.);
}
template <typename Link>
void AudioEngine<Link>::setSampleRate(double sampleRate)
{
mSampleRate = sampleRate;
}
template <typename Link>
typename AudioEngine<Link>::EngineData AudioEngine<Link>::pullEngineData()
{
auto engineData = EngineData{};
if (mEngineDataGuard.try_lock())
{
engineData.requestedTempo = mSharedEngineData.requestedTempo;
mSharedEngineData.requestedTempo = 0;
engineData.requestStart = mSharedEngineData.requestStart;
mSharedEngineData.requestStart = false;
engineData.requestStop = mSharedEngineData.requestStop;
mSharedEngineData.requestStop = false;
mLockfreeEngineData.quantum = mSharedEngineData.quantum;
mLockfreeEngineData.startStopSyncOn = mSharedEngineData.startStopSyncOn;
mEngineDataGuard.unlock();
}
engineData.quantum = mLockfreeEngineData.quantum;
return engineData;
}
template <typename Link>
void AudioEngine<Link>::renderMetronomeIntoBuffer(
const typename Link::SessionState sessionState,
const double quantum,
const std::chrono::microseconds beginHostTime,
const std::size_t numSamples)
{
using namespace std::chrono;
static const double highTone = 1567.98;
static const double lowTone = 1108.73;
static const auto clickDuration = duration<double>{0.1};
const auto microsPerSample = 1e6 / mSampleRate;
for (std::size_t i = 0; i < numSamples; ++i)
{
double amplitude = 0.;
const auto hostTime =
beginHostTime + microseconds(llround(static_cast<double>(i) * microsPerSample));
const auto lastSampleHostTime = hostTime - microseconds(llround(microsPerSample));
if (sessionState.beatAtTime(hostTime, quantum) >= 0.)
{
if (sessionState.phaseAtTime(hostTime, 1)
< sessionState.phaseAtTime(lastSampleHostTime, 1))
{
mTimeAtLastClick = hostTime;
}
const auto secondsAfterClick =
duration_cast<duration<double>>(hostTime - mTimeAtLastClick);
if (secondsAfterClick < clickDuration)
{
const auto freq =
floor(sessionState.phaseAtTime(hostTime, quantum)) == 0 ? highTone : lowTone;
amplitude = cos(2 * M_PI * secondsAfterClick.count() * freq)
* (1 - sin(5 * M_PI * secondsAfterClick.count()));
}
}
mBuffers[0][i] = amplitude;
}
}
template <typename Link>
void AudioEngine<Link>::audioCallback(const std::chrono::microseconds hostTime,
const std::size_t numSamples)
{
const auto engineData = pullEngineData();
auto sessionState = mLink.captureAudioSessionState();
for (auto& buffer : mBuffers)
{
std::fill(buffer.begin(), buffer.end(), 0);
}
if (engineData.requestStart)
{
sessionState.setIsPlaying(true, hostTime);
}
if (engineData.requestStop)
{
sessionState.setIsPlaying(false, hostTime);
}
if (!mIsPlaying && sessionState.isPlaying())
{
sessionState.requestBeatAtStartPlayingTime(0, engineData.quantum);
mIsPlaying = true;
}
else if (mIsPlaying && !sessionState.isPlaying())
{
mIsPlaying = false;
}
if (engineData.requestedTempo > 0)
{
sessionState.setTempo(engineData.requestedTempo, hostTime);
}
mLink.commitAudioSessionState(sessionState);
if (mIsPlaying)
{
renderMetronomeIntoBuffer(sessionState, engineData.quantum, hostTime, numSamples);
}
mLinkAudioRenderer(mBuffers[0].data(),
mBuffers[1].data(),
mBuffers[0].size(),
sessionState,
mSampleRate,
hostTime,
engineData.quantum);
}
} }