mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-19 14:19:57 +01:00
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:
112
Metronome.cpp
112
Metronome.cpp
@@ -1,19 +1,30 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#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 <ableton/Link.hpp>
|
||||||
|
|
||||||
|
#include "Settings.h"
|
||||||
#include "Metronome.h"
|
#include "Metronome.h"
|
||||||
|
|
||||||
|
|
||||||
namespace ableton
|
namespace ableton
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// Inspired from Dummy audio platform example
|
||||||
|
/// https://github.com/Ableton/link/blob/master/examples/linkaudio/AudioPlatform_Dummy.hpp
|
||||||
class Engine
|
class Engine
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -48,11 +59,24 @@ namespace ableton
|
|||||||
return sessionState.beatAtTime(now(), mQuantum);
|
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();
|
auto sessionState = mLink.captureAppSessionState();
|
||||||
sessionState.setTempo(tempo, now());
|
sessionState.setTempo(tempo, now());
|
||||||
mLink.commitAppSessionState(sessionState);
|
mLink.commitAppSessionState(sessionState);
|
||||||
|
return sessionState.tempo();
|
||||||
}
|
}
|
||||||
|
|
||||||
double quantum() const
|
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
|
} // namespace ableton
|
||||||
|
|
||||||
|
|
||||||
ableton::State link_state_;
|
ableton::Link link_(120.);
|
||||||
|
ableton::Engine engine_(link_);
|
||||||
|
|
||||||
Metronome::Metronome()
|
Metronome::Metronome()
|
||||||
{
|
{
|
||||||
@@ -128,28 +123,65 @@ Metronome::Metronome()
|
|||||||
|
|
||||||
bool Metronome::init()
|
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()
|
void Metronome::terminate()
|
||||||
{
|
{
|
||||||
link_state_.running = false;
|
// save current tempo
|
||||||
|
Settings::application.metronome.tempo = tempo();
|
||||||
|
|
||||||
|
// disconnect
|
||||||
|
link_.enable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
double Metronome::beats() const
|
double Metronome::beats() const
|
||||||
{
|
{
|
||||||
return link_state_.beats;
|
return engine_.beatTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
double Metronome::phase() const
|
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
|
double Metronome::tempo() const
|
||||||
{
|
{
|
||||||
return link_state_.tempo;
|
return engine_.tempo();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Metronome::peers() const
|
||||||
|
{
|
||||||
|
return link_.numPeers();
|
||||||
}
|
}
|
||||||
|
|||||||
10
Metronome.h
10
Metronome.h
@@ -1,6 +1,7 @@
|
|||||||
#ifndef METRONOME_H
|
#ifndef METRONOME_H
|
||||||
#define METRONOME_H
|
#define METRONOME_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class Metronome
|
class Metronome
|
||||||
{
|
{
|
||||||
@@ -21,7 +22,16 @@ public:
|
|||||||
bool init ();
|
bool init ();
|
||||||
void terminate();
|
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
|
#endif // METRONOME_H
|
||||||
|
|||||||
13
Settings.cpp
13
Settings.cpp
@@ -249,6 +249,12 @@ void Settings::Save()
|
|||||||
pRoot->InsertEndChild(recent);
|
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
|
// First save : create filename
|
||||||
if (settingsFilename.empty())
|
if (settingsFilename.empty())
|
||||||
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
|
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)
|
void Settings::History::push(const string &filename)
|
||||||
|
|||||||
13
Settings.h
13
Settings.h
@@ -168,6 +168,16 @@ struct SourceConfig
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MetronomeConfig
|
||||||
|
{
|
||||||
|
double tempo;
|
||||||
|
double quantum;
|
||||||
|
|
||||||
|
MetronomeConfig() {
|
||||||
|
tempo = 120.;
|
||||||
|
quantum = 4.;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct Application
|
struct Application
|
||||||
{
|
{
|
||||||
@@ -224,6 +234,9 @@ struct Application
|
|||||||
History recentImport;
|
History recentImport;
|
||||||
std::map< std::string, std::string > dialogRecentFolder;
|
std::map< std::string, std::string > dialogRecentFolder;
|
||||||
|
|
||||||
|
// Metronome
|
||||||
|
MetronomeConfig metronome;
|
||||||
|
|
||||||
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
|
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
|
||||||
scale = 1.f;
|
scale = 1.f;
|
||||||
accent_color = 0;
|
accent_color = 0;
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ using namespace std;
|
|||||||
#include "PickingVisitor.h"
|
#include "PickingVisitor.h"
|
||||||
#include "ImageShader.h"
|
#include "ImageShader.h"
|
||||||
#include "ImageProcessingShader.h"
|
#include "ImageProcessingShader.h"
|
||||||
|
#include "Metronome.h"
|
||||||
|
|
||||||
#include "TextEditor.h"
|
#include "TextEditor.h"
|
||||||
TextEditor editor;
|
TextEditor editor;
|
||||||
@@ -1589,14 +1590,55 @@ void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
|
|||||||
ImGui::Combo("##mode", p_mode,
|
ImGui::Combo("##mode", p_mode,
|
||||||
ICON_FA_TACHOMETER_ALT " Performance\0"
|
ICON_FA_TACHOMETER_ALT " Performance\0"
|
||||||
ICON_FA_HOURGLASS_HALF " Timers\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();
|
ImGui::SameLine();
|
||||||
if (ImGuiToolkit::IconButton(5,8))
|
if (ImGuiToolkit::IconButton(5,8))
|
||||||
ImGui::OpenPopup("metrics_menu");
|
ImGui::OpenPopup("metrics_menu");
|
||||||
ImGui::Spacing();
|
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);
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
||||||
Source *s = Mixer::manager().currentSource();
|
Source *s = Mixer::manager().currentSource();
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -4693,7 +4735,9 @@ void Navigator::RenderMainPannelSettings()
|
|||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
ImGui::SetCursorPosY(width_);
|
ImGui::SetCursorPosY(width_);
|
||||||
|
|
||||||
|
//
|
||||||
// Appearance
|
// Appearance
|
||||||
|
//
|
||||||
ImGui::Text("Appearance");
|
ImGui::Text("Appearance");
|
||||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||||
if ( ImGui::DragFloat("Scale", &Settings::application.scale, 0.01, 0.5f, 2.0f, "%.1f"))
|
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)
|
if (b || o || g)
|
||||||
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
|
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
|
||||||
|
|
||||||
|
//
|
||||||
// Options
|
// Options
|
||||||
|
//
|
||||||
ImGuiToolkit::Spacing();
|
ImGuiToolkit::Spacing();
|
||||||
ImGui::Text("Options");
|
ImGui::Text("Options");
|
||||||
ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor);
|
ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor);
|
||||||
ImGuiToolkit::ButtonSwitch( ICON_FA_TACHOMETER_ALT " Metrics", &Settings::application.widget.stats);
|
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
|
#ifndef NDEBUG
|
||||||
ImGui::Text("Expert");
|
ImGui::Text("Expert");
|
||||||
// ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_HISTORY, &Settings::application.widget.history);
|
// 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_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T");
|
||||||
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
|
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
|
||||||
#endif
|
#endif
|
||||||
|
//
|
||||||
// Recording preferences
|
// Recording preferences
|
||||||
|
//
|
||||||
ImGuiToolkit::Spacing();
|
ImGuiToolkit::Spacing();
|
||||||
ImGui::Text("Recording");
|
ImGui::Text("Recording");
|
||||||
|
|
||||||
@@ -4754,7 +4836,9 @@ void Navigator::RenderMainPannelSettings()
|
|||||||
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
||||||
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Clock\0Framerate\0");
|
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Clock\0Framerate\0");
|
||||||
|
|
||||||
// system preferences
|
//
|
||||||
|
// System preferences
|
||||||
|
//
|
||||||
ImGuiToolkit::Spacing();
|
ImGuiToolkit::Spacing();
|
||||||
ImGui::Text("System");
|
ImGui::Text("System");
|
||||||
ImGui::SameLine( ImGui::GetContentRegionAvailWidth() IMGUI_RIGHT_ALIGN * 0.8);
|
ImGui::SameLine( ImGui::GetContentRegionAvailWidth() IMGUI_RIGHT_ALIGN * 0.8);
|
||||||
|
|||||||
Reference in New Issue
Block a user