Files
vimix/Metronome.cpp
Bruno Herbelin 1b4849f214 Media player synchronicity to beat or phase
Metronome synched play, rewind and step. saving in xml.
2021-11-26 12:22:39 +01:00

254 lines
5.7 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() 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() 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()
{
return engine_.timeNextBeat() - engine_.now();
}
std::chrono::microseconds Metronome::timeToPhase()
{
return engine_.timeNextPhase() - engine_.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();
}
size_t Metronome::peers() const
{
return link_.numPeers();
}