| @@ -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); | ||||
| @@ -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); | |||||
| } | |||||
| } | } | ||||
| // ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
| @@ -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) | ||||
| }; | }; | ||||
| @@ -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); | ||||
| } | } | ||||
| @@ -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) | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| @@ -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> | ||||
| @@ -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 | ||||
| @@ -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 | |||||
| @@ -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; | ||||
| @@ -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; | ||||
| @@ -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 | ||||
| @@ -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{}) | ||||
| { | { | ||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -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 | ||||