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
{
pData->timeInfo.playing = true;
pData->time.fillEngineTimeInfo(0);
pData->time.setNeedsReset();
}

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

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:
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);
delete[] pData->options.transportExtra;
pData->options.transportExtra = (valueStr != nullptr) ? carla_strdup_safe(valueStr) : nullptr;

pData->time.setNeedsReset();

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

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)
{
CarlaPlugin* const plugin(pData->plugins[i].plugin);
@@ -1687,9 +1686,6 @@ void CarlaEngine::sampleRateChanged(const double 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)
{
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;

#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
: bpm(120.0),
: beatsPerBar(4.0),
beatsPerMinute(120.0),
bufferSize(0.0),
sampleRate(0.0),
tick(0.0),
needsReset(true),
needsReset(false),
nextFrame(0),
timeInfo(ti),
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)
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

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 (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
}

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)
@@ -135,146 +123,221 @@ void EngineInternalTime::enableLink(const bool enable)
if (hylia.enabled == enable)
return;

hylia.enabled = enable;

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

// unused
(void)enable;
#endif

needsReset = true;
}

void EngineInternalTime::setNeedsReset()
void EngineInternalTime::setNeedsReset() noexcept
{
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
{
CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate),);
CARLA_SAFE_ASSERT_RETURN(newFrames > 0,);

timeInfo.usecs = 0;

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

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

double abs_beat, abs_tick;

#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
#endif
{
const double min = timeInfo.frame / (sampleRate * 60.0);
abs_tick = min * bpm * kTicksPerBeat;
abs_tick = min * beatsPerMinute * 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;
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++;

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

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

if (++timeInfo.bbt.beat > timeInfo.bbt.beatsPerBar)
if (++timeInfo.bbt.beat > beatsPerBar)
{
timeInfo.bbt.beat = 1;
++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
{
CARLA_SAFE_ASSERT_RETURN(carla_isNotZero(sampleRate),);
CARLA_SAFE_ASSERT_RETURN(newFrames > 0,);

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

double abs_beat, abs_tick;

#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
#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;
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;
tick = abs_tick - (abs_beat * kTicksPerBeat);
pos->bar_start_tick = pos->bar * pos->beats_per_bar * kTicksPerBeat;
pos->bar++;

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

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

if (++pos->beat > pos->beats_per_bar)
if (++pos->beat > beatsPerBar)
{
pos->beat = 1;
++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
EngineInternalTime::Hylia::Hylia()
: enabled(false),
# ifdef HAVE_HYLIA
instance(hylia_create())
# else
instance(nullptr)
# endif
{
carla_zeroStruct(timeInfo);
}

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

// -----------------------------------------------------------------------
@@ -580,12 +643,6 @@ PendingRtEventsRunner::PendingRtEventsRunner(CarlaEngine* const engine, const ui
PendingRtEventsRunner::~PendingRtEventsRunner() noexcept
{
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 {
public:
EngineInternalTime(EngineTimeInfo& timeInfo, const EngineTransportMode& transportMode) noexcept;
~EngineInternalTime() noexcept;

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 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:
double bpm;
double beatsPerBar;
double beatsPerMinute;
double bufferSize;
double sampleRate;
double tick;
bool needsReset;

uint64_t nextFrame;

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

EngineTimeInfo& timeInfo;
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)
};



+ 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)
{
if (new_pos)
nframes = 0;
pData->time.setNeedsReset();

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


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

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

class HyliaTransport {
public:
HyliaTransport(double bpm, double bufferSize, double sampleRate)
: link(bpm),
HyliaTransport()
: link(120.0),
engine(link),
outputLatency(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)
{
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);
}

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

private:
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;

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


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

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

typedef struct _hylia_time_info_t {
double bpm, beats, phase;
double beatsPerBar, beatsPerMinute, beat, phase;
} 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_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);

#ifdef __cplusplus


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

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

namespace ableton
{
namespace linkaudio
namespace link
{

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

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

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

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

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

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

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

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

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

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

namespace ableton
{
namespace linkaudio
namespace link
{

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

friend class AudioPlatform;
};


} // namespace linkaudio
} // namespace link
} // 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
* unexpected behavior.
*/
class Link
template <typename Clock>
class BasicLink
{
public:
using Clock = link::platform::Clock;
class Timeline;

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

/*! @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?
* Thread-safe: yes
@@ -287,20 +287,28 @@ public:
void forceBeatAtTime(double beat, std::chrono::microseconds time, double quantum);

private:
friend Link;
friend BasicLink<Clock>;

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

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

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

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

} // ableton

#include <ableton/Link.ipp>

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

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

inline Link::Link(const double bpm)
template <typename Clock>
inline BasicLink<Clock>::BasicLink(const double bpm)
: mPeerCountCallback([](std::size_t) {})
, mTempoCallback([](link::Tempo) {})
, mClock{}
, mController(link::Tempo(bpm),
[this](const std::size_t peers) {
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();
}

inline void Link::enable(const bool bEnable)
template <typename Clock>
inline void BasicLink<Clock>::enable(const bool 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();
}

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

template <typename Clock>
template <typename Callback>
void Link::setTempoCallback(Callback callback)
void BasicLink<Clock>::setTempoCallback(Callback callback)
{
std::lock_guard<std::mutex> lock(mCallbackMutex);
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;
}

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)
{
@@ -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)
{
@@ -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)
, mbRespectQuantum(bRespectQuantum)
, mTimeline(timeline)
{
}

inline double Link::Timeline::tempo() const
template <typename Clock>
inline double BasicLink<Clock>::Timeline::tempo() const
{
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 auto desiredTl =
@@ -125,26 +141,30 @@ inline void Link::Timeline::setTempo(
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
{
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
{
return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum})
.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
{
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)
{
if (mbRespectQuantum)
@@ -157,7 +177,8 @@ inline void Link::Timeline::requestBeatAtTime(
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)
{
// 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

#include <ableton/discovery/Service.hpp>
#include <ableton/link/CircularFifo.hpp>
#include <ableton/link/ClientSessionTimelines.hpp>
#include <ableton/link/Gateway.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
// 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

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

Controller(Tempo tempo,
PeerCountCallback peerCallback,
TempoCallback tempoCallback,
@@ -81,7 +81,7 @@ public:
, mSessionPeerCounter(*this, std::move(peerCallback))
, mEnabled(false)
, mIo(std::move(io))
, mRealtimeIo(util::injectRef(*mIo))
, mRtTimelineSetter(*this)
, mPeers(util::injectRef(*mIo),
std::ref(mSessionPeerCounter),
SessionTimelineCallback{*this})
@@ -109,7 +109,7 @@ public:
const bool bWasEnabled = mEnabled.exchange(bEnable);
if (bWasEnabled != bEnable)
{
mRealtimeIo.async([this, bEnable] {
mIo->async([this, bEnable] {
if (bEnable)
{
// Always reset when first enabling to avoid hijacking
@@ -164,7 +164,7 @@ public:
// cached version of the timeline.
const auto now = mClock.micros();
if (now - mRtClientTimelineTimestamp > detail::kLocalModGracePeriod
&& mSessionTimingGuard.try_lock())
&& !mRtTimelineSetter.hasPendingTimelines() && mSessionTimingGuard.try_lock())
{
const auto clientTimeline = updateClientTimelineFromSession(
mRtClientTimeline, mSessionTimeline, now, mGhostXForm);
@@ -184,21 +184,17 @@ public:
void setTimelineRtSafe(Timeline newTimeline, const std::chrono::microseconds atTime)
{
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:
@@ -248,6 +244,16 @@ private:
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)
{
const bool sessionIdChanged = mSessionId != session.sessionId;
@@ -293,6 +299,60 @@ private:
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
{
SessionPeerCounter(Controller& controller, PeerCountCallback callback)
@@ -424,9 +484,8 @@ private:
std::atomic<bool> mEnabled;

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;



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

@@ -22,15 +22,7 @@
#include <array>
#include <cfloat>
#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
{
@@ -42,9 +34,6 @@ struct Kalman
{
Kalman()
: mValue(0)
, mGain(0)
, mVVariance(1)
, mWVariance(1)
, mCoVariance(1)
, mVarianceLength(n)
, mCounter(mVarianceLength)
@@ -124,21 +113,18 @@ struct Kalman
// prediction equations
const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength];
mFilterValues[currentIndex] = prevFilterValue;
mWVariance = calculateWVariance();
const double coVarianceEstimation = mCoVariance + mWVariance;
const auto wVariance = calculateWVariance();
const double coVarianceEstimation = mCoVariance + wVariance;

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

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

double mValue;
double mGain;
double mVVariance;
double mWVariance;
double mCoVariance;
size_t mVarianceLength;
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>
#elif LINK_PLATFORM_LINUX
#include <ableton/platforms/asio/Context.hpp>
#include <ableton/platforms/linux/Clock.hpp>
#include <ableton/platforms/posix/ScanIpIfAddrs.hpp>
#include <ableton/platforms/stl/Clock.hpp>
#endif

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

#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 =
platforms::asio::Context<platforms::posix::ScanIpIfAddrs, util::NullLog>;
#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/platforms/asio/AsioTimer.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 <thread>

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

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

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

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

Context()
: 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
{
using Ticks = std::uint64_t;

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

std::chrono::microseconds micros() const
{
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


Loading…
Cancel
Save