Browse Source

Small rework of hylia/link code

tags/1.9.7b
falkTX 8 years ago
parent
commit
365e343e43
19 changed files with 662 additions and 368 deletions
  1. +6
    -10
      source/backend/engine/CarlaEngine.cpp
  2. +150
    -93
      source/backend/engine/CarlaEngineInternal.cpp
  3. +16
    -10
      source/backend/engine/CarlaEngineInternal.hpp
  4. +1
    -1
      source/backend/engine/CarlaEngineJack.cpp
  5. +38
    -24
      source/modules/hylia/hylia.cpp
  6. +6
    -4
      source/modules/hylia/hylia.h
  7. +10
    -7
      source/modules/hylia/link/AudioEngine.cpp
  8. +3
    -5
      source/modules/hylia/link/AudioEngine.hpp
  9. +17
    -9
      source/modules/hylia/link/ableton/Link.hpp
  10. +44
    -23
      source/modules/hylia/link/ableton/Link.ipp
  11. +113
    -0
      source/modules/hylia/link/ableton/link/CircularFifo.hpp
  12. +83
    -24
      source/modules/hylia/link/ableton/link/Controller.hpp
  13. +10
    -27
      source/modules/hylia/link/ableton/link/Kalman.hpp
  14. +6
    -2
      source/modules/hylia/link/ableton/platforms/Config.hpp
  15. +4
    -4
      source/modules/hylia/link/ableton/platforms/asio/Context.hpp
  16. +89
    -0
      source/modules/hylia/link/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp
  17. +0
    -115
      source/modules/hylia/link/ableton/platforms/asio/PooledHandlerContext.hpp
  18. +64
    -0
      source/modules/hylia/link/ableton/platforms/linux/Clock.hpp
  19. +2
    -10
      source/modules/hylia/link/ableton/platforms/stl/Clock.hpp

+ 6
- 10
source/backend/engine/CarlaEngine.cpp View File

@@ -1344,19 +1344,18 @@ void CarlaEngine::setFileCallback(const FileCallbackFunc func, void* const ptr)
void CarlaEngine::transportPlay() noexcept void CarlaEngine::transportPlay() noexcept
{ {
pData->timeInfo.playing = true; pData->timeInfo.playing = true;
pData->time.fillEngineTimeInfo(0);
pData->time.setNeedsReset();
} }


void CarlaEngine::transportPause() noexcept void CarlaEngine::transportPause() noexcept
{ {
pData->timeInfo.playing = false; pData->timeInfo.playing = false;
pData->time.fillEngineTimeInfo(0);
pData->time.setNeedsReset();
} }


void CarlaEngine::transportRelocate(const uint64_t frame) noexcept void CarlaEngine::transportRelocate(const uint64_t frame) noexcept
{ {
pData->timeInfo.frame = frame;
pData->time.fillEngineTimeInfo(0);
pData->time.relocate(frame);
} }


// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@@ -1415,10 +1414,13 @@ void CarlaEngine::setOption(const EngineOption option, const int value, const ch


case ENGINE_OPTION_TRANSPORT_MODE: case ENGINE_OPTION_TRANSPORT_MODE:
CARLA_SAFE_ASSERT_RETURN(value >= ENGINE_TRANSPORT_MODE_INTERNAL && value <= ENGINE_TRANSPORT_MODE_BRIDGE,); CARLA_SAFE_ASSERT_RETURN(value >= ENGINE_TRANSPORT_MODE_INTERNAL && value <= ENGINE_TRANSPORT_MODE_BRIDGE,);
CARLA_SAFE_ASSERT_RETURN(getType() == kEngineTypeJack || value != ENGINE_TRANSPORT_MODE_JACK,);
pData->options.transportMode = static_cast<EngineTransportMode>(value); pData->options.transportMode = static_cast<EngineTransportMode>(value);
delete[] pData->options.transportExtra; delete[] pData->options.transportExtra;
pData->options.transportExtra = (valueStr != nullptr) ? carla_strdup_safe(valueStr) : nullptr; pData->options.transportExtra = (valueStr != nullptr) ? carla_strdup_safe(valueStr) : nullptr;


pData->time.setNeedsReset();

#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE) #if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
// enable link now if needed // enable link now if needed
{ {
@@ -1659,9 +1661,6 @@ void CarlaEngine::bufferSizeChanged(const uint32_t newBufferSize)


pData->time.updateAudioValues(newBufferSize, pData->sampleRate); pData->time.updateAudioValues(newBufferSize, pData->sampleRate);


if (pData->options.transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
pData->time.fillEngineTimeInfo(0);

for (uint i=0; i < pData->curPluginCount; ++i) for (uint i=0; i < pData->curPluginCount; ++i)
{ {
CarlaPlugin* const plugin(pData->plugins[i].plugin); CarlaPlugin* const plugin(pData->plugins[i].plugin);
@@ -1687,9 +1686,6 @@ void CarlaEngine::sampleRateChanged(const double newSampleRate)


pData->time.updateAudioValues(pData->bufferSize, newSampleRate); pData->time.updateAudioValues(pData->bufferSize, newSampleRate);


if (pData->options.transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
pData->time.fillEngineTimeInfo(0);

for (uint i=0; i < pData->curPluginCount; ++i) for (uint i=0; i < pData->curPluginCount; ++i)
{ {
CarlaPlugin* const plugin(pData->plugins[i].plugin); CarlaPlugin* const plugin(pData->plugins[i].plugin);


+ 150
- 93
source/backend/engine/CarlaEngineInternal.cpp View File

@@ -61,72 +61,60 @@ void EngineInternalEvents::clear() noexcept


static const float kTicksPerBeat = 1920.0f; static const float kTicksPerBeat = 1920.0f;


#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
static uint32_t calculate_link_latency(const double bufferSize, const double sampleRate) noexcept
{
CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate), 0);

const long long int latency = llround(1.0e6 * bufferSize / sampleRate);
CARLA_SAFE_ASSERT_RETURN(latency >= 0 && latency < UINT32_MAX, 0);

return static_cast<uint32_t>(latency);
}
#endif

EngineInternalTime::EngineInternalTime(EngineTimeInfo& ti, const EngineTransportMode& tm) noexcept EngineInternalTime::EngineInternalTime(EngineTimeInfo& ti, const EngineTransportMode& tm) noexcept
: bpm(120.0),
: beatsPerBar(4.0),
beatsPerMinute(120.0),
bufferSize(0.0),
sampleRate(0.0), sampleRate(0.0),
tick(0.0), tick(0.0),
needsReset(true),
needsReset(false),
nextFrame(0),
timeInfo(ti), timeInfo(ti),
transportMode(tm) {} transportMode(tm) {}


EngineInternalTime::~EngineInternalTime() noexcept
{
#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
hylia_cleanup(hylia.instance);
#endif
}

void EngineInternalTime::init(const uint32_t bufferSize, const double sr)
void EngineInternalTime::init(const uint32_t bsize, const double srate)
{ {
sampleRate = sr;
bufferSize = bsize;
sampleRate = srate;


#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE) #if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
hylia.instance = hylia_create(bpm, bufferSize, sr);
if (hylia.instance != nullptr)
{
hylia_set_beats_per_bar(hylia.instance, beatsPerBar);
hylia_set_beats_per_minute(hylia.instance, beatsPerMinute);
hylia_set_output_latency(hylia.instance, calculate_link_latency(bsize, srate));


if (hylia.enabled)
hylia_enable(hylia.instance, true, bpm);
if (hylia.enabled)
hylia_enable(hylia.instance, true);
}
#endif #endif


fillEngineTimeInfo(0);
needsReset = true;
} }


void EngineInternalTime::updateAudioValues(const uint32_t bufferSize, double sampleRate)
void EngineInternalTime::updateAudioValues(const uint32_t bsize, const double srate)
{ {
// TODO
}
bufferSize = bsize;
sampleRate = srate;


void EngineInternalTime::preProcess(const uint32_t numFrames)
{
#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE) #if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
if (hylia.enabled)
{
hylia_process(hylia.instance, numFrames, &hylia.timeInfo);
const double new_bpm = hylia.timeInfo.bpm;

if (new_bpm > 0.0 && bpm != new_bpm)
{
bpm = new_bpm;
needsReset = true;

if (transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
fillEngineTimeInfo(0);
}
}
#else
return;

// unused
(void)numFrames;
if (hylia.instance != nullptr)
hylia_set_output_latency(hylia.instance, calculate_link_latency(bsize, srate));
#endif #endif
}


void EngineInternalTime::postProcess(const uint32_t numFrames)
{
if (timeInfo.playing && transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
{
timeInfo.frame += numFrames;
fillEngineTimeInfo(numFrames);
}
needsReset = true;
} }


void EngineInternalTime::enableLink(const bool enable) void EngineInternalTime::enableLink(const bool enable)
@@ -135,146 +123,221 @@ void EngineInternalTime::enableLink(const bool enable)
if (hylia.enabled == enable) if (hylia.enabled == enable)
return; return;


hylia.enabled = enable;

if (hylia.instance != nullptr) if (hylia.instance != nullptr)
hylia_enable(hylia.instance, enable, bpm);
{
hylia.enabled = enable;
hylia_enable(hylia.instance, enable);
}
#else #else
return;

// unused // unused
(void)enable; (void)enable;
#endif #endif

needsReset = true;
} }


void EngineInternalTime::setNeedsReset()
void EngineInternalTime::setNeedsReset() noexcept
{ {
needsReset = true; needsReset = true;
} }


void EngineInternalTime::relocate(const uint64_t frame) noexcept
{
timeInfo.frame = frame;
nextFrame = frame;
needsReset = true;
}

void EngineInternalTime::fillEngineTimeInfo(const uint32_t newFrames) noexcept void EngineInternalTime::fillEngineTimeInfo(const uint32_t newFrames) noexcept
{ {
CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate),); CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate),);
CARLA_SAFE_ASSERT_RETURN(newFrames > 0,);


timeInfo.usecs = 0; timeInfo.usecs = 0;


if (newFrames == 0 || needsReset)
if (transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
timeInfo.frame = nextFrame;

if (needsReset)
{ {
timeInfo.valid = EngineTimeInfo::kValidBBT; timeInfo.valid = EngineTimeInfo::kValidBBT;
timeInfo.bbt.beatsPerBar = 4.0f;
timeInfo.bbt.beatsPerBar = beatsPerBar;
timeInfo.bbt.beatType = 4.0f; timeInfo.bbt.beatType = 4.0f;
timeInfo.bbt.ticksPerBeat = kTicksPerBeat; timeInfo.bbt.ticksPerBeat = kTicksPerBeat;
timeInfo.bbt.beatsPerMinute = bpm;
timeInfo.bbt.beatsPerMinute = beatsPerMinute;


double abs_beat, abs_tick; double abs_beat, abs_tick;


#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE) #if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
if (hylia.enabled && hylia.timeInfo.beats >= 0.0)
if (hylia.enabled)
{ {
const double beats = hylia.timeInfo.beats;

abs_beat = std::floor(beats);
abs_tick = beats * kTicksPerBeat;
if (hylia.timeInfo.beat >= 0.0)
{
const double beat = hylia.timeInfo.beat;
abs_beat = std::floor(beat);
abs_tick = beat * kTicksPerBeat;
}
else
{
abs_beat = 0.0;
abs_tick = 0.0;
timeInfo.playing = false;
}
} }
else else
#endif #endif
{ {
const double min = timeInfo.frame / (sampleRate * 60.0); const double min = timeInfo.frame / (sampleRate * 60.0);
abs_tick = min * bpm * kTicksPerBeat;
abs_tick = min * beatsPerMinute * kTicksPerBeat;
abs_beat = abs_tick / kTicksPerBeat; abs_beat = abs_tick / kTicksPerBeat;
needsReset = false;
} }


timeInfo.bbt.bar = abs_beat / timeInfo.bbt.beatsPerBar;
timeInfo.bbt.bar = (int32_t)(std::floor(abs_beat / timeInfo.bbt.beatsPerBar) + 0.5);
timeInfo.bbt.beat = abs_beat - (timeInfo.bbt.bar * timeInfo.bbt.beatsPerBar) + 1; timeInfo.bbt.beat = abs_beat - (timeInfo.bbt.bar * timeInfo.bbt.beatsPerBar) + 1;
tick = abs_tick - (abs_beat * kTicksPerBeat);
timeInfo.bbt.barStartTick = timeInfo.bbt.bar * timeInfo.bbt.beatsPerBar * kTicksPerBeat;
timeInfo.bbt.barStartTick = timeInfo.bbt.bar * beatsPerBar * kTicksPerBeat;
timeInfo.bbt.bar++; timeInfo.bbt.bar++;

tick = abs_tick - timeInfo.bbt.barStartTick;
} }
else else
{ {
tick += newFrames * kTicksPerBeat * bpm / (sampleRate * 60);
tick += newFrames * kTicksPerBeat * beatsPerMinute / (sampleRate * 60);


while (tick >= kTicksPerBeat) while (tick >= kTicksPerBeat)
{ {
tick -= kTicksPerBeat; tick -= kTicksPerBeat;


if (++timeInfo.bbt.beat > timeInfo.bbt.beatsPerBar)
if (++timeInfo.bbt.beat > beatsPerBar)
{ {
timeInfo.bbt.beat = 1; timeInfo.bbt.beat = 1;
++timeInfo.bbt.bar; ++timeInfo.bbt.bar;
timeInfo.bbt.barStartTick += timeInfo.bbt.beatsPerBar * kTicksPerBeat;
timeInfo.bbt.barStartTick += beatsPerBar * kTicksPerBeat;
} }
} }
} }


timeInfo.bbt.tick = (int)(tick + 0.5);
needsReset = false;
timeInfo.bbt.tick = (int32_t)(tick + 0.5);

if (transportMode == ENGINE_TRANSPORT_MODE_INTERNAL && timeInfo.playing)
nextFrame += newFrames;
} }


void EngineInternalTime::fillJackTimeInfo(jack_position_t* const pos, const uint32_t newFrames) noexcept void EngineInternalTime::fillJackTimeInfo(jack_position_t* const pos, const uint32_t newFrames) noexcept
{ {
CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate),); CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate),);
CARLA_SAFE_ASSERT_RETURN(newFrames > 0,);


if (newFrames == 0 || needsReset)
if (needsReset)
{ {
pos->valid = JackPositionBBT; pos->valid = JackPositionBBT;
pos->beats_per_bar = 4.0f;
pos->beats_per_bar = beatsPerBar;
pos->beat_type = 4.0f; pos->beat_type = 4.0f;
pos->ticks_per_beat = kTicksPerBeat; pos->ticks_per_beat = kTicksPerBeat;
pos->beats_per_minute = bpm;
pos->beats_per_minute = beatsPerMinute;


double abs_beat, abs_tick; double abs_beat, abs_tick;


#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE) #if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
if (hylia.enabled && hylia.timeInfo.beats >= 0.0)
if (hylia.enabled)
{ {
const double beats = hylia.timeInfo.beats;

abs_beat = std::floor(beats);
abs_tick = beats * kTicksPerBeat;
if (hylia.timeInfo.beat >= 0.0)
{
const double beat = hylia.timeInfo.beat;
abs_beat = std::floor(beat);
abs_tick = beat * kTicksPerBeat;
}
else
{
abs_beat = 0.0;
abs_tick = 0.0;
timeInfo.playing = false;
}
} }
else else
#endif #endif
{ {
const double min = pos->frame / (sampleRate * 60.0);
abs_tick = min * bpm * kTicksPerBeat;
const double min = static_cast<double>(pos->frame) / (sampleRate * 60.0);
abs_tick = min * beatsPerMinute * kTicksPerBeat;
abs_beat = abs_tick / kTicksPerBeat; abs_beat = abs_tick / kTicksPerBeat;
needsReset = false;
} }


pos->bar = abs_beat / pos->beats_per_bar;
pos->bar = (int32_t)(std::floor(abs_beat / pos->beats_per_bar) + 0.5);
pos->beat = abs_beat - (pos->bar * pos->beats_per_bar) + 1; pos->beat = abs_beat - (pos->bar * pos->beats_per_bar) + 1;
tick = abs_tick - (abs_beat * kTicksPerBeat);
pos->bar_start_tick = pos->bar * pos->beats_per_bar * kTicksPerBeat; pos->bar_start_tick = pos->bar * pos->beats_per_bar * kTicksPerBeat;
pos->bar++; pos->bar++;

tick = abs_tick - pos->bar_start_tick;
} }
else else
{ {
tick += newFrames * kTicksPerBeat * bpm / (sampleRate * 60);
tick += newFrames * kTicksPerBeat * beatsPerMinute / (sampleRate * 60);


while (tick >= kTicksPerBeat) while (tick >= kTicksPerBeat)
{ {
tick -= kTicksPerBeat; tick -= kTicksPerBeat;


if (++pos->beat > pos->beats_per_bar)
if (++pos->beat > beatsPerBar)
{ {
pos->beat = 1; pos->beat = 1;
++pos->bar; ++pos->bar;
pos->bar_start_tick += pos->beats_per_bar * pos->ticks_per_beat;
pos->bar_start_tick += beatsPerBar * kTicksPerBeat;
} }
} }
} }


pos->tick = (int)(tick + 0.5);
needsReset = false;
pos->tick = (int32_t)(tick + 0.5);
}

void EngineInternalTime::preProcess(const uint32_t numFrames)
{
#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE)
if (hylia.enabled)
{
hylia_process(hylia.instance, numFrames, &hylia.timeInfo);

const double new_bpb = hylia.timeInfo.beatsPerBar;
const double new_bpm = hylia.timeInfo.beatsPerMinute;

if (new_bpb >= 1.0 && beatsPerBar != new_bpb)
{
beatsPerBar = new_bpb;
needsReset = true;
}
if (new_bpm > 0.0 && beatsPerMinute != new_bpm)
{
beatsPerMinute = new_bpm;
needsReset = true;
}
}
#endif

if (transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
fillEngineTimeInfo(numFrames);
} }


// -----------------------------------------------------------------------
// EngineInternalTime::Hylia

#ifndef BUILD_BRIDGE #ifndef BUILD_BRIDGE
EngineInternalTime::Hylia::Hylia() EngineInternalTime::Hylia::Hylia()
: enabled(false), : enabled(false),
# ifdef HAVE_HYLIA
instance(hylia_create())
# else
instance(nullptr) instance(nullptr)
# endif
{ {
carla_zeroStruct(timeInfo); carla_zeroStruct(timeInfo);
} }

EngineInternalTime::Hylia::~Hylia()
{
# ifdef HAVE_HYLIA
hylia_cleanup(instance);
# endif
}
#endif #endif


// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@@ -580,12 +643,6 @@ PendingRtEventsRunner::PendingRtEventsRunner(CarlaEngine* const engine, const ui
PendingRtEventsRunner::~PendingRtEventsRunner() noexcept PendingRtEventsRunner::~PendingRtEventsRunner() noexcept
{ {
pData->doNextPluginAction(true); pData->doNextPluginAction(true);

if (pData->timeInfo.playing && pData->options.transportMode == ENGINE_TRANSPORT_MODE_INTERNAL)
{
pData->timeInfo.frame += numFrames;
pData->time.fillEngineTimeInfo(numFrames);
}
} }


// ----------------------------------------------------------------------- // -----------------------------------------------------------------------


+ 16
- 10
source/backend/engine/CarlaEngineInternal.hpp View File

@@ -116,38 +116,44 @@ private:
class EngineInternalTime { class EngineInternalTime {
public: public:
EngineInternalTime(EngineTimeInfo& timeInfo, const EngineTransportMode& transportMode) noexcept; EngineInternalTime(EngineTimeInfo& timeInfo, const EngineTransportMode& transportMode) noexcept;
~EngineInternalTime() noexcept;


void init(const uint32_t bufferSize, double sampleRate); void init(const uint32_t bufferSize, double sampleRate);
void updateAudioValues(const uint32_t bufferSize, double sampleRate);

void preProcess(const uint32_t numFrames);
void postProcess(const uint32_t numFrames);
void updateAudioValues(const uint32_t bufferSize, const double sampleRate);


void enableLink(const bool enable); void enableLink(const bool enable);
void setNeedsReset();

void fillEngineTimeInfo(const uint32_t newFrames) noexcept;
void fillJackTimeInfo(jack_position_t* const pos, const uint32_t newFrames) noexcept;
void setNeedsReset() noexcept;
void relocate(const uint64_t frame) noexcept;


private: private:
double bpm;
double beatsPerBar;
double beatsPerMinute;
double bufferSize;
double sampleRate; double sampleRate;
double tick; double tick;
bool needsReset; bool needsReset;


uint64_t nextFrame;

#ifndef BUILD_BRIDGE #ifndef BUILD_BRIDGE
struct Hylia { struct Hylia {
bool enabled; bool enabled;
hylia_t* instance; hylia_t* instance;
hylia_time_info_t timeInfo; hylia_time_info_t timeInfo;
Hylia(); Hylia();
~Hylia();
} hylia; } hylia;
#endif #endif


EngineTimeInfo& timeInfo; EngineTimeInfo& timeInfo;
const EngineTransportMode& transportMode; const EngineTransportMode& transportMode;


friend class PendingRtEventsRunner;
void preProcess(const uint32_t numFrames);
void fillEngineTimeInfo(const uint32_t newFrames) noexcept;

friend class CarlaEngineJack;
void fillJackTimeInfo(jack_position_t* const pos, const uint32_t newFrames) noexcept;

CARLA_DECLARE_NON_COPY_STRUCT(EngineInternalTime) CARLA_DECLARE_NON_COPY_STRUCT(EngineInternalTime)
}; };




+ 1
- 1
source/backend/engine/CarlaEngineJack.cpp View File

@@ -1725,7 +1725,7 @@ protected:
void handleJackTimebaseCallback(jack_nframes_t nframes, jack_position_t* const pos, const int new_pos) void handleJackTimebaseCallback(jack_nframes_t nframes, jack_position_t* const pos, const int new_pos)
{ {
if (new_pos) if (new_pos)
nframes = 0;
pData->time.setNeedsReset();


pData->time.fillJackTimeInfo(pos, nframes); pData->time.fillJackTimeInfo(pos, nframes);
} }


+ 38
- 24
source/modules/hylia/hylia.cpp View File

@@ -23,56 +23,60 @@


class HyliaTransport { class HyliaTransport {
public: public:
HyliaTransport(double bpm, double bufferSize, double sampleRate)
: link(bpm),
HyliaTransport()
: link(120.0),
engine(link), engine(link),
outputLatency(0), outputLatency(0),
sampleTime(0) sampleTime(0)
{ {
outputLatency = std::chrono::microseconds(llround(1.0e6 * bufferSize / sampleRate));
} }


void setEnabled(bool enabled, double bpm)
void setEnabled(const bool enabled)
{ {
link.enable(enabled);

if (enabled) if (enabled)
{
sampleTime = 0; sampleTime = 0;
engine.setTempo(bpm);
}
link.enable(enabled);
} }


void setTempo(double tempo)
void setQuantum(const double quantum)
{
engine.setQuantum(quantum);
}

void setTempo(const double tempo)
{ {
engine.setTempo(tempo); engine.setTempo(tempo);
} }


void process(uint32_t frames, LinkTimeInfo* info)
void setOutputLatency(const uint32_t latency) noexcept
{ {
const auto hostTime = hostTimeFilter.sampleTimeToHostTime(sampleTime);
const auto bufferBeginAtOutput = hostTime + outputLatency;
outputLatency = latency;
}


engine.timelineCallback(bufferBeginAtOutput, info);
void process(const uint32_t frames, LinkTimeInfo* const info)
{
const std::chrono::microseconds hostTime = hostTimeFilter.sampleTimeToHostTime(sampleTime)
+ std::chrono::microseconds(outputLatency);
engine.timelineCallback(hostTime, info);


sampleTime += frames; sampleTime += frames;
} }


private: private:
ableton::Link link; ableton::Link link;
ableton::linkaudio::AudioEngine engine;
ableton::link::AudioEngine engine;
ableton::link::HostTimeFilter<ableton::link::platform::Clock> hostTimeFilter;


ableton::link::HostTimeFilter<ableton::platforms::stl::Clock> hostTimeFilter;
std::chrono::microseconds outputLatency;
uint32_t sampleTime;
uint32_t outputLatency, sampleTime;
}; };


hylia_t* hylia_create(double bpm, uint32_t buffer_size, uint32_t sample_rate)
hylia_t* hylia_create(void)
{ {
HyliaTransport* t; HyliaTransport* t;


try { try {
t = new HyliaTransport(bpm, buffer_size, sample_rate);
t = new HyliaTransport();
} catch (...) { } catch (...) {
return nullptr; return nullptr;
} }
@@ -80,14 +84,24 @@ hylia_t* hylia_create(double bpm, uint32_t buffer_size, uint32_t sample_rate)
return (hylia_t*)t; return (hylia_t*)t;
} }


void hylia_enable(hylia_t* link, bool on, double bpm)
void hylia_enable(hylia_t* link, bool on)
{
((HyliaTransport*)link)->setEnabled(on);
}

void hylia_set_beats_per_bar(hylia_t* link, double beatsPerBar)
{
((HyliaTransport*)link)->setQuantum(beatsPerBar);
}

void hylia_set_beats_per_minute(hylia_t* link, double beatsPerMinute)
{ {
((HyliaTransport*)link)->setEnabled(on, bpm);
((HyliaTransport*)link)->setTempo(beatsPerMinute);
} }


void hylia_set_tempo(hylia_t* link, double bpm)
void hylia_set_output_latency(hylia_t* link, uint32_t latency)
{ {
((HyliaTransport*)link)->setTempo(bpm);
((HyliaTransport*)link)->setOutputLatency(latency);
} }


void hylia_process(hylia_t* link, uint32_t frames, hylia_time_info_t* info) void hylia_process(hylia_t* link, uint32_t frames, hylia_time_info_t* info)


+ 6
- 4
source/modules/hylia/hylia.h View File

@@ -29,13 +29,15 @@ extern "C" {
typedef struct _hylia_t hylia_t; typedef struct _hylia_t hylia_t;


typedef struct _hylia_time_info_t { typedef struct _hylia_time_info_t {
double bpm, beats, phase;
double beatsPerBar, beatsPerMinute, beat, phase;
} hylia_time_info_t; } hylia_time_info_t;


hylia_t* hylia_create(double bpm, uint32_t buffer_size, uint32_t sample_rate);
void hylia_enable(hylia_t* link, bool on, double bpm);
hylia_t* hylia_create(void);
void hylia_enable(hylia_t* link, bool on);
void hylia_process(hylia_t* link, uint32_t frames, hylia_time_info_t* info); void hylia_process(hylia_t* link, uint32_t frames, hylia_time_info_t* info);
void hylia_set_tempo(hylia_t* link, double tempo);
void hylia_set_beats_per_bar(hylia_t* link, double beatsPerBar);
void hylia_set_beats_per_minute(hylia_t* link, double beatsPerMinute);
void hylia_set_output_latency(hylia_t* link, uint32_t latency);
void hylia_cleanup(hylia_t* link); void hylia_cleanup(hylia_t* link);


#ifdef __cplusplus #ifdef __cplusplus


+ 10
- 7
source/modules/hylia/link/AudioEngine.cpp View File

@@ -25,7 +25,7 @@


namespace ableton namespace ableton
{ {
namespace linkaudio
namespace link
{ {


AudioEngine::AudioEngine(Link& link) AudioEngine::AudioEngine(Link& link)
@@ -42,7 +42,7 @@ double AudioEngine::beatTime() const


void AudioEngine::setTempo(double tempo) void AudioEngine::setTempo(double tempo)
{ {
std::lock_guard<std::mutex> lock(mEngineDataGuard);
const std::lock_guard<std::mutex> lock(mEngineDataGuard);
mSharedEngineData.requestedTempo = tempo; mSharedEngineData.requestedTempo = tempo;
} }


@@ -53,7 +53,7 @@ double AudioEngine::quantum() const


void AudioEngine::setQuantum(double quantum) void AudioEngine::setQuantum(double quantum)
{ {
std::lock_guard<std::mutex> lock(mEngineDataGuard);
const std::lock_guard<std::mutex> lock(mEngineDataGuard);
mSharedEngineData.quantum = quantum; mSharedEngineData.quantum = quantum;
mSharedEngineData.resetBeatTime = true; mSharedEngineData.resetBeatTime = true;
} }
@@ -61,10 +61,12 @@ void AudioEngine::setQuantum(double quantum)
AudioEngine::EngineData AudioEngine::pullEngineData() AudioEngine::EngineData AudioEngine::pullEngineData()
{ {
auto engineData = EngineData{}; auto engineData = EngineData{};

if (mEngineDataGuard.try_lock()) if (mEngineDataGuard.try_lock())
{ {
engineData.requestedTempo = mSharedEngineData.requestedTempo; engineData.requestedTempo = mSharedEngineData.requestedTempo;
mSharedEngineData.requestedTempo = 0; mSharedEngineData.requestedTempo = 0;

engineData.resetBeatTime = mSharedEngineData.resetBeatTime; engineData.resetBeatTime = mSharedEngineData.resetBeatTime;
engineData.quantum = mSharedEngineData.quantum; engineData.quantum = mSharedEngineData.quantum;
mSharedEngineData.resetBeatTime = false; mSharedEngineData.resetBeatTime = false;
@@ -98,10 +100,11 @@ void AudioEngine::timelineCallback(const std::chrono::microseconds hostTime, Lin
mLink.commitAudioTimeline(timeline); mLink.commitAudioTimeline(timeline);


// Save timeline info // Save timeline info
info->bpm = timeline.tempo();
info->beats = timeline.beatAtTime(hostTime, engineData.quantum);
info->phase = timeline.phaseAtTime(hostTime, engineData.quantum);
info->beatsPerBar = engineData.quantum;
info->beatsPerMinute = timeline.tempo();
info->beat = timeline.beatAtTime(hostTime, engineData.quantum);
info->phase = timeline.phaseAtTime(hostTime, engineData.quantum);
} }


} // namespace linkaudio
} // namespace link
} // namespace ableton } // namespace ableton

+ 3
- 5
source/modules/hylia/link/AudioEngine.hpp View File

@@ -25,12 +25,12 @@
#include <mutex> #include <mutex>


struct LinkTimeInfo { struct LinkTimeInfo {
double bpm, beats, phase;
double beatsPerBar, beatsPerMinute, beat, phase;
}; };


namespace ableton namespace ableton
{ {
namespace linkaudio
namespace link
{ {


class AudioEngine class AudioEngine
@@ -57,10 +57,8 @@ private:
Link& mLink; Link& mLink;
EngineData mSharedEngineData; EngineData mSharedEngineData;
std::mutex mEngineDataGuard; std::mutex mEngineDataGuard;

friend class AudioPlatform;
}; };




} // namespace linkaudio
} // namespace link
} // namespace ableton } // namespace ableton

+ 17
- 9
source/modules/hylia/link/ableton/Link.hpp View File

@@ -58,20 +58,20 @@ namespace ableton
* concurrently is not advised and will potentially lead to * concurrently is not advised and will potentially lead to
* unexpected behavior. * unexpected behavior.
*/ */
class Link
template <typename Clock>
class BasicLink
{ {
public: public:
using Clock = link::platform::Clock;
class Timeline; class Timeline;


/*! @brief Construct with an initial tempo. */ /*! @brief Construct with an initial tempo. */
Link(double bpm);
BasicLink(double bpm);


/*! @brief Link instances cannot be copied or moved */ /*! @brief Link instances cannot be copied or moved */
Link(const Link&) = delete;
Link& operator=(const Link&) = delete;
Link(Link&&) = delete;
Link& operator=(Link&&) = delete;
BasicLink(const BasicLink<Clock>&) = delete;
BasicLink& operator=(const BasicLink<Clock>&) = delete;
BasicLink(BasicLink<Clock>&&) = delete;
BasicLink& operator=(BasicLink<Clock>&&) = delete;


/*! @brief Is Link currently enabled? /*! @brief Is Link currently enabled?
* Thread-safe: yes * Thread-safe: yes
@@ -287,20 +287,28 @@ public:
void forceBeatAtTime(double beat, std::chrono::microseconds time, double quantum); void forceBeatAtTime(double beat, std::chrono::microseconds time, double quantum);


private: private:
friend Link;
friend BasicLink<Clock>;

link::Timeline mOriginalTimeline; link::Timeline mOriginalTimeline;
bool mbRespectQuantum; bool mbRespectQuantum;
link::Timeline mTimeline; link::Timeline mTimeline;
}; };


private: private:
using Controller = ableton::link::Controller<link::PeerCountCallback,
link::TempoCallback,
Clock,
link::platform::IoContext>;

std::mutex mCallbackMutex; std::mutex mCallbackMutex;
link::PeerCountCallback mPeerCountCallback; link::PeerCountCallback mPeerCountCallback;
link::TempoCallback mTempoCallback; link::TempoCallback mTempoCallback;
Clock mClock; Clock mClock;
link::platform::Controller mController;
Controller mController;
}; };


using Link = BasicLink<link::platform::Clock>;

} // ableton } // ableton


#include <ableton/Link.ipp> #include <ableton/Link.ipp>

+ 44
- 23
source/modules/hylia/link/ableton/Link.ipp View File

@@ -24,10 +24,10 @@
namespace ableton namespace ableton
{ {


inline Link::Link(const double bpm)
template <typename Clock>
inline BasicLink<Clock>::BasicLink(const double bpm)
: mPeerCountCallback([](std::size_t) {}) : mPeerCountCallback([](std::size_t) {})
, mTempoCallback([](link::Tempo) {}) , mTempoCallback([](link::Tempo) {})
, mClock{}
, mController(link::Tempo(bpm), , mController(link::Tempo(bpm),
[this](const std::size_t peers) { [this](const std::size_t peers) {
std::lock_guard<std::mutex> lock(mCallbackMutex); std::lock_guard<std::mutex> lock(mCallbackMutex);
@@ -42,46 +42,54 @@ inline Link::Link(const double bpm)
{ {
} }


inline bool Link::isEnabled() const
template <typename Clock>
inline bool BasicLink<Clock>::isEnabled() const
{ {
return mController.isEnabled(); return mController.isEnabled();
} }


inline void Link::enable(const bool bEnable)
template <typename Clock>
inline void BasicLink<Clock>::enable(const bool bEnable)
{ {
mController.enable(bEnable); mController.enable(bEnable);
} }


inline std::size_t Link::numPeers() const
template <typename Clock>
inline std::size_t BasicLink<Clock>::numPeers() const
{ {
return mController.numPeers(); return mController.numPeers();
} }


template <typename Clock>
template <typename Callback> template <typename Callback>
void Link::setNumPeersCallback(Callback callback)
void BasicLink<Clock>::setNumPeersCallback(Callback callback)
{ {
std::lock_guard<std::mutex> lock(mCallbackMutex); std::lock_guard<std::mutex> lock(mCallbackMutex);
mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); }; mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); };
} }


template <typename Clock>
template <typename Callback> template <typename Callback>
void Link::setTempoCallback(Callback callback)
void BasicLink<Clock>::setTempoCallback(Callback callback)
{ {
std::lock_guard<std::mutex> lock(mCallbackMutex); std::lock_guard<std::mutex> lock(mCallbackMutex);
mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); }; mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); };
} }


inline Link::Clock Link::clock() const
template <typename Clock>
inline Clock BasicLink<Clock>::clock() const
{ {
return mClock; return mClock;
} }


inline Link::Timeline Link::captureAudioTimeline() const
template <typename Clock>
inline typename BasicLink<Clock>::Timeline BasicLink<Clock>::captureAudioTimeline() const
{ {
return Link::Timeline{mController.timelineRtSafe(), numPeers() > 0};
return BasicLink<Clock>::Timeline{mController.timelineRtSafe(), numPeers() > 0};
} }


inline void Link::commitAudioTimeline(const Link::Timeline timeline)
template <typename Clock>
inline void BasicLink<Clock>::commitAudioTimeline(const Timeline timeline)
{ {
if (timeline.mOriginalTimeline != timeline.mTimeline) if (timeline.mOriginalTimeline != timeline.mTimeline)
{ {
@@ -89,12 +97,14 @@ inline void Link::commitAudioTimeline(const Link::Timeline timeline)
} }
} }


inline Link::Timeline Link::captureAppTimeline() const
template <typename Clock>
inline typename BasicLink<Clock>::Timeline BasicLink<Clock>::captureAppTimeline() const
{ {
return Link::Timeline{mController.timeline(), numPeers() > 0};
return Timeline{mController.timeline(), numPeers() > 0};
} }


inline void Link::commitAppTimeline(const Link::Timeline timeline)
template <typename Clock>
inline void BasicLink<Clock>::commitAppTimeline(const Timeline timeline)
{ {
if (timeline.mOriginalTimeline != timeline.mTimeline) if (timeline.mOriginalTimeline != timeline.mTimeline)
{ {
@@ -102,21 +112,27 @@ inline void Link::commitAppTimeline(const Link::Timeline timeline)
} }
} }


// Link::Timeline
////////////////////
// Link::Timeline //
////////////////////


inline Link::Timeline::Timeline(const link::Timeline timeline, const bool bRespectQuantum)
template <typename Clock>
inline BasicLink<Clock>::Timeline::Timeline(
const link::Timeline timeline, const bool bRespectQuantum)
: mOriginalTimeline(timeline) : mOriginalTimeline(timeline)
, mbRespectQuantum(bRespectQuantum) , mbRespectQuantum(bRespectQuantum)
, mTimeline(timeline) , mTimeline(timeline)
{ {
} }


inline double Link::Timeline::tempo() const
template <typename Clock>
inline double BasicLink<Clock>::Timeline::tempo() const
{ {
return mTimeline.tempo.bpm(); return mTimeline.tempo.bpm();
} }


inline void Link::Timeline::setTempo(
template <typename Clock>
inline void BasicLink<Clock>::Timeline::setTempo(
const double bpm, const std::chrono::microseconds atTime) const double bpm, const std::chrono::microseconds atTime)
{ {
const auto desiredTl = const auto desiredTl =
@@ -125,26 +141,30 @@ inline void Link::Timeline::setTempo(
mTimeline.timeOrigin = desiredTl.fromBeats(mTimeline.beatOrigin); mTimeline.timeOrigin = desiredTl.fromBeats(mTimeline.beatOrigin);
} }


inline double Link::Timeline::beatAtTime(
template <typename Clock>
inline double BasicLink<Clock>::Timeline::beatAtTime(
const std::chrono::microseconds time, const double quantum) const const std::chrono::microseconds time, const double quantum) const
{ {
return link::toPhaseEncodedBeats(mTimeline, time, link::Beats{quantum}).floating(); return link::toPhaseEncodedBeats(mTimeline, time, link::Beats{quantum}).floating();
} }


inline double Link::Timeline::phaseAtTime(
template <typename Clock>
inline double BasicLink<Clock>::Timeline::phaseAtTime(
const std::chrono::microseconds time, const double quantum) const const std::chrono::microseconds time, const double quantum) const
{ {
return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum}) return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum})
.floating(); .floating();
} }


inline std::chrono::microseconds Link::Timeline::timeAtBeat(
template <typename Clock>
inline std::chrono::microseconds BasicLink<Clock>::Timeline::timeAtBeat(
const double beat, const double quantum) const const double beat, const double quantum) const
{ {
return link::fromPhaseEncodedBeats(mTimeline, link::Beats{beat}, link::Beats{quantum}); return link::fromPhaseEncodedBeats(mTimeline, link::Beats{beat}, link::Beats{quantum});
} }


inline void Link::Timeline::requestBeatAtTime(
template <typename Clock>
inline void BasicLink<Clock>::Timeline::requestBeatAtTime(
const double beat, std::chrono::microseconds time, const double quantum) const double beat, std::chrono::microseconds time, const double quantum)
{ {
if (mbRespectQuantum) if (mbRespectQuantum)
@@ -157,7 +177,8 @@ inline void Link::Timeline::requestBeatAtTime(
forceBeatAtTime(beat, time, quantum); forceBeatAtTime(beat, time, quantum);
} }


inline void Link::Timeline::forceBeatAtTime(
template <typename Clock>
inline void BasicLink<Clock>::Timeline::forceBeatAtTime(
const double beat, const std::chrono::microseconds time, const double quantum) const double beat, const std::chrono::microseconds time, const double quantum)
{ {
// There are two components to the beat adjustment: a phase shift // There are two components to the beat adjustment: a phase shift


+ 113
- 0
source/modules/hylia/link/ableton/link/CircularFifo.hpp View File

@@ -0,0 +1,113 @@
/* 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>.
*/

#pragma once

#include <array>
#include <atomic>
#include <cassert>

namespace ableton
{
namespace link
{

// Single producer, single consumer lockfree Fifo

template <typename Type, std::size_t size>
class CircularFifo
{
public:
struct PoppedItem
{
Type item;
bool valid;
};

CircularFifo()
: tail(0)
, head(0)
{
assert(head.is_lock_free() && tail.is_lock_free());
}

bool push(Type item)
{
const auto currentTail = tail.load();
const auto nextTail = nextIndex(currentTail);
if (nextTail != head.load())
{
data[currentTail] = std::move(item);
tail.store(nextTail);
return true;
}
return false;
}

PoppedItem pop()
{
const auto currentHead = head.load();
if (currentHead == tail.load())
{
return {Type{}, false};
}

auto item = data[currentHead];
head.store(nextIndex(currentHead));
return {std::move(item), true};
}

PoppedItem clearAndPopLast()
{
auto hasData = false;
auto currentHead = head.load();
while (currentHead != tail.load())
{
currentHead = nextIndex(currentHead);
hasData = true;
}

auto item = data[previousIndex(currentHead)];
head.store(currentHead);
return {std::move(item), hasData};
}

bool isEmpty() const
{
return tail == head;
}

private:
size_t nextIndex(const size_t index) const
{
return (index + 1) % (size + 1);
}

size_t previousIndex(const size_t index) const
{
return (index + size) % (size + 1);
}

std::atomic_size_t tail;
std::atomic_size_t head;
std::array<Type, size + 1> data;
};

} // link
} // ableton

+ 83
- 24
source/modules/hylia/link/ableton/link/Controller.hpp View File

@@ -20,6 +20,7 @@
#pragma once #pragma once


#include <ableton/discovery/Service.hpp> #include <ableton/discovery/Service.hpp>
#include <ableton/link/CircularFifo.hpp>
#include <ableton/link/ClientSessionTimelines.hpp> #include <ableton/link/ClientSessionTimelines.hpp>
#include <ableton/link/Gateway.hpp> #include <ableton/link/Gateway.hpp>
#include <ableton/link/GhostXForm.hpp> #include <ableton/link/GhostXForm.hpp>
@@ -45,7 +46,8 @@ GhostXForm initXForm(const Clock& clock)


// The timespan in which local modifications to the timeline will be // The timespan in which local modifications to the timeline will be
// preferred over any modifications coming from the network. // preferred over any modifications coming from the network.
const auto kLocalModGracePeriod = std::chrono::seconds(1);
const auto kLocalModGracePeriod = std::chrono::milliseconds(1000);
const auto kRtHandlerFallbackPeriod = kLocalModGracePeriod / 2;


} // namespace detail } // namespace detail


@@ -61,8 +63,6 @@ template <typename PeerCountCallback,
class Controller class Controller
{ {
public: public:
using Ticks = typename Clock::Ticks;

Controller(Tempo tempo, Controller(Tempo tempo,
PeerCountCallback peerCallback, PeerCountCallback peerCallback,
TempoCallback tempoCallback, TempoCallback tempoCallback,
@@ -81,7 +81,7 @@ public:
, mSessionPeerCounter(*this, std::move(peerCallback)) , mSessionPeerCounter(*this, std::move(peerCallback))
, mEnabled(false) , mEnabled(false)
, mIo(std::move(io)) , mIo(std::move(io))
, mRealtimeIo(util::injectRef(*mIo))
, mRtTimelineSetter(*this)
, mPeers(util::injectRef(*mIo), , mPeers(util::injectRef(*mIo),
std::ref(mSessionPeerCounter), std::ref(mSessionPeerCounter),
SessionTimelineCallback{*this}) SessionTimelineCallback{*this})
@@ -109,7 +109,7 @@ public:
const bool bWasEnabled = mEnabled.exchange(bEnable); const bool bWasEnabled = mEnabled.exchange(bEnable);
if (bWasEnabled != bEnable) if (bWasEnabled != bEnable)
{ {
mRealtimeIo.async([this, bEnable] {
mIo->async([this, bEnable] {
if (bEnable) if (bEnable)
{ {
// Always reset when first enabling to avoid hijacking // Always reset when first enabling to avoid hijacking
@@ -164,7 +164,7 @@ public:
// cached version of the timeline. // cached version of the timeline.
const auto now = mClock.micros(); const auto now = mClock.micros();
if (now - mRtClientTimelineTimestamp > detail::kLocalModGracePeriod if (now - mRtClientTimelineTimestamp > detail::kLocalModGracePeriod
&& mSessionTimingGuard.try_lock())
&& !mRtTimelineSetter.hasPendingTimelines() && mSessionTimingGuard.try_lock())
{ {
const auto clientTimeline = updateClientTimelineFromSession( const auto clientTimeline = updateClientTimelineFromSession(
mRtClientTimeline, mSessionTimeline, now, mGhostXForm); mRtClientTimeline, mSessionTimeline, now, mGhostXForm);
@@ -184,21 +184,17 @@ public:
void setTimelineRtSafe(Timeline newTimeline, const std::chrono::microseconds atTime) void setTimelineRtSafe(Timeline newTimeline, const std::chrono::microseconds atTime)
{ {
newTimeline = clampTempo(newTimeline); newTimeline = clampTempo(newTimeline);
// Cache the new timeline for serving back to the client
mRtClientTimeline = newTimeline;
mRtClientTimelineTimestamp =
isEnabled() ? mClock.micros() : std::chrono::microseconds(0);

// Update the session timeline from the new client timeline
mRealtimeIo.async([this, newTimeline, atTime] {
// Synchronize with the non-rt version of the client timeline
{
std::lock_guard<std::mutex> lock(mClientTimelineGuard);
mClientTimeline = newTimeline;
}
handleTimelineFromClient(updateSessionTimelineFromClient(
mSessionTimeline, newTimeline, atTime, mGhostXForm));
});

// This will fail in case the Fifo in the RtTimelineSetter is full. This indicates a
// very high rate of calls to the setter. In this case we ignore one value because we
// expect the setter to be called again soon.
if (mRtTimelineSetter.tryPush(newTimeline, atTime))
{
// Cache the new timeline for serving back to the client
mRtClientTimeline = newTimeline;
mRtClientTimelineTimestamp =
isEnabled() ? mClock.micros() : std::chrono::microseconds(0);
}
} }


private: private:
@@ -248,6 +244,16 @@ private:
mSessions.sawSessionTimeline(std::move(id), std::move(timeline)), mGhostXForm); mSessions.sawSessionTimeline(std::move(id), std::move(timeline)), mGhostXForm);
} }


void handleRtTimeline(const Timeline timeline, const std::chrono::microseconds time)
{
{
std::lock_guard<std::mutex> lock(mClientTimelineGuard);
mClientTimeline = timeline;
}
handleTimelineFromClient(
updateSessionTimelineFromClient(mSessionTimeline, timeline, time, mGhostXForm));
}

void joinSession(const Session& session) void joinSession(const Session& session)
{ {
const bool sessionIdChanged = mSessionId != session.sessionId; const bool sessionIdChanged = mSessionId != session.sessionId;
@@ -293,6 +299,60 @@ private:
Controller& mController; Controller& mController;
}; };


struct RtTimelineSetter
{
using CallbackDispatcher =
typename IoContext::template LockFreeCallbackDispatcher<std::function<void()>,
std::chrono::milliseconds>;
using RtTimeline = std::pair<Timeline, std::chrono::microseconds>;

RtTimelineSetter(Controller& controller)
: mController(controller)
, mHasPendingTimelines(false)
, mCallbackDispatcher(
[this] { processPendingTimelines(); }, detail::kRtHandlerFallbackPeriod)
{
}

bool tryPush(const Timeline timeline, const std::chrono::microseconds time)
{
mHasPendingTimelines = true;
const auto success = mFifo.push({timeline, time});
if (success)
{
mCallbackDispatcher.invoke();
}
return success;
}

bool hasPendingTimelines() const
{
return mHasPendingTimelines;
}

private:
void processPendingTimelines()
{
auto result = mFifo.clearAndPopLast();

if (result.valid)
{
auto timeline = std::move(result.item);
mController.mIo->async([this, timeline]() {
mController.handleRtTimeline(timeline.first, timeline.second);
mHasPendingTimelines = false;
});
}
}

Controller& mController;
// Assuming a wake up time of one ms for the threads owned by the CallbackDispatcher
// and the ioService, buffering 16 timelines allows to set eight timelines per ms.
CircularFifo<RtTimeline, 16> mFifo;
std::atomic<bool> mHasPendingTimelines;
CallbackDispatcher mCallbackDispatcher;
};

struct SessionPeerCounter struct SessionPeerCounter
{ {
SessionPeerCounter(Controller& controller, PeerCountCallback callback) SessionPeerCounter(Controller& controller, PeerCountCallback callback)
@@ -424,9 +484,8 @@ private:
std::atomic<bool> mEnabled; std::atomic<bool> mEnabled;


util::Injected<IoContext> mIo; util::Injected<IoContext> mIo;
// A realtime facade over the provided IoContext. This should only
// be used by realtime code, non-realtime code should use mIo.
typename IoType::template RealTimeContext<IoType&> mRealtimeIo;

RtTimelineSetter mRtTimelineSetter;


ControllerPeers mPeers; ControllerPeers mPeers;




+ 10
- 27
source/modules/hylia/link/ableton/link/Kalman.hpp View File

@@ -22,15 +22,7 @@
#include <array> #include <array>
#include <cfloat> #include <cfloat>
#include <cmath> #include <cmath>
#include <limits>


#if LINK_PLATFORM_WINDOWS
// Windows.h (or more specifically, minwindef.h) define the max(a, b) macro
// which conflicts with the symbol provided by std::numeric_limits.
#ifdef max
#undef max
#endif
#endif


namespace ableton namespace ableton
{ {
@@ -42,9 +34,6 @@ struct Kalman
{ {
Kalman() Kalman()
: mValue(0) : mValue(0)
, mGain(0)
, mVVariance(1)
, mWVariance(1)
, mCoVariance(1) , mCoVariance(1)
, mVarianceLength(n) , mVarianceLength(n)
, mCounter(mVarianceLength) , mCounter(mVarianceLength)
@@ -124,21 +113,18 @@ struct Kalman
// prediction equations // prediction equations
const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength]; const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength];
mFilterValues[currentIndex] = prevFilterValue; mFilterValues[currentIndex] = prevFilterValue;
mWVariance = calculateWVariance();
const double coVarianceEstimation = mCoVariance + mWVariance;
const auto wVariance = calculateWVariance();
const double coVarianceEstimation = mCoVariance + wVariance;


// update equations // update equations
mVVariance = calculateVVariance();
if ((coVarianceEstimation + mVVariance) != 0)
{
mGain = coVarianceEstimation / (coVarianceEstimation + mVVariance);
}
else
{
mGain = std::numeric_limits<double>::max();
}
mValue = prevFilterValue + mGain * (value - prevFilterValue);
mCoVariance = (1 - mGain) * coVarianceEstimation;
const auto vVariance = calculateVVariance();
// Gain defines how easily the filter will adjust to a new condition
// With gain = 1 the output equals the input, with gain = 0 the input
// is ignored and the output equals the last filtered value
const auto divisor = coVarianceEstimation + vVariance;
const auto gain = divisor != 0. ? coVarianceEstimation / divisor : 0.7;
mValue = prevFilterValue + gain * (value - prevFilterValue);
mCoVariance = (1 - gain) * coVarianceEstimation;
} }
mFilterValues[currentIndex] = mValue; mFilterValues[currentIndex] = mValue;


@@ -146,9 +132,6 @@ struct Kalman
} }


double mValue; double mValue;
double mGain;
double mVVariance;
double mWVariance;
double mCoVariance; double mCoVariance;
size_t mVarianceLength; size_t mVarianceLength;
size_t mCounter; size_t mCounter;


+ 6
- 2
source/modules/hylia/link/ableton/platforms/Config.hpp View File

@@ -32,8 +32,8 @@
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp> #include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
#elif LINK_PLATFORM_LINUX #elif LINK_PLATFORM_LINUX
#include <ableton/platforms/asio/Context.hpp> #include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/linux/Clock.hpp>
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp> #include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
#include <ableton/platforms/stl/Clock.hpp>
#endif #endif


namespace ableton namespace ableton
@@ -54,7 +54,11 @@ using IoContext =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>; platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;


#elif LINK_PLATFORM_LINUX #elif LINK_PLATFORM_LINUX
using Clock = platforms::stl::Clock;
#ifdef __ARM_ARCH_7A__
using Clock = platforms::linux::ClockMonotonicRaw;
#else
using Clock = platforms::linux::ClockMonotonic;
#endif
using IoContext = using IoContext =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>; platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;
#endif #endif


+ 4
- 4
source/modules/hylia/link/ableton/platforms/asio/Context.hpp View File

@@ -22,7 +22,7 @@
#include <ableton/discovery/IpV4Interface.hpp> #include <ableton/discovery/IpV4Interface.hpp>
#include <ableton/platforms/asio/AsioTimer.hpp> #include <ableton/platforms/asio/AsioTimer.hpp>
#include <ableton/platforms/asio/AsioWrapper.hpp> #include <ableton/platforms/asio/AsioWrapper.hpp>
#include <ableton/platforms/asio/PooledHandlerContext.hpp>
#include <ableton/platforms/asio/LockFreeCallbackDispatcher.hpp>
#include <ableton/platforms/asio/Socket.hpp> #include <ableton/platforms/asio/Socket.hpp>
#include <thread> #include <thread>


@@ -40,12 +40,12 @@ public:
using Timer = AsioTimer; using Timer = AsioTimer;
using Log = LogT; using Log = LogT;


template <typename Handler, typename Duration>
using LockFreeCallbackDispatcher = LockFreeCallbackDispatcher<Handler, Duration>;

template <std::size_t BufferSize> template <std::size_t BufferSize>
using Socket = asio::Socket<BufferSize>; using Socket = asio::Socket<BufferSize>;


template <typename IoContext>
using RealTimeContext = PooledHandlerContext<IoContext>;

Context() Context()
: Context(DefaultHandler{}) : Context(DefaultHandler{})
{ {


+ 89
- 0
source/modules/hylia/link/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp View File

@@ -0,0 +1,89 @@
/* 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>.
*/

#pragma once

#include <atomic>
#include <condition_variable>
#include <thread>

namespace ableton
{
namespace platforms
{

// Utility to signal invocation of a callback on another thread in a lock free manner.
// The callback is evoked on a thread owned by the instance of this class.
//
// A condition variable is used to notify a waiting thread, but only if the required
// lock can be acquired immediately. If that fails, we fall back on signaling
// after a timeout. This gives us a guaranteed minimum signalling rate which is defined
// by the fallbackPeriod parameter.

template <typename Callback, typename Duration>
class LockFreeCallbackDispatcher
{
public:
LockFreeCallbackDispatcher(Callback callback, Duration fallbackPeriod)
: mCallback(std::move(callback))
, mFallbackPeriod(std::move(fallbackPeriod))
, mRunning(true)
, mThread([this] { run(); })
{
}

~LockFreeCallbackDispatcher()
{
mRunning = false;
mCondition.notify_one();
mThread.join();
}

void invoke()
{
if (mMutex.try_lock())
{
mCondition.notify_one();
mMutex.unlock();
}
}

private:
void run()
{
while (mRunning.load())
{
{
std::unique_lock<std::mutex> lock(mMutex);
mCondition.wait_for(lock, mFallbackPeriod);
}
mCallback();
}
}

Callback mCallback;
Duration mFallbackPeriod;
std::atomic<bool> mRunning;
std::mutex mMutex;
std::condition_variable mCondition;
std::thread mThread;
};

} // platforms
} // ableton

+ 0
- 115
source/modules/hylia/link/ableton/platforms/asio/PooledHandlerContext.hpp View File

@@ -1,115 +0,0 @@
/* 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>.
*/

#pragma once

#include <ableton/platforms/asio/AsioWrapper.hpp>
#include <ableton/util/Injected.hpp>

namespace ableton
{
namespace platforms
{
namespace asio
{

template <typename IoContext,
std::size_t MaxNumHandlers = 32,
std::size_t MaxHandlerSize = 128>
struct PooledHandlerContext
{
PooledHandlerContext(util::Injected<IoContext> io)
: mIo(std::move(io))
{
// initialize the handler free store
mFreeStack.reserve(MaxNumHandlers);
for (std::size_t i = 0; i < MaxNumHandlers; ++i)
{
mFreeStack.push_back(reinterpret_cast<void*>(mRaw + i));
}
}

template <typename Handler>
void async(Handler handler)
{
try
{
mIo->async(HandlerWrapper<Handler>{*this, std::move(handler)});
}
catch (std::bad_alloc)
{
warning(mIo->log()) << "Handler dropped due to low memory pool";
}
}

template <typename Handler>
struct HandlerWrapper
{
HandlerWrapper(PooledHandlerContext& context, Handler handler)
: mContext(context)
, mHandler(std::move(handler))
{
}

void operator()()
{
mHandler();
}

// Use pooled allocation so that posting handlers will not cause
// system allocation
friend void* asio_handler_allocate(
const std::size_t size, HandlerWrapper* const pHandler)
{
if (size > MaxHandlerSize || pHandler->mContext.mFreeStack.empty())
{
// Going over the max handler size is a programming error, as
// this is not a dynamically variable value.
assert(size <= MaxHandlerSize);
throw std::bad_alloc();
}
else
{
const auto p = pHandler->mContext.mFreeStack.back();
pHandler->mContext.mFreeStack.pop_back();
return p;
}
}

friend void asio_handler_deallocate(
void* const p, std::size_t, HandlerWrapper* const pHandler)
{
pHandler->mContext.mFreeStack.push_back(p);
}

PooledHandlerContext& mContext;
Handler mHandler;
};

using MemChunk = typename std::aligned_storage<MaxHandlerSize,
std::alignment_of<max_align_t>::value>::type;
MemChunk mRaw[MaxNumHandlers];
std::vector<void*> mFreeStack;

util::Injected<IoContext> mIo;
};

} // namespace asio
} // namespace platforms
} // namespace ableton

+ 64
- 0
source/modules/hylia/link/ableton/platforms/linux/Clock.hpp View File

@@ -0,0 +1,64 @@
/* 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>.
*/

#pragma once

#include <chrono>
#include <cmath>
#include <ctime>

namespace ableton
{
namespace platforms
{

#ifdef linux
#undef linux
#endif

namespace linux
{

template <clockid_t CLOCK>
class Clock
{
public:
std::chrono::microseconds micros() const
{
::timespec ts;
::clock_gettime(CLOCK, &ts);
std::uint64_t ns = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
return std::chrono::microseconds(ns / 1000ULL);
}
};

using ClockMonotonic = Clock<CLOCK_MONOTONIC>;
using ClockMonotonicRaw = Clock<CLOCK_MONOTONIC_RAW>;

/* Warning: the realtime clock can be subject to time change.
* One of the hard requirements of Ableton Link is that the clock must be
* monotonic.
* Only use the Realtime Clock if you can't use the monotonic ones, and
* beware that things might go wrong if time jumps.
*/
using ClockRealtime = Clock<CLOCK_REALTIME>;

} // namespace linux
} // namespace platforms
} // namespace ableton

+ 2
- 10
source/modules/hylia/link/ableton/platforms/stl/Clock.hpp View File

@@ -30,20 +30,12 @@ namespace stl


struct Clock struct Clock
{ {
using Ticks = std::uint64_t;

Clock()
{
mStartTime = std::chrono::high_resolution_clock::now();
}

std::chrono::microseconds micros() const std::chrono::microseconds micros() const
{ {
using namespace std::chrono; using namespace std::chrono;
return duration_cast<microseconds>(high_resolution_clock::now() - mStartTime);
auto nowInMicros = time_point_cast<microseconds>(steady_clock::now());
return nowInMicros.time_since_epoch();
} }

std::chrono::high_resolution_clock::time_point mStartTime;
}; };


} // namespace stl } // namespace stl


Loading…
Cancel
Save