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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@
#include "Settings.h" #include "Settings.h"
#include "Metronome.h" #include "Metronome.h"
#include "Log.h"
namespace ableton namespace ableton
@@ -129,15 +130,11 @@ Metronome::Metronome()
bool Metronome::init() bool Metronome::init()
{ {
// connect
link_.enable(true);
// enable sync
link_.enableStartStopSync(true);
// set parameters // set parameters
setTempo(Settings::application.metronome.tempo); setEnabled(Settings::application.timer.link_enabled);
setQuantum(Settings::application.metronome.quantum); setTempo(Settings::application.timer.link_tempo);
setQuantum(Settings::application.timer.link_quantum);
setStartStopSync(Settings::application.timer.link_start_stop_sync);
// no reason for failure? // no reason for failure?
return true; return true;
@@ -146,12 +143,24 @@ bool Metronome::init()
void Metronome::terminate() void Metronome::terminate()
{ {
// save current tempo // save current tempo
Settings::application.metronome.tempo = tempo(); Settings::application.timer.link_tempo = tempo();
// disconnect // disconnect
link_.enable(false); 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 double Metronome::beats() const
{ {
return engine_.beatTime(); return engine_.beatTime();
@@ -165,7 +174,7 @@ double Metronome::phase() const
void Metronome::setQuantum(double q) void Metronome::setQuantum(double q)
{ {
engine_.setQuantum(q); engine_.setQuantum(q);
Settings::application.metronome.quantum = engine_.quantum(); Settings::application.timer.link_quantum = engine_.quantum();
} }
double Metronome::quantum() const double Metronome::quantum() const
@@ -178,7 +187,7 @@ void Metronome::setTempo(double t)
// set the tempo to t // set the tempo to t
// OR // OR
// adopt the last tempo value that have been proposed on the network // 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 double Metronome::tempo() const
@@ -186,11 +195,40 @@ double Metronome::tempo() const
return engine_.tempo(); 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() std::chrono::microseconds Metronome::timeToBeat()
{ {
return engine_.timeNextBeat() - engine_.now(); 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 size_t Metronome::peers() const
{ {
return link_.numPeers(); return link_.numPeers();

View File

@@ -2,6 +2,8 @@
#define METRONOME_H #define METRONOME_H
#include <chrono> #include <chrono>
#include <thread>
#include <functional>
class Metronome class Metronome
{ {
@@ -22,8 +24,8 @@ public:
bool init (); bool init ();
void terminate (); void terminate ();
double beats () const; void setEnabled (bool on);
double phase () const; bool enabled () const;
void setTempo (double t); void setTempo (double t);
double tempo () const; double tempo () const;
@@ -31,9 +33,31 @@ public:
void setQuantum (double q); void setQuantum (double q);
double quantum () const; 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(); std::chrono::microseconds timeToBeat();
void executeAtBeat( std::function<void()> f );
size_t peers () const; 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 #endif // METRONOME_H

View File

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

View File

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

View File

@@ -109,9 +109,22 @@ FrameGrabber *delayTrigger(FrameGrabber *g, std::chrono::milliseconds delay) {
return g; 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() UserInterface::UserInterface()
{ {
start_time = gst_util_get_timestamp ();
ctrl_modifier_active = false; ctrl_modifier_active = false;
alt_modifier_active = false; alt_modifier_active = false;
shift_modifier_active = false; shift_modifier_active = false;
@@ -204,6 +217,11 @@ bool UserInterface::Init()
return true; return true;
} }
uint64_t UserInterface::Runtime() const
{
return gst_util_get_timestamp () - start_time;
}
void UserInterface::handleKeyboard() void UserInterface::handleKeyboard()
{ {
const ImGuiIO& io = ImGui::GetIO(); const ImGuiIO& io = ImGui::GetIO();
@@ -250,11 +268,15 @@ void UserInterface::handleKeyboard()
Settings::application.widget.logs = !Settings::application.widget.logs; Settings::application.widget.logs = !Settings::application.widget.logs;
} }
else if (ImGui::IsKeyPressed( GLFW_KEY_T )) { 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 // Developer toolbox
Settings::application.widget.toolbox = !Settings::application.widget.toolbox; Settings::application.widget.toolbox = !Settings::application.widget.toolbox;
} }
else if (ImGui::IsKeyPressed( GLFW_KEY_H )) { else if (ImGui::IsKeyPressed( GLFW_KEY_H )) {
// Session toolbox // Helper
Settings::application.widget.help = !Settings::application.widget.help; Settings::application.widget.help = !Settings::application.widget.help;
} }
else if (ImGui::IsKeyPressed( GLFW_KEY_E )) { else if (ImGui::IsKeyPressed( GLFW_KEY_E )) {
@@ -374,11 +396,11 @@ void UserInterface::handleKeyboard()
// 3. hide windows // 3. hide windows
else if (Settings::application.widget.preview || else if (Settings::application.widget.preview ||
Settings::application.widget.media_player || Settings::application.widget.media_player ||
Settings::application.widget.history || Settings::application.widget.timer ||
Settings::application.widget.logs) { Settings::application.widget.logs) {
Settings::application.widget.preview = false; Settings::application.widget.preview = false;
Settings::application.widget.media_player = false; Settings::application.widget.media_player = false;
Settings::application.widget.history = false; Settings::application.widget.timer = false;
Settings::application.widget.logs = false; Settings::application.widget.logs = false;
} }
// 4. cancel selection // 4. cancel selection
@@ -771,8 +793,9 @@ void UserInterface::Render()
if (Settings::application.widget.preview && ( Settings::application.widget.preview_view < 0 || if (Settings::application.widget.preview && ( Settings::application.widget.preview_view < 0 ||
Settings::application.widget.preview_view == Settings::application.current_view )) Settings::application.widget.preview_view == Settings::application.current_view ))
RenderPreview(); RenderPreview();
if (Settings::application.widget.history) if (Settings::application.widget.timer && ( Settings::application.widget.timer_view < 0 ||
RenderHistory(); Settings::application.widget.timer_view == Settings::application.current_view ))
RenderTimer();
if (Settings::application.widget.shader_editor) if (Settings::application.widget.shader_editor)
RenderShaderEditor(); RenderShaderEditor();
if (Settings::application.widget.logs) 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); #define MAX_SEGMENTS 64
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(MinWindowSize, ImVec2(FLT_MAX, FLT_MAX)); void UserInterface::RenderTimer()
if ( !ImGui::Begin(IMGUI_TITLE_HISTORY, &Settings::application.widget.history, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse )) {
// 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(); ImGui::End();
return; return;
} }
// current window
ImGuiWindow* window = ImGui::GetCurrentWindow();
timer_window_pos = window->Pos;
timer_window_size = window->Size;
// menu (no title bar) // menu (no title bar)
bool tmp = false;
if (ImGui::BeginMenuBar()) if (ImGui::BeginMenuBar())
{ {
// Close and widget menu
if (ImGuiToolkit::IconButton(4,16)) if (ImGuiToolkit::IconButton(4,16))
Settings::application.widget.history = false; Settings::application.widget.timer = false;
if (ImGui::BeginMenu(IMGUI_TITLE_HISTORY)) if (ImGui::BeginMenu(IMGUI_TITLE_TIMER))
{ {
if ( ImGui::MenuItem( ICON_FA_UNDO " Undo", CTRL_MOD "Z") ) // Enable/Disable Ableton Link
Action::manager().undo(); if ( ImGui::MenuItem( ICON_FA_USER_CLOCK " Ableton Link", nullptr, &Settings::application.timer.link_enabled) ) {
if ( ImGui::MenuItem( ICON_FA_REDO " Redo", CTRL_MOD "Shift+Z") ) Metronome::manager().setEnabled(Settings::application.timer.link_enabled);
Action::manager().redo(); }
ImGui::MenuItem( ICON_FA_DIRECTIONS " Follow view", nullptr, &Settings::application.action_history_follow_view);
// 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") ) if ( ImGui::MenuItem( ICON_FA_TIMES " Close") )
Settings::application.widget.history = false; Settings::application.widget.timer = false;
ImGui::EndMenu(); ImGui::EndMenu();
} }
if ( ImGui::Selectable(ICON_FA_UNDO, &tmp, ImGuiSelectableFlags_None, ImVec2(20,0))) // Selection of the timer mode
Action::manager().undo(); if (ImGui::BeginMenu( timer_menu[Settings::application.timer.mode].c_str() ))
if ( ImGui::Selectable(ICON_FA_REDO, &tmp, ImGuiSelectableFlags_None, ImVec2(20,0))) {
Action::manager().redo(); 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(); ImGui::EndMenuBar();
} }
if (ImGui::ListBoxHeader("##History", ImGui::GetContentRegionAvail() ) ) // Window draw parameters
{ ImDrawList* draw_list = ImGui::GetWindowDrawList();
for (uint i = Action::manager().max(); i > 0; i--) { // 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)) // Metronome info
Action::manager().stepTo(i); 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(); ImGui::End();
@@ -1014,15 +1238,6 @@ void UserInterface::RenderPreview()
// recording location // recording location
static DialogToolkit::OpenFolderDialog recordFolderDialog("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(); FrameBuffer *output = Mixer::manager().session()->frame();
if (output) if (output)
{ {
@@ -1562,9 +1777,6 @@ void UserInterface::RenderShaderEditor()
void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode) 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) if (!p_corner || !p_open)
return; return;
@@ -1589,53 +1801,15 @@ void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
ImGui::SetNextItemWidth(200); ImGui::SetNextItemWidth(200);
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 " Runtime\0"
ICON_FA_VECTOR_SQUARE " Source\0" ICON_FA_VECTOR_SQUARE " Source\0");
ICON_FA_USER_CLOCK " Metronome\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 > 2) { if (*p_mode > 1) {
// 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) {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
Source *s = Mixer::manager().currentSource(); Source *s = Mixer::manager().currentSource();
if (s) { if (s) {
@@ -1690,24 +1864,13 @@ void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
ImGui::PopFont(); ImGui::PopFont();
} }
else if (*p_mode > 0) { else if (*p_mode > 0) {
guint64 time_ = gst_util_get_timestamp (); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::Text("Session %s", GstToolkit::time_to_string(Mixer::manager().session()->runtime(), GstToolkit::TIME_STRING_READABLE).c_str());
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); uint64_t time = Runtime();
ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_1_, GstToolkit::TIME_STRING_FIXED).c_str()); 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::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 { else {
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); 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), 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), 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), active_label_(LABEL_AUTO_MEDIA_PLAYER), active_selection_(-1),
selection_context_menu_(false), selection_mediaplayer_(nullptr), selection_target_slower_(0), selection_target_faster_(0), 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) 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(); 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_ ); 2.f * timeline_height_ + scrollbar_ );
/// ///
@@ -3267,6 +3430,11 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp)
// restore buttons style // restore buttons style
ImGui::PopStyleColor(5); 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 /// media player timeline actions
/// ///
@@ -3283,11 +3451,12 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp)
if ( mediaplayer_active_->isPlaying() != media_play ) { if ( mediaplayer_active_->isPlaying() != media_play ) {
mediaplayer_active_->play( media_play ); mediaplayer_active_->play( media_play );
} }
} }
else { else {
const ImGuiContext& g = *GImGui; 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_); 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()) if (ImGui::IsItemHovered())
tooltip_ = "Output " CTRL_MOD "D"; 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(); ImGui::PopFont();
if (!tooltip_.empty()) { if (!tooltip_.empty()) {
ImGuiToolkit::ToolTip(tooltip_.substr(0, tooltip_.size()-12).c_str(), tooltip_.substr(tooltip_.size()-12, 12).c_str()); 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_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("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 #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);
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, CTRL_MOD "E"); 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"); ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
#endif #endif
// //
@@ -5118,6 +5260,7 @@ void Thumbnail::Render(float width)
#define SEGMENT_ARRAY_MAX 1000 #define SEGMENT_ARRAY_MAX 1000
#define MAXSIZE 65535 #define MAXSIZE 65535
void ShowSandbox(bool* p_open) void ShowSandbox(bool* p_open)
{ {
ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver); ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);

View File

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

View File

@@ -64,10 +64,10 @@
#define IMGUI_TITLE_MAINWINDOW ICON_FA_CIRCLE_NOTCH " vimix" #define IMGUI_TITLE_MAINWINDOW ICON_FA_CIRCLE_NOTCH " vimix"
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_PLAY_CIRCLE " Player" #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_LOGS ICON_FA_LIST " Logs"
#define IMGUI_TITLE_HELP ICON_FA_LIFE_RING " Help" #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_SHADEREDITOR ICON_FA_CODE " Code Editor"
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput" #define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput"
#define IMGUI_TITLE_DELETE ICON_FA_BROOM " Delete?" #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 /// lock to inform an instance is running
Settings::Lock(); Settings::Lock();
///
/// ///
/// CONNECTION INIT /// CONNECTION INIT
/// ///
if ( !Connection::manager().init() ) if ( !Connection::manager().init() )
return 1; return 1;
///
/// METRONOME INIT /// METRONOME INIT
/// ///
if ( !Metronome::manager().init() ) if ( !Metronome::manager().init() )
@@ -162,7 +162,7 @@ int main(int argc, char *argv[])
/// ///
/// Settings /// Settings
/// ///
Settings::Save(); Settings::Save(UserInterface::manager().Runtime());
/// ok /// ok
return 0; return 0;