Integration of Ableton link in vimix application

No useful functionality yet. Only connecting, set parameters, show metrics and save settings.
This commit is contained in:
Bruno Herbelin
2021-11-13 15:01:02 +01:00
parent c271cad9aa
commit 8676e9b900
5 changed files with 195 additions and 43 deletions

View File

@@ -1,19 +1,30 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#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"
namespace ableton
{
/// Inspired from Dummy audio platform example
/// https://github.com/Ableton/link/blob/master/examples/linkaudio/AudioPlatform_Dummy.hpp
class Engine
{
public:
@@ -48,11 +59,24 @@ namespace ableton
return sessionState.beatAtTime(now(), mQuantum);
}
void setTempo(double tempo)
double phaseTime() const
{
auto sessionState = mLink.captureAppSessionState();
return sessionState.phaseAtTime(now(), 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
@@ -86,40 +110,11 @@ namespace ableton
};
struct State
{
std::atomic<bool> running;
Link link;
Engine engine;
double beats, phase, tempo;
State() : running(true), link(120.), engine(link), beats(0.), phase(4.), tempo(120.)
{
}
};
void update(State& state)
{
state.link.enable(true);
while (state.running)
{
const auto time = state.link.clock().micros();
auto sessionState = state.link.captureAppSessionState();
state.beats = sessionState.beatAtTime(time, state.engine.quantum());
state.phase = sessionState.phaseAtTime(time, state.engine.quantum());
state.tempo = sessionState.tempo();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
state.link.enable(false);
}
} // namespace ableton
ableton::State link_state_;
ableton::Link link_(120.);
ableton::Engine engine_(link_);
Metronome::Metronome()
{
@@ -128,28 +123,65 @@ Metronome::Metronome()
bool Metronome::init()
{
std::thread(ableton::update, std::ref(link_state_)).detach();
// connect
link_.enable(true);
return link_state_.running;
// enable sync
link_.enableStartStopSync(true);
// set parameters
setTempo(Settings::application.metronome.tempo);
setQuantum(Settings::application.metronome.quantum);
// no reason for failure?
return true;
}
void Metronome::terminate()
{
link_state_.running = false;
// save current tempo
Settings::application.metronome.tempo = tempo();
// disconnect
link_.enable(false);
}
double Metronome::beats() const
{
return link_state_.beats;
return engine_.beatTime();
}
double Metronome::phase() const
{
return link_state_.phase;
return engine_.phaseTime();
}
void Metronome::setQuantum(double q)
{
engine_.setQuantum(q);
Settings::application.metronome.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.metronome.tempo = engine_.setTempo(t);
}
double Metronome::tempo() const
{
return link_state_.tempo;
return engine_.tempo();
}
size_t Metronome::peers() const
{
return link_.numPeers();
}

View File

@@ -1,6 +1,7 @@
#ifndef METRONOME_H
#define METRONOME_H
#include <string>
class Metronome
{
@@ -21,7 +22,16 @@ public:
bool init ();
void terminate();
double beats() const;
double phase() const;
void setTempo(double t);
double tempo() const;
void setQuantum(double q);
double quantum() const;
size_t peers() const;
};
#endif // METRONOME_H

View File

@@ -249,6 +249,12 @@ void Settings::Save()
pRoot->InsertEndChild(recent);
}
// Metronome
XMLElement *metroConfNode = xmlDoc.NewElement( "Metronome" );
metroConfNode->SetAttribute("tempo", application.metronome.tempo);
metroConfNode->SetAttribute("quantum", application.metronome.quantum);
pRoot->InsertEndChild(metroConfNode);
// First save : create filename
if (settingsFilename.empty())
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
@@ -518,6 +524,13 @@ void Settings::Load()
}
}
// bloc metronome
XMLElement * metroconfnode = pRoot->FirstChildElement("Metronome");
if (metroconfnode != nullptr) {
metroconfnode->QueryDoubleAttribute("tempo", &application.metronome.tempo);
metroconfnode->QueryDoubleAttribute("quantum", &application.metronome.quantum);
}
}
void Settings::History::push(const string &filename)

View File

@@ -168,6 +168,16 @@ struct SourceConfig
}
};
struct MetronomeConfig
{
double tempo;
double quantum;
MetronomeConfig() {
tempo = 120.;
quantum = 4.;
}
};
struct Application
{
@@ -224,6 +234,9 @@ struct Application
History recentImport;
std::map< std::string, std::string > dialogRecentFolder;
// Metronome
MetronomeConfig metronome;
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
scale = 1.f;
accent_color = 0;

View File

@@ -83,6 +83,7 @@ using namespace std;
#include "PickingVisitor.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "Metronome.h"
#include "TextEditor.h"
TextEditor editor;
@@ -1589,14 +1590,55 @@ void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
ImGui::Combo("##mode", p_mode,
ICON_FA_TACHOMETER_ALT " Performance\0"
ICON_FA_HOURGLASS_HALF " Timers\0"
ICON_FA_VECTOR_SQUARE " Source\0");
ICON_FA_VECTOR_SQUARE " Source\0"
ICON_FA_USER_CLOCK " Ableton link\0");
ImGui::SameLine();
if (ImGuiToolkit::IconButton(5,8))
ImGui::OpenPopup("metrics_menu");
ImGui::Spacing();
if (*p_mode > 1) {
if (*p_mode > 2) {
// get values
double t = Metronome::manager().tempo();
double p = Metronome::manager().phase();
double q = Metronome::manager().quantum();
int n = (int) Metronome::manager().peers();
// tempo
char buf[32];
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::Text("Tempo %.1f BPM ", t);
// network peers indicator
ImGui::SameLine();
if ( n < 1) {
ImGui::TextDisabled( ICON_FA_NETWORK_WIRED );
if (ImGui::IsItemHovered()){
sprintf(buf, "No peer linked");
ImGuiToolkit::ToolTip(buf);
}
}
else {
ImGui::Text( ICON_FA_NETWORK_WIRED );
if (ImGui::IsItemHovered()){
sprintf(buf, "%d peer%c linked", n, n < 2 ? ' ' : 's');
ImGuiToolkit::ToolTip(buf);
}
}
// compute and display duration of a phase
double duration = 60.0 / t * q;
guint64 time_phase = GST_SECOND * duration ;
ImGui::Text("Phase %s", GstToolkit::time_to_string(time_phase, GstToolkit::TIME_STRING_READABLE).c_str());
ImGui::PopFont();
// metronome
sprintf(buf, "%d/%d", (int)(p)+1, (int)(q) );
ImGui::ProgressBar(ceil(p)/ceil(q), ImVec2(250.f,0.f), buf);
}
else if (*p_mode > 1) {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
Source *s = Mixer::manager().currentSource();
if (s) {
@@ -4693,7 +4735,9 @@ void Navigator::RenderMainPannelSettings()
ImGui::PopFont();
ImGui::SetCursorPosY(width_);
//
// Appearance
//
ImGui::Text("Appearance");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
if ( ImGui::DragFloat("Scale", &Settings::application.scale, 0.01, 0.5f, 2.0f, "%.1f"))
@@ -4704,12 +4748,48 @@ void Navigator::RenderMainPannelSettings()
if (b || o || g)
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
//
// Options
//
ImGuiToolkit::Spacing();
ImGui::Text("Options");
ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor);
ImGuiToolkit::ButtonSwitch( ICON_FA_TACHOMETER_ALT " Metrics", &Settings::application.widget.stats);
//
// Metronome
//
ImGuiToolkit::Spacing();
ImGui::Text("Ableton Link");
ImGuiToolkit::HelpMarker("Ableton link enables time synchronization\n "
ICON_FA_ANGLE_RIGHT " Tempo is the number of beats per minute (or set by peers).\n "
ICON_FA_ANGLE_RIGHT " Quantum is the number of beats in a phase.");
ImGui::SameLine(0);
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
int t = (int) Metronome::manager().tempo();
// if no other peers, user can set a tempo
if (Metronome::manager().peers() < 1) {
if ( ImGui::SliderInt("Tempo", &t, 20, 240, "%d BPM") )
Metronome::manager().setTempo((double) t);
}
// if there are other peers, tempo cannot be changed
else {
// show static info (same size than slider)
static char dummy_str[64];
sprintf(dummy_str, "%d BPM", t);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
ImGui::InputText("Tempo", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleColor(1);
}
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
int q = (int) Metronome::manager().quantum();
if ( ImGui::SliderInt("Quantum", &q, 2, 100) )
Metronome::manager().setQuantum((double) q);
#ifndef NDEBUG
ImGui::Text("Expert");
// ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_HISTORY, &Settings::application.widget.history);
@@ -4717,7 +4797,9 @@ void Navigator::RenderMainPannelSettings()
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T");
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
#endif
//
// Recording preferences
//
ImGuiToolkit::Spacing();
ImGui::Text("Recording");
@@ -4754,7 +4836,9 @@ void Navigator::RenderMainPannelSettings()
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Clock\0Framerate\0");
// system preferences
//
// System preferences
//
ImGuiToolkit::Spacing();
ImGui::Text("System");
ImGui::SameLine( ImGui::GetContentRegionAvailWidth() IMGUI_RIGHT_ALIGN * 0.8);