mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-16 04:40:08 +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 <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();
|
||||
}
|
||||
|
||||
10
Metronome.h
10
Metronome.h
@@ -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
|
||||
|
||||
13
Settings.cpp
13
Settings.cpp
@@ -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)
|
||||
|
||||
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
|
||||
{
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user