mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-11 18:34:58 +01:00
273 lines
6.4 KiB
C++
273 lines
6.4 KiB
C++
#include <atomic>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <thread>
|
|
|
|
/// Ableton Link is a technology that synchronizes musical beat, tempo,
|
|
/// and phase across multiple applications running on one or more devices.
|
|
/// Applications on devices connected to a local network discover each other
|
|
/// automatically and form a musical session in which each participant can
|
|
/// perform independently: anyone can start or stop while still staying in time.
|
|
/// Anyone can change the tempo, the others will follow.
|
|
/// Anyone can join or leave without disrupting the session.
|
|
///
|
|
/// https://ableton.github.io/link/
|
|
///
|
|
#include <ableton/Link.hpp>
|
|
|
|
#include "Settings.h"
|
|
#include "Metronome.h"
|
|
#include "Log.h"
|
|
|
|
|
|
namespace ableton
|
|
{
|
|
|
|
/// Inspired from Dummy audio platform example
|
|
/// https://github.com/Ableton/link/blob/master/examples/linkaudio/AudioPlatform_Dummy.hpp
|
|
class Engine
|
|
{
|
|
public:
|
|
Engine(Link& link)
|
|
: mLink(link)
|
|
, mQuantum(4.)
|
|
{
|
|
}
|
|
|
|
void startPlaying()
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
sessionState.setIsPlayingAndRequestBeatAtTime(true, now(), 0., mQuantum);
|
|
mLink.commitAppSessionState(sessionState);
|
|
}
|
|
|
|
void stopPlaying()
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
sessionState.setIsPlaying(false, now());
|
|
mLink.commitAppSessionState(sessionState);
|
|
}
|
|
|
|
bool isPlaying() const
|
|
{
|
|
return mLink.captureAppSessionState().isPlaying();
|
|
}
|
|
|
|
double beatTime() const
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
return sessionState.beatAtTime(now(), mQuantum);
|
|
}
|
|
|
|
std::chrono::microseconds timeNextBeat(std::chrono::microseconds now) const
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
double beat = ceil(sessionState.beatAtTime(now, mQuantum));
|
|
return sessionState.timeAtBeat(beat, mQuantum);
|
|
}
|
|
|
|
double phaseTime() const
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
return sessionState.phaseAtTime(now(), mQuantum);
|
|
}
|
|
|
|
std::chrono::microseconds timeNextPhase(std::chrono::microseconds now) const
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
double phase = ceil(sessionState.phaseAtTime(now, mQuantum));
|
|
double beat = ceil(sessionState.beatAtTime(now, mQuantum));
|
|
return sessionState.timeAtBeat(beat + (mQuantum-phase), mQuantum);
|
|
}
|
|
|
|
double tempo() const
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
return sessionState.tempo();
|
|
}
|
|
|
|
double setTempo(double tempo)
|
|
{
|
|
auto sessionState = mLink.captureAppSessionState();
|
|
sessionState.setTempo(tempo, now());
|
|
mLink.commitAppSessionState(sessionState);
|
|
return sessionState.tempo();
|
|
}
|
|
|
|
double quantum() const
|
|
{
|
|
return mQuantum;
|
|
}
|
|
|
|
void setQuantum(double quantum)
|
|
{
|
|
mQuantum = quantum;
|
|
}
|
|
|
|
bool isStartStopSyncEnabled() const
|
|
{
|
|
return mLink.isStartStopSyncEnabled();
|
|
}
|
|
|
|
void setStartStopSyncEnabled(bool enabled)
|
|
{
|
|
mLink.enableStartStopSync(enabled);
|
|
}
|
|
|
|
std::chrono::microseconds now() const
|
|
{
|
|
return mLink.clock().micros();
|
|
}
|
|
|
|
private:
|
|
Link& mLink;
|
|
double mQuantum;
|
|
};
|
|
|
|
|
|
} // namespace ableton
|
|
|
|
|
|
ableton::Link link_(120.);
|
|
ableton::Engine engine_(link_);
|
|
|
|
Metronome::Metronome()
|
|
{
|
|
|
|
}
|
|
|
|
bool Metronome::init()
|
|
{
|
|
// set parameters
|
|
setEnabled(Settings::application.timer.link_enabled);
|
|
setTempo(Settings::application.timer.link_tempo);
|
|
setQuantum(Settings::application.timer.link_quantum);
|
|
setStartStopSync(Settings::application.timer.link_start_stop_sync);
|
|
|
|
// no reason for failure?
|
|
return true;
|
|
}
|
|
|
|
void Metronome::terminate()
|
|
{
|
|
// save current tempo
|
|
Settings::application.timer.link_tempo = tempo();
|
|
|
|
// disconnect
|
|
link_.enable(false);
|
|
}
|
|
|
|
void Metronome::setEnabled (bool on)
|
|
{
|
|
link_.enable(on);
|
|
Settings::application.timer.link_enabled = link_.isEnabled();
|
|
Log::Info("Metronome Ableton Link %s", Settings::application.timer.link_enabled ? "Enabled" : "Disabled");
|
|
}
|
|
|
|
bool Metronome::enabled () const
|
|
{
|
|
return link_.isEnabled();
|
|
}
|
|
|
|
double Metronome::beats() const
|
|
{
|
|
return engine_.beatTime();
|
|
}
|
|
|
|
double Metronome::phase() const
|
|
{
|
|
return engine_.phaseTime();
|
|
}
|
|
|
|
void Metronome::setQuantum(double q)
|
|
{
|
|
engine_.setQuantum(q);
|
|
Settings::application.timer.link_quantum = engine_.quantum();
|
|
}
|
|
|
|
double Metronome::quantum() const
|
|
{
|
|
return engine_.quantum();
|
|
}
|
|
|
|
void Metronome::setTempo(double t)
|
|
{
|
|
// set the tempo to t
|
|
// OR
|
|
// adopt the last tempo value that have been proposed on the network
|
|
Settings::application.timer.link_tempo = engine_.setTempo(t);
|
|
}
|
|
|
|
double Metronome::tempo() const
|
|
{
|
|
return engine_.tempo();
|
|
}
|
|
|
|
|
|
void Metronome::setStartStopSync (bool on)
|
|
{
|
|
engine_.setStartStopSyncEnabled(on);
|
|
Settings::application.timer.link_start_stop_sync = engine_.isStartStopSyncEnabled();
|
|
Log::Info("Metronome Ableton Link start & stop sync %s", Settings::application.timer.link_start_stop_sync ? "Enabled" : "Disabled");
|
|
}
|
|
|
|
bool Metronome::startStopSync () const
|
|
{
|
|
return engine_.isStartStopSyncEnabled();
|
|
}
|
|
|
|
void Metronome::restart()
|
|
{
|
|
engine_.startPlaying();
|
|
}
|
|
|
|
std::chrono::microseconds Metronome::timeToBeat()
|
|
{
|
|
std::chrono::microseconds now = engine_.now();
|
|
return engine_.timeNextBeat( now ) - now;
|
|
}
|
|
|
|
std::chrono::microseconds Metronome::timeToPhase()
|
|
{
|
|
std::chrono::microseconds now = engine_.now();
|
|
return engine_.timeNextPhase( now ) - now;
|
|
}
|
|
|
|
void delay(std::function<void()> f, std::chrono::microseconds us)
|
|
{
|
|
std::this_thread::sleep_for(us);
|
|
f();
|
|
}
|
|
|
|
void Metronome::executeAtBeat( std::function<void()> f )
|
|
{
|
|
std::thread( delay, f, timeToBeat() ).detach();
|
|
}
|
|
|
|
void Metronome::executeAtPhase( std::function<void()> f )
|
|
{
|
|
std::thread( delay, f, timeToPhase() ).detach();
|
|
}
|
|
|
|
float Metronome::timeToSync(Synchronicity sync)
|
|
{
|
|
float ret = 0.f;
|
|
if ( sync > Metronome::SYNC_BEAT ) {
|
|
// SYNC TO PHASE
|
|
std::chrono::duration<float, std::milli> fp_ms = Metronome::manager().timeToPhase();
|
|
ret = fp_ms.count();
|
|
}
|
|
else if ( sync > Metronome::SYNC_NONE ) {
|
|
// SYNC TO BEAT
|
|
std::chrono::duration<float, std::milli> fp_ms = Metronome::manager().timeToBeat();
|
|
ret = fp_ms.count();
|
|
}
|
|
// SYNC NONE
|
|
return ret;
|
|
}
|
|
|
|
size_t Metronome::peers() const
|
|
{
|
|
return link_.numPeers();
|
|
}
|