Metronome and Stopwatch User Interface

New Timer window in UI for Metronome (Ableton Link management) and replaces Timers. Former Timers in Metrics are replaced with Runtime (of session, of program and of total vimix runtime in settings). Temporarily disconnected Metronome from MediaPlayer actions.
This commit is contained in:
Bruno Herbelin
2021-11-21 16:54:56 +01:00
parent 1506d36407
commit 3c20314aab
11 changed files with 505 additions and 231 deletions

View File

@@ -28,6 +28,7 @@
#include "BaseToolkit.h"
#include "GstToolkit.h"
#include "RenderingManager.h"
#include "Metronome.h"
#include "MediaPlayer.h"
@@ -49,6 +50,9 @@ MediaPlayer::MediaPlayer()
desired_state_ = GST_STATE_PAUSED;
failed_ = false;
pending_ = false;
metro_linked_ = false;
force_update_ = false;
seeking_ = false;
rewind_on_disable_ = false;
force_software_decoding_ = false;
@@ -597,12 +601,8 @@ void MediaPlayer::setSoftwareDecodingForced(bool on)
reopen();
}
void MediaPlayer::play(bool on)
void MediaPlayer::execute_play_command(bool on)
{
// ignore if disabled, and cannot play an image
if (!enabled_ || media_.isimage)
return;
// request state
GstState requested_state = on ? GST_STATE_PLAYING : GST_STATE_PAUSED;
@@ -619,9 +619,10 @@ void MediaPlayer::play(bool on)
// requesting to play, but stopped at end of stream : rewind first !
if ( desired_state_ == GST_STATE_PLAYING) {
if ( ( rate_ < 0.0 && position_ <= timeline_.next(0) )
|| ( rate_ > 0.0 && position_ >= timeline_.previous(timeline_.last()) ) )
rewind();
if (rate_ > 0.0 && position_ >= timeline_.previous(timeline_.last()))
execute_seek_command(timeline_.next(0));
else if ( rate_ < 0.0 && position_ <= timeline_.next(0) )
execute_seek_command(timeline_.previous(timeline_.last()));
}
// all ready, apply state change immediately
@@ -636,7 +637,25 @@ void MediaPlayer::play(bool on)
else
Log::Info("MediaPlayer %s Stop [%ld]", std::to_string(id_).c_str(), position());
#endif
}
void MediaPlayer::play(bool on)
{
// ignore if disabled, and cannot play an image
if (!enabled_ || media_.isimage || pending_)
return;
// Metronome
if (metro_linked_) {
// busy with this play()
pending_ = true;
// Execute: sync to Metronome if active
Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p, bool o) {
p->execute_play_command(o); p->pending_=false; }, this, on) );
}
else
// execute immediately
execute_play_command( on );
}
bool MediaPlayer::isPlaying(bool testpipeline) const
@@ -666,44 +685,60 @@ void MediaPlayer::setLoop(MediaPlayer::LoopMode mode)
loop_ = mode;
}
//void
void MediaPlayer::rewind(bool force)
{
if (!enabled_ || !media_.seekable)
if (!enabled_ || !media_.seekable || pending_)
return;
// playing forward, loop to begin
if (rate_ > 0.0) {
// begin is the end of a gab which includes the first PTS (if exists)
// normal case, begin is zero
execute_seek_command( timeline_.next(0) );
}
// playing forward, loop to begin;
// begin is the end of a gab which includes the first PTS (if exists)
// normal case, begin is zero
// playing backward, loop to endTimeInterval gap;
else {
// end is the start of a gab which includes the last PTS (if exists)
// normal case, end is last frame
execute_seek_command( timeline_.previous(timeline_.last()) );
}
// end is the start of a gab which includes the last PTS (if exists)
// normal case, end is last frame
GstClockTime target = (rate_ > 0.0) ? timeline_.next(0) : timeline_.previous(timeline_.last());
if (force) {
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
update();
// Metronome
if (metro_linked_) {
// busy with this play()
pending_ = true;
// Execute: sync to Metronome if active
Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p, GstClockTime t, bool f) {
p->execute_seek_command( t, f ); p->pending_=false; }, this, target, force) );
}
else
// execute immediately
execute_seek_command( target, force );
}
void MediaPlayer::step()
{
// useful only when Paused
if (!enabled_ || isPlaying())
if (!enabled_ || isPlaying() || pending_)
return;
if ( ( rate_ < 0.0 && position_ <= timeline_.next(0) )
|| ( rate_ > 0.0 && position_ >= timeline_.previous(timeline_.last()) ) )
rewind();
else {
// step event
GstEvent *stepevent = gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS(rate_), TRUE, FALSE);
// step
gst_element_send_event (pipeline_, gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS(rate_), TRUE, FALSE));
// Metronome
if (metro_linked_) {
// busy with this play()
pending_ = true;
// Execute: sync to Metronome if active
Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p, GstEvent *e) {
gst_element_send_event(p->pipeline_, e); p->pending_=false; }, this, stepevent) );
}
else
// execute immediately
gst_element_send_event (pipeline_, stepevent);
}
}
bool MediaPlayer::go_to(GstClockTime pos)
@@ -893,7 +928,7 @@ void MediaPlayer::update()
}
// prevent unnecessary updates: disabled or already filled image
if (!enabled_ || (media_.isimage && textureindex_>0 ) )
if ( (!enabled_ && !force_update_) || (media_.isimage && textureindex_>0 ) )
return;
// local variables before trying to update
@@ -904,13 +939,6 @@ void MediaPlayer::update()
index_lock_.lock();
// get the last frame filled from fill_frame()
read_index = last_index_;
// // Do NOT miss and jump directly (after seek) to a pre-roll
// for (guint i = 0; i < N_VFRAME; ++i) {
// if (frame_[i].status == PREROLL) {
// read_index = i;
// break;
// }
// }
// unlock access to index change
index_lock_.unlock();
@@ -988,6 +1016,7 @@ void MediaPlayer::update()
execute_loop_command();
}
force_update_ = false;
}
void MediaPlayer::execute_loop_command()
@@ -1004,7 +1033,7 @@ void MediaPlayer::execute_loop_command()
}
}
void MediaPlayer::execute_seek_command(GstClockTime target)
void MediaPlayer::execute_seek_command(GstClockTime target, bool force)
{
if ( pipeline_ == nullptr || !media_.seekable )
return;
@@ -1052,6 +1081,13 @@ void MediaPlayer::execute_seek_command(GstClockTime target)
#endif
}
// Force update
if (force) {
GstState state;
gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
force_update_ = true;
}
}
void MediaPlayer::setPlaySpeed(double s)

View File

@@ -188,6 +188,10 @@ public:
* Seek to zero
* */
void rewind(bool force = false);
/**
* pending
* */
bool pending() const { return pending_; }
/**
* Get position time
* */
@@ -296,11 +300,14 @@ private:
GstVideoInfo v_frame_video_info_;
std::atomic<bool> opened_;
std::atomic<bool> failed_;
bool force_update_;
bool pending_;
bool seeking_;
bool enabled_;
bool rewind_on_disable_;
bool force_software_decoding_;
std::string decoder_name_;
bool metro_linked_;
// fps counter
struct TimeCounter {
@@ -348,8 +355,9 @@ private:
// gst pipeline control
void execute_open();
void execute_play_command(bool on);
void execute_loop_command();
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE);
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE, bool force = false);
// gst frame filling
void init_texture(guint index);

View File

@@ -17,6 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <glm/gtc/matrix_transform.hpp>
#include "defines.h"

View File

@@ -17,6 +17,7 @@
#include "Settings.h"
#include "Metronome.h"
#include "Log.h"
namespace ableton
@@ -129,15 +130,11 @@ Metronome::Metronome()
bool Metronome::init()
{
// connect
link_.enable(true);
// enable sync
link_.enableStartStopSync(true);
// set parameters
setTempo(Settings::application.metronome.tempo);
setQuantum(Settings::application.metronome.quantum);
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;
@@ -146,12 +143,24 @@ bool Metronome::init()
void Metronome::terminate()
{
// save current tempo
Settings::application.metronome.tempo = 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();
@@ -165,7 +174,7 @@ double Metronome::phase() const
void Metronome::setQuantum(double q)
{
engine_.setQuantum(q);
Settings::application.metronome.quantum = engine_.quantum();
Settings::application.timer.link_quantum = engine_.quantum();
}
double Metronome::quantum() const
@@ -178,7 +187,7 @@ 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);
Settings::application.timer.link_tempo = engine_.setTempo(t);
}
double Metronome::tempo() const
@@ -186,11 +195,40 @@ 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();
}
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();
}
size_t Metronome::peers() const
{
return link_.numPeers();

View File

@@ -2,6 +2,8 @@
#define METRONOME_H
#include <chrono>
#include <thread>
#include <functional>
class Metronome
{
@@ -22,8 +24,8 @@ public:
bool init ();
void terminate ();
double beats () const;
double phase () const;
void setEnabled (bool on);
bool enabled () const;
void setTempo (double t);
double tempo () const;
@@ -31,9 +33,31 @@ public:
void setQuantum (double q);
double quantum () const;
void setStartStopSync (bool on);
bool startStopSync () const;
void restart();
// get beat and phase
double beats () const;
double phase () const;
// mechanisms to delay execution to next beat of phase
std::chrono::microseconds timeToBeat();
void executeAtBeat( std::function<void()> f );
size_t peers () const;
};
/// Example calls to executeAtBeat
///
/// With a Lamda function calling a member function of an object
/// - without parameter
/// Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p) { p->rewind(); }, mediaplayer_) );
///
/// - with parameter
/// Metronome::manager().executeAtBeat( std::bind([](MediaPlayer *p, bool o) { p->play(o); }, mediaplayer_, on) );
///
#endif // METRONOME_H

View File

@@ -34,7 +34,7 @@ using namespace tinyxml2;
Settings::Application Settings::application;
string settingsFilename = "";
void Settings::Save()
void Settings::Save(uint64_t runtime)
{
// impose C locale for all app
setlocale(LC_ALL, "C");
@@ -49,6 +49,9 @@ void Settings::Save()
pRoot->SetAttribute("minor", VIMIX_VERSION_MINOR);
xmlDoc.InsertEndChild(pRoot);
#endif
// runtime
if (runtime>0)
pRoot->SetAttribute("runtime", runtime + application.total_runtime);
string comment = "Settings for " + application.name;
XMLComment *pComment = xmlDoc.NewComment(comment.c_str());
@@ -93,7 +96,8 @@ void Settings::Save()
XMLElement *widgetsNode = xmlDoc.NewElement( "Widgets" );
widgetsNode->SetAttribute("preview", application.widget.preview);
widgetsNode->SetAttribute("preview_view", application.widget.preview_view);
widgetsNode->SetAttribute("history", application.widget.history);
widgetsNode->SetAttribute("timer", application.widget.timer);
widgetsNode->SetAttribute("timer_view", application.widget.timer_view);
widgetsNode->SetAttribute("media_player", application.widget.media_player);
widgetsNode->SetAttribute("media_player_view", application.widget.media_player_view);
widgetsNode->SetAttribute("timeline_editmode", application.widget.timeline_editmode);
@@ -250,10 +254,14 @@ void Settings::Save()
}
// Metronome
XMLElement *metroConfNode = xmlDoc.NewElement( "Metronome" );
metroConfNode->SetAttribute("tempo", application.metronome.tempo);
metroConfNode->SetAttribute("quantum", application.metronome.quantum);
pRoot->InsertEndChild(metroConfNode);
XMLElement *timerConfNode = xmlDoc.NewElement( "Timer" );
timerConfNode->SetAttribute("mode", application.timer.mode);
timerConfNode->SetAttribute("link_enabled", application.timer.link_enabled);
timerConfNode->SetAttribute("link_tempo", application.timer.link_tempo);
timerConfNode->SetAttribute("link_quantum", application.timer.link_quantum);
timerConfNode->SetAttribute("link_start_stop_sync", application.timer.link_start_stop_sync);
timerConfNode->SetAttribute("stopwatch_duration", application.timer.stopwatch_duration);
pRoot->InsertEndChild(timerConfNode);
// First save : create filename
if (settingsFilename.empty())
@@ -296,6 +304,8 @@ void Settings::Load()
if (version_major != VIMIX_VERSION_MAJOR || version_minor != VIMIX_VERSION_MINOR)
return;
#endif
// runtime
pRoot->QueryUnsigned64Attribute("runtime", &application.total_runtime);
XMLElement * applicationNode = pRoot->FirstChildElement("Application");
if (applicationNode != nullptr) {
@@ -314,7 +324,8 @@ void Settings::Load()
if (widgetsNode != nullptr) {
widgetsNode->QueryBoolAttribute("preview", &application.widget.preview);
widgetsNode->QueryIntAttribute("preview_view", &application.widget.preview_view);
widgetsNode->QueryBoolAttribute("history", &application.widget.history);
widgetsNode->QueryBoolAttribute("timer", &application.widget.timer);
widgetsNode->QueryIntAttribute("timer_view", &application.widget.timer_view);
widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player);
widgetsNode->QueryIntAttribute("media_player_view", &application.widget.media_player_view);
widgetsNode->QueryBoolAttribute("timeline_editmode", &application.widget.timeline_editmode);
@@ -525,10 +536,14 @@ 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);
XMLElement * timerconfnode = pRoot->FirstChildElement("Timer");
if (timerconfnode != nullptr) {
timerconfnode->QueryUnsigned64Attribute("mode", &application.timer.mode);
timerconfnode->QueryBoolAttribute("link_enabled", &application.timer.link_enabled);
timerconfnode->QueryDoubleAttribute("link_tempo", &application.timer.link_tempo);
timerconfnode->QueryDoubleAttribute("link_quantum", &application.timer.link_quantum);
timerconfnode->QueryBoolAttribute("link_start_stop_sync", &application.timer.link_start_stop_sync);
timerconfnode->QueryUnsigned64Attribute("stopwatch_duration", &application.timer.stopwatch_duration);
}
}
@@ -606,8 +621,6 @@ void Settings::Unlock()
void Settings::Check()
{
Settings::Save();
XMLDocument xmlDoc;
XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str());
if (XMLResultError(eResult)) {

View File

@@ -25,10 +25,11 @@ struct WidgetsConfig
int preview_view;
bool media_player;
int media_player_view;
bool timer;
int timer_view;
bool timeline_editmode;
bool shader_editor;
bool toolbox;
bool history;
bool help;
WidgetsConfig() {
@@ -38,13 +39,14 @@ struct WidgetsConfig
logs = false;
preview = false;
preview_view = -1;
history = false;
media_player = false;
media_player_view = -1;
timeline_editmode = false;
shader_editor = false;
toolbox = false;
help = false;
timer = false;
timer_view = -1;
}
};
@@ -168,18 +170,22 @@ struct SourceConfig
}
};
struct MetronomeConfig
struct TimerConfig
{
bool start_stop_sync;
double tempo;
double quantum;
bool sync_tempo;
uint64_t mode;
bool link_enabled;
double link_tempo;
double link_quantum;
bool link_start_stop_sync;
uint64_t stopwatch_duration;
MetronomeConfig() {
start_stop_sync = true;
tempo = 120.;
quantum = 4.;
sync_tempo = true;
TimerConfig() {
mode = 0;
link_enabled = true;
link_tempo = 120.;
link_quantum = 4.;
link_start_stop_sync = true;
stopwatch_duration = 60;
}
};
@@ -192,6 +198,7 @@ struct Application
// Verification
std::string name;
std::string executable;
uint64_t total_runtime;
// Global settings Application interface
float scale;
@@ -238,8 +245,8 @@ struct Application
History recentImport;
std::map< std::string, std::string > dialogRecentFolder;
// Metronome
MetronomeConfig metronome;
// Metronome & stopwatch
TimerConfig timer;
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
scale = 1.f;
@@ -268,7 +275,7 @@ struct Application
extern Application application;
// Save and Load store settings in XML file
void Save();
void Save(uint64_t runtime = 0);
void Load();
void Lock();
void Unlock();

View File

@@ -109,9 +109,22 @@ FrameGrabber *delayTrigger(FrameGrabber *g, std::chrono::milliseconds delay) {
return g;
}
// Helper functions for imgui window aspect-ratio constraints
struct CustomConstraints
{
static void AspectRatio(ImGuiSizeCallbackData* data) {
float *ar = (float*) data->UserData;
data->DesiredSize.y = (data->CurrentSize.x / (*ar)) + 35.f;
}
static void Square(ImGuiSizeCallbackData* data) {
data->DesiredSize.x = data->DesiredSize.y = (data->DesiredSize.x > data->DesiredSize.y ? data->DesiredSize.x : data->DesiredSize.y);
}
};
UserInterface::UserInterface()
{
start_time = gst_util_get_timestamp ();
ctrl_modifier_active = false;
alt_modifier_active = false;
shift_modifier_active = false;
@@ -204,6 +217,11 @@ bool UserInterface::Init()
return true;
}
uint64_t UserInterface::Runtime() const
{
return gst_util_get_timestamp () - start_time;
}
void UserInterface::handleKeyboard()
{
const ImGuiIO& io = ImGui::GetIO();
@@ -250,11 +268,15 @@ void UserInterface::handleKeyboard()
Settings::application.widget.logs = !Settings::application.widget.logs;
}
else if (ImGui::IsKeyPressed( GLFW_KEY_T )) {
// Timers
Settings::application.widget.timer = !Settings::application.widget.timer;
}
else if (ImGui::IsKeyPressed( GLFW_KEY_G )) {
// Developer toolbox
Settings::application.widget.toolbox = !Settings::application.widget.toolbox;
}
else if (ImGui::IsKeyPressed( GLFW_KEY_H )) {
// Session toolbox
// Helper
Settings::application.widget.help = !Settings::application.widget.help;
}
else if (ImGui::IsKeyPressed( GLFW_KEY_E )) {
@@ -374,11 +396,11 @@ void UserInterface::handleKeyboard()
// 3. hide windows
else if (Settings::application.widget.preview ||
Settings::application.widget.media_player ||
Settings::application.widget.history ||
Settings::application.widget.timer ||
Settings::application.widget.logs) {
Settings::application.widget.preview = false;
Settings::application.widget.media_player = false;
Settings::application.widget.history = false;
Settings::application.widget.timer = false;
Settings::application.widget.logs = false;
}
// 4. cancel selection
@@ -771,8 +793,9 @@ void UserInterface::Render()
if (Settings::application.widget.preview && ( Settings::application.widget.preview_view < 0 ||
Settings::application.widget.preview_view == Settings::application.current_view ))
RenderPreview();
if (Settings::application.widget.history)
RenderHistory();
if (Settings::application.widget.timer && ( Settings::application.widget.timer_view < 0 ||
Settings::application.widget.timer_view == Settings::application.current_view ))
RenderTimer();
if (Settings::application.widget.shader_editor)
RenderShaderEditor();
if (Settings::application.widget.logs)
@@ -944,62 +967,263 @@ void UserInterface::handleScreenshot()
}
}
void UserInterface::RenderHistory()
{
float history_height = 5.f * ImGui::GetFrameHeightWithSpacing();
ImVec2 MinWindowSize = ImVec2(250.f, history_height);
ImGui::SetNextWindowPos(ImVec2(1180, 400), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(MinWindowSize, ImVec2(FLT_MAX, FLT_MAX));
if ( !ImGui::Begin(IMGUI_TITLE_HISTORY, &Settings::application.widget.history, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ))
#define MAX_SEGMENTS 64
void UserInterface::RenderTimer()
{
// timer modes : 0 Metronome, 1 Stopwatch
static std::array< std::string, 2 > timer_menu = { " Metronome ", " Stopwatch " };
// constraint position
static ImVec2 timer_window_pos = ImVec2(1180, 20);
static ImVec2 timer_window_size = ImVec2(400, 400);
SetNextWindowVisible(timer_window_pos, timer_window_size);
// constraint square resizing
ImGui::SetNextWindowSizeConstraints(ImVec2(300, 300), ImVec2(600, 600), CustomConstraints::Square);
if ( !ImGui::Begin(IMGUI_TITLE_TIMER, &Settings::application.widget.timer, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ))
{
ImGui::End();
return;
}
// current window
ImGuiWindow* window = ImGui::GetCurrentWindow();
timer_window_pos = window->Pos;
timer_window_size = window->Size;
// menu (no title bar)
bool tmp = false;
if (ImGui::BeginMenuBar())
{
// Close and widget menu
if (ImGuiToolkit::IconButton(4,16))
Settings::application.widget.history = false;
if (ImGui::BeginMenu(IMGUI_TITLE_HISTORY))
Settings::application.widget.timer = false;
if (ImGui::BeginMenu(IMGUI_TITLE_TIMER))
{
if ( ImGui::MenuItem( ICON_FA_UNDO " Undo", CTRL_MOD "Z") )
Action::manager().undo();
if ( ImGui::MenuItem( ICON_FA_REDO " Redo", CTRL_MOD "Shift+Z") )
Action::manager().redo();
ImGui::MenuItem( ICON_FA_DIRECTIONS " Follow view", nullptr, &Settings::application.action_history_follow_view);
// Enable/Disable Ableton Link
if ( ImGui::MenuItem( ICON_FA_USER_CLOCK " Ableton Link", nullptr, &Settings::application.timer.link_enabled) ) {
Metronome::manager().setEnabled(Settings::application.timer.link_enabled);
}
// output manager menu
ImGui::Separator();
bool pinned = Settings::application.widget.timer_view == Settings::application.current_view;
if ( ImGui::MenuItem( ICON_FA_MAP_PIN " Pin window to view", nullptr, &pinned) ){
if (pinned)
Settings::application.widget.timer_view = Settings::application.current_view;
else
Settings::application.widget.timer_view = -1;
}
if ( ImGui::MenuItem( ICON_FA_TIMES " Close") )
Settings::application.widget.history = false;
Settings::application.widget.timer = false;
ImGui::EndMenu();
}
if ( ImGui::Selectable(ICON_FA_UNDO, &tmp, ImGuiSelectableFlags_None, ImVec2(20,0)))
Action::manager().undo();
if ( ImGui::Selectable(ICON_FA_REDO, &tmp, ImGuiSelectableFlags_None, ImVec2(20,0)))
Action::manager().redo();
// Selection of the timer mode
if (ImGui::BeginMenu( timer_menu[Settings::application.timer.mode].c_str() ))
{
for (size_t i = 0; i < timer_menu.size(); ++i) {
if (ImGui::MenuItem(timer_menu[i].c_str(), NULL, Settings::application.timer.mode==i))
Settings::application.timer.mode = i;
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
if (ImGui::ListBoxHeader("##History", ImGui::GetContentRegionAvail() ) )
{
for (uint i = Action::manager().max(); i > 0; i--) {
// Window draw parameters
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// positions and size of GUI elements
const float margin = window->MenuBarHeight();
const float h = 0.4f * ImGui::GetFrameHeight();
const ImVec2 circle_top_left = window->Pos + ImVec2(margin + h, margin + h);
const ImVec2 circle_top_right = window->Pos + ImVec2(window->Size.y - margin - h, margin + h);
// const ImVec2 circle_botom_left = window->Pos + ImVec2(margin + h, window->Size.x - margin - h);
const ImVec2 circle_botom_right = window->Pos + ImVec2(window->Size.y - margin - h, window->Size.x - margin - h);
const ImVec2 circle_center = window->Pos + (window->Size + ImVec2(margin, margin) )/ 2.f;
const float circle_radius = (window->Size.y - 2.f * margin) / 2.f;
std::string step_label_ = Action::manager().label(i);
// color palette
const ImU32 colorbg = ImGui::GetColorU32(ImGuiCol_FrameBgActive, 0.6f);
const ImU32 colorfg = ImGui::GetColorU32(ImGuiCol_FrameBg, 2.5f);
const ImU32 colorline = ImGui::GetColorU32(ImGuiCol_PlotHistogram);
bool enable = i == Action::manager().current();
if (ImGui::Selectable( step_label_.c_str(), &enable, ImGuiSelectableFlags_AllowDoubleClick )) {
//
// METRONOME
//
if (Settings::application.timer.mode < 1) {
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
Action::manager().stepTo(i);
// Metronome info
double t = Metronome::manager().tempo();
double p = Metronome::manager().phase();
double q = Metronome::manager().quantum();
uint np = (int) Metronome::manager().peers();
// draw background ring
draw_list->AddCircleFilled(circle_center, circle_radius, colorbg, MAX_SEGMENTS);
// draw quarter
static const float resolution = MAX_SEGMENTS / (2.f * M_PI);
static ImVec2 buffer[MAX_SEGMENTS];
float a0 = -M_PI_2 + (floor(p)/floor(q)) * (2.f * M_PI);
float a1 = a0 + (1.f / floor(q)) * (2.f * M_PI);
int n = ImMax(3, (int)((a1 - a0) * resolution));
double da = (a1 - a0) / (n - 1);
int index = 0;
buffer[index++] = circle_center;
for (int i = 0; i < n; ++i) {
double a = a0 + i * da;
buffer[index++] = ImVec2(circle_center.x + circle_radius * cos(a), circle_center.y + circle_radius * sin(a));
}
draw_list->AddConvexPolyFilled(buffer, index, colorfg);
// draw clock hand
a0 = -M_PI_2 + (p/q) * (2.f * M_PI);
draw_list->AddLine(ImVec2(circle_center.x + margin * cos(a0), circle_center.y + margin * sin(a0)),
ImVec2(circle_center.x + circle_radius * cos(a0), circle_center.y + circle_radius * sin(a0)), colorline, 2.f);
// centered indicator 'x / N'
draw_list->AddCircleFilled(circle_center, margin, colorfg, MAX_SEGMENTS);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
char text_buf[24];
sprintf(text_buf, "%d/%d", (int)(p)+1, (int)(q) );
ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL);
ImGui::SetCursorScreenPos(circle_center - label_size/2);
ImGui::Text("%s", text_buf);
ImGui::PopFont();
// left slider : quantum
float float_value = ceil(Metronome::manager().quantum());
ImGui::SetCursorScreenPos(timer_window_pos + ImVec2(0.5f * margin, 1.5f * margin));
if ( ImGui::VSliderFloat("##quantum", ImVec2(0.5f * margin, 2.f * circle_radius ), &float_value, 2, 200, "", 2.f) ){
Metronome::manager().setQuantum( ceil(float_value) );
}
if (ImGui::IsItemHovered() || ImGui::IsItemActive() ) {
ImGui::BeginTooltip();
guint64 time_phase = GST_SECOND * (60.0 * q / t) ;
ImGui::Text("Quantum: %d\nPhase duration: %s", (int) ceil(float_value), GstToolkit::time_to_string(time_phase, GstToolkit::TIME_STRING_READABLE).c_str() );
ImGui::EndTooltip();
}
// Controls NOT operational if peer connected
if (np >0 ) {
// Tempo
ImGui::SetCursorScreenPos(circle_top_right);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
ImGui::PushStyleColor(ImGuiCol_Text, colorfg);
sprintf(text_buf, "%d", (int) ceil(t) );
ImGui::Text("%s", text_buf);
ImGui::PopStyleColor();
ImGui::PopFont();
if (ImGui::IsItemHovered()){
sprintf(text_buf, "%d BPM\n(set by peer)", (int) ceil(t));
ImGuiToolkit::ToolTip(text_buf);
}
}
ImGui::ListBoxFooter();
// Controls operational only if no peer
else {
// Tempo
ImGui::SetCursorScreenPos(circle_top_right);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
sprintf(text_buf, "%d", (int) ceil(t) );
ImGui::Text("%s", text_buf);
ImGui::PopFont();
if (ImGui::IsItemClicked())
ImGui::OpenPopup("bpm_popup");
else if (ImGui::IsItemHovered()){
sprintf(text_buf, "%d BPM\n(clic to edit)", (int) ceil(t));
ImGuiToolkit::ToolTip(text_buf);
}
if (ImGui::BeginPopup("bpm_popup", ImGuiWindowFlags_NoMove))
{
ImGui::SetNextItemWidth(80);
ImGui::InputText("BPM", text_buf, 8, ImGuiInputTextFlags_CharsDecimal);
if (ImGui::IsItemDeactivatedAfterEdit()) {
int t = 0;
sscanf(text_buf, "%d", &t);
t = CLAMP(t, 20, 2000);
Metronome::manager().setTempo((double) t);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
// restart icon
ImGui::SetCursorScreenPos(circle_top_left);
if (ImGuiToolkit::IconButton(9, 13)) {
Metronome::manager().restart();
}
}
// Network indicator, if link enabled
if (Settings::application.timer.link_enabled) {
ImGui::SetCursorScreenPos(circle_botom_right);
ImGuiToolkit::Icon(16, 5, np > 0);
if (ImGui::IsItemHovered()){
sprintf(text_buf, np < 1 ? "Ableton Link\nNo peer" : "Ableton Link\n%d peer%c", np, np < 2 ? ' ' : 's' );
ImGuiToolkit::ToolTip(text_buf);
}
}
}
//
// STOPWATCH
//
else {
// clock times
static guint64 start_time_ = gst_util_get_timestamp ();
static guint64 start_time_hand_ = gst_util_get_timestamp ();
static guint64 duration_hand_ = Settings::application.timer.stopwatch_duration * GST_SECOND;
guint64 time_ = gst_util_get_timestamp ();
// draw ring
draw_list->AddCircle(circle_center, circle_radius, colorbg, MAX_SEGMENTS, 12 );
draw_list->AddCircleFilled(ImVec2(circle_center.x, circle_center.y - circle_radius), 7, colorfg, MAX_SEGMENTS);
// draw indicator time hand
double da = -M_PI_2 + ( (double) (time_-start_time_hand_) / (double) duration_hand_) * (2.f * M_PI);
draw_list->AddCircleFilled(ImVec2(circle_center.x + circle_radius * cos(da), circle_center.y + circle_radius * sin(da)), 7, colorline, MAX_SEGMENTS);
// left slider : countdown
float float_value = (float) Settings::application.timer.stopwatch_duration;
ImGui::SetCursorScreenPos(timer_window_pos + ImVec2(0.5f * margin, 1.5f * margin));
if ( ImGui::VSliderFloat("##duration", ImVec2(0.5f * margin, 2.f * circle_radius ), &float_value, 1, 3600, "", 3.f) ){
Settings::application.timer.stopwatch_duration = (uint64_t) float_value;
duration_hand_ = Settings::application.timer.stopwatch_duration * GST_SECOND;
}
if (ImGui::IsItemHovered() || ImGui::IsItemActive()) {
ImGui::BeginTooltip();
ImGui::Text("Countdown\n%s", GstToolkit::time_to_string(duration_hand_, GstToolkit::TIME_STRING_READABLE).c_str() );
ImGui::EndTooltip();
}
// main text: elapsed time
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
char text_buf[24];
sprintf(text_buf, "%s", GstToolkit::time_to_string(time_-start_time_, GstToolkit::TIME_STRING_FIXED).c_str() );
ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL);
ImGui::SetCursorScreenPos(circle_center - label_size/2);
ImGui::Text("%s", text_buf);
ImGui::PopFont();
// small text: remaining time
ImGui::PushStyleColor(ImGuiCol_Text, colorfg);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
sprintf(text_buf, "%s", GstToolkit::time_to_string(duration_hand_-(time_-start_time_hand_)%duration_hand_, GstToolkit::TIME_STRING_READABLE).c_str() );
label_size = ImGui::CalcTextSize(text_buf, NULL);
ImGui::SetCursorScreenPos(circle_center + ImVec2(0.f, circle_radius * -0.7f) - label_size/2);
ImGui::Text("%s", text_buf);
ImGui::PopFont();
ImGui::PopStyleColor();
// reset icon
ImGui::SetCursorScreenPos(circle_top_left);
if (ImGuiToolkit::IconButton(8, 13))
start_time_ = start_time_hand_ = time_; // reset timers
// TODO : pause ?
}
ImGui::End();
@@ -1014,15 +1238,6 @@ void UserInterface::RenderPreview()
// recording location
static DialogToolkit::OpenFolderDialog recordFolderDialog("Recording Location");
// Helper functions for aspect-ratio constraints
struct CustomConstraints
{
static void AspectRatio(ImGuiSizeCallbackData* data) {
float *ar = (float*) data->UserData;
data->DesiredSize.y = (data->CurrentSize.x / (*ar)) + 35.f;
}
};
FrameBuffer *output = Mixer::manager().session()->frame();
if (output)
{
@@ -1562,9 +1777,6 @@ void UserInterface::RenderShaderEditor()
void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
{
static guint64 start_time_1_ = gst_util_get_timestamp ();
static guint64 start_time_2_ = gst_util_get_timestamp ();
if (!p_corner || !p_open)
return;
@@ -1589,53 +1801,15 @@ void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
ImGui::SetNextItemWidth(200);
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_USER_CLOCK " Metronome\0");
ICON_FA_HOURGLASS_HALF " Runtime\0"
ICON_FA_VECTOR_SQUARE " Source\0");
ImGui::SameLine();
if (ImGuiToolkit::IconButton(5,8))
ImGui::OpenPopup("metrics_menu");
ImGui::Spacing();
if (*p_mode > 2) {
// get values
double t = Metronome::manager().tempo();
double p = Metronome::manager().phase();
double q = Metronome::manager().quantum();
uint n = (int) Metronome::manager().peers();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
// tempo
char buf[32];
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
guint64 time_phase = GST_SECOND * (60.0 * q / t) ;
ImGui::Text("Phase %s", GstToolkit::time_to_string(time_phase, GstToolkit::TIME_STRING_READABLE).c_str());
// metronome
sprintf(buf, "%d/%d", (int)(p)+1, (int)(q) );
ImGui::ProgressBar(ceil(p)/ceil(q), ImVec2(250.f,0.f), buf);
ImGui::PopFont();
}
else if (*p_mode > 1) {
if (*p_mode > 1) {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
Source *s = Mixer::manager().currentSource();
if (s) {
@@ -1690,24 +1864,13 @@ void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
ImGui::PopFont();
}
else if (*p_mode > 0) {
guint64 time_ = gst_util_get_timestamp ();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_1_, GstToolkit::TIME_STRING_FIXED).c_str());
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::Text("Session %s", GstToolkit::time_to_string(Mixer::manager().session()->runtime(), GstToolkit::TIME_STRING_READABLE).c_str());
uint64_t time = Runtime();
ImGui::Text("Program %s", GstToolkit::time_to_string(time, GstToolkit::TIME_STRING_READABLE).c_str());
time += Settings::application.total_runtime;
ImGui::Text("Total %s", GstToolkit::time_to_string(time, GstToolkit::TIME_STRING_READABLE).c_str());
ImGui::PopFont();
ImGui::SameLine(0, 10);
ImGui::PushID( "timermetric1" );
if (ImGuiToolkit::IconButton(12, 14))
start_time_1_ = time_; // reset timer 1
ImGui::PopID();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_2_, GstToolkit::TIME_STRING_FIXED).c_str());
ImGui::PopFont();
ImGui::SameLine(0, 10);
ImGui::PushID( "timermetric2" );
if (ImGuiToolkit::IconButton(12, 14))
start_time_2_ = time_; // reset timer 2
ImGui::PopID();
}
else {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
@@ -2089,7 +2252,7 @@ void HelperToolbox::Render()
///
SourceController::SourceController() : focused_(false), min_width_(0.f), h_space_(0.f), v_space_(0.f), scrollbar_(0.f),
timeline_height_(0.f), mediaplayer_height_(0.f), buttons_width_(0.f), buttons_height_(0.f),
play_toggle_request_(false), replay_request_(false),
play_toggle_request_(false), replay_request_(false), pending_(false),
active_label_(LABEL_AUTO_MEDIA_PLAYER), active_selection_(-1),
selection_context_menu_(false), selection_mediaplayer_(nullptr), selection_target_slower_(0), selection_target_faster_(0),
mediaplayer_active_(nullptr), mediaplayer_edit_fading_(false), mediaplayer_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f)
@@ -3081,7 +3244,7 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp)
ImGui::PopFont();
ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0,
const ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0,
2.f * timeline_height_ + scrollbar_ );
///
@@ -3267,6 +3430,11 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp)
// restore buttons style
ImGui::PopStyleColor(5);
if (mediaplayer_active_->pending())
{
draw_list->AddRectFilled(bottom, bottom + ImVec2(rendersize.x, buttons_height_), ImGui::GetColorU32(ImGuiCol_ScrollbarBg), h_space_);
}
///
/// media player timeline actions
///
@@ -3283,11 +3451,12 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp)
if ( mediaplayer_active_->isPlaying() != media_play ) {
mediaplayer_active_->play( media_play );
}
}
else {
const ImGuiContext& g = *GImGui;
const double width_ratio = static_cast<double>(scrollwindow.x + g.Style.FramePadding.x ) / static_cast<double>(mediaplayer_active_->timeline()->sectionsDuration());
const double width_ratio = static_cast<double>(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast<double>(mediaplayer_active_->timeline()->sectionsDuration());
DrawTimeline("##timeline_mediaplayers", mediaplayer_active_->timeline(), mediaplayer_active_->position(), width_ratio, 2.f * timeline_height_);
///
@@ -4716,6 +4885,15 @@ void Navigator::RenderMainPannelVimix()
if (ImGui::IsItemHovered())
tooltip_ = "Output " CTRL_MOD "D";
ImGui::SameLine(0, 40);
if ( ImGuiToolkit::IconButton( ICON_FA_CLOCK ) ) {
Settings::application.widget.timer = true;
if (Settings::application.widget.timer_view != Settings::application.current_view)
Settings::application.widget.timer_view = -1;
}
if (ImGui::IsItemHovered())
tooltip_ = "Timer " CTRL_MOD "T";
ImGui::PopFont();
if (!tooltip_.empty()) {
ImGuiToolkit::ToolTip(tooltip_.substr(0, tooltip_.size()-12).c_str(), tooltip_.substr(tooltip_.size()-12, 12).c_str());
@@ -4753,47 +4931,11 @@ void Navigator::RenderMainPannelSettings()
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("Time synchronization between computers with Ableton link\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) ceil(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) ceil(Metronome::manager().quantum());
if ( ImGui::SliderInt("Quantum", &q, 2, 100) )
Metronome::manager().setQuantum((double) q);
// ImGuiToolkit::ButtonSwitch( ICON_FA_USER_CLOCK " Start/stop sync", &Settings::application.metronome.start_stop_sync);
#ifndef NDEBUG
ImGui::Text("Expert");
// ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_HISTORY, &Settings::application.widget.history);
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, CTRL_MOD "E");
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T");
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "G");
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
#endif
//
@@ -5118,6 +5260,7 @@ void Thumbnail::Render(float width)
#define SEGMENT_ARRAY_MAX 1000
#define MAXSIZE 65535
void ShowSandbox(bool* p_open)
{
ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);

View File

@@ -131,6 +131,7 @@ class SourceController
float buttons_height_;
bool play_toggle_request_, replay_request_;
bool pending_;
std::string active_label_;
int active_selection_;
InfoVisitor info_;
@@ -185,6 +186,7 @@ class UserInterface
SourceController sourcecontrol;
HelperToolbox sessiontoolbox;
uint64_t start_time;
bool ctrl_modifier_active;
bool alt_modifier_active;
bool shift_modifier_active;
@@ -230,6 +232,8 @@ public:
void Render();
// Post-loop termination
void Terminate();
// Runtime
uint64_t Runtime() const;
// status querries
inline bool ctrlModifier() const { return ctrl_modifier_active; }
@@ -255,7 +259,7 @@ protected:
void RenderMetrics (bool* p_open, int* p_corner, int *p_mode);
void RenderPreview();
void RenderHistory();
void RenderTimer();
void RenderShaderEditor();
int RenderViewNavigator(int* shift);
void RenderAbout(bool* p_open);

View File

@@ -64,10 +64,10 @@
#define IMGUI_TITLE_MAINWINDOW ICON_FA_CIRCLE_NOTCH " vimix"
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_PLAY_CIRCLE " Player"
#define IMGUI_TITLE_HISTORY ICON_FA_HISTORY " History"
#define IMGUI_TITLE_TIMER ICON_FA_CLOCK " Timer"
#define IMGUI_TITLE_LOGS ICON_FA_LIST " Logs"
#define IMGUI_TITLE_HELP ICON_FA_LIFE_RING " Help"
#define IMGUI_TITLE_TOOLBOX ICON_FA_WRENCH " Development Toolbox"
#define IMGUI_TITLE_TOOLBOX ICON_FA_HAMSA " Guru Toolbox"
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Code Editor"
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput"
#define IMGUI_TITLE_DELETE ICON_FA_BROOM " Delete?"

View File

@@ -85,13 +85,13 @@ int main(int argc, char *argv[])
/// lock to inform an instance is running
Settings::Lock();
///
///
/// CONNECTION INIT
///
if ( !Connection::manager().init() )
return 1;
///
/// METRONOME INIT
///
if ( !Metronome::manager().init() )
@@ -162,7 +162,7 @@ int main(int argc, char *argv[])
///
/// Settings
///
Settings::Save();
Settings::Save(UserInterface::manager().Runtime());
/// ok
return 0;