From 809e30d9060b44140727f2eb25ff3abe3053a14d Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sat, 27 Nov 2021 19:26:33 +0100 Subject: [PATCH] Timeline display in beat unit for synched to metronome --- ImGuiToolkit.cpp | 242 +++++++++++++++++++++++++++++++-------- ImGuiToolkit.h | 7 +- UserInterfaceManager.cpp | 14 ++- 3 files changed, 208 insertions(+), 55 deletions(-) diff --git a/ImGuiToolkit.cpp b/ImGuiToolkit.cpp index 48084c6..46b5290 100644 --- a/ImGuiToolkit.cpp +++ b/ImGuiToolkit.cpp @@ -584,7 +584,8 @@ bool ImGuiToolkit::SliderTiming (const char* label, uint* ms, uint v_min, uint v #define LARGE_TICK_INCREMENT 1 #define LABEL_TICK_INCREMENT 3 -void ImGuiToolkit::RenderTimeline (ImGuiWindow* window, ImRect timeline_bbox, guint64 begin, guint64 end, guint64 step, bool verticalflip) +void ImGuiToolkit::RenderTimeline (ImGuiWindow* window, ImRect timeline_bbox, + guint64 begin, guint64 end, guint64 step, bool verticalflip) { static guint64 optimal_tick_marks[NUM_MARKS + LABEL_TICK_INCREMENT] = { 100 * MILISECOND, 500 * MILISECOND, 1 * SECOND, 2 * SECOND, 5 * SECOND, 10 * SECOND, 20 * SECOND, 1 * MINUTE, 2 * MINUTE, 5 * MINUTE, 10 * MINUTE, 60 * MINUTE, 60 * MINUTE }; @@ -719,7 +720,151 @@ void ImGuiToolkit::RenderTimeline (ImGuiWindow* window, ImRect timeline_bbox, gu ImGui::PopStyleColor(1); } -bool ImGuiToolkit::TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, guint64 end, guint64 step, const float width) + +void ImGuiToolkit::RenderTimelineBPM (ImGuiWindow* window, ImRect timeline_bbox, double tempo, double quantum, + guint64 begin, guint64 end, guint64 step, bool verticalflip) +{ + if (tempo<1.) + return; + + + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float fontsize = g.FontSize; + const ImU32 text_color = ImGui::GetColorU32(ImGuiCol_Text); + + // keep duration + const guint64 duration = end - begin; + + // by default, put a tick mark at every frame step + guint64 tick_step = step; + + // how many pixels to represent one frame step? + const float step_ = static_cast ( static_cast(tick_step) / static_cast(duration) ); + + // spacing between small tick marks for frames + for (float tick_step_pixels = timeline_bbox.GetWidth() * step_; tick_step_pixels < 5.f; tick_step *= 2) + { + tick_step_pixels = timeline_bbox.GetWidth() * static_cast ( static_cast(tick_step) / static_cast(duration) ); + } + + // by default, put large mark every beat and label every phase + guint64 time_beat = GST_SECOND * (60.0 / tempo) ; + guint64 large_tick_step = time_beat; + + // how many pixels to represent one beat? + const float beat_ = static_cast ( static_cast(large_tick_step) / static_cast(duration) ); + + // spacing between small tick marks for frames + for (float tick_beat_pixels = timeline_bbox.GetWidth() * beat_; tick_beat_pixels < 10.f; large_tick_step *= 2) + { + tick_beat_pixels = timeline_bbox.GetWidth() * static_cast ( static_cast(large_tick_step) / static_cast(duration) ); + } + + guint64 label_tick_step = large_tick_step * (guint64) ceil(quantum); + + // render tics and text + char text_buf[24]; + + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); + + // render tick and text END + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%s", + GstToolkit::time_to_string(end, GstToolkit::TIME_STRING_MINIMAL).c_str()); + ImVec2 duration_label_size = ImGui::CalcTextSize(text_buf, NULL); + ImVec2 duration_label_pos = timeline_bbox.GetTR() + ImVec2( -2.f -duration_label_size.x, fontsize); + if (verticalflip) + duration_label_pos.y -= fontsize; + ImGui::RenderTextClipped( duration_label_pos, duration_label_pos + duration_label_size, + text_buf, NULL, &duration_label_size); + window->DrawList->AddLine( timeline_bbox.GetTR(), timeline_bbox.GetBR(), text_color, 1.5f); + + // render tick and text START + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%s", + GstToolkit::time_to_string(begin, GstToolkit::TIME_STRING_MINIMAL).c_str()); + ImVec2 beginning_label_size = ImGui::CalcTextSize(text_buf, NULL); + ImVec2 beginning_label_pos = timeline_bbox.GetTL() + ImVec2(3.f, fontsize); + if (verticalflip) + beginning_label_pos.y -= fontsize; + if ( beginning_label_pos.x + beginning_label_size.x < duration_label_pos . x) { + ImGui::RenderTextClipped( beginning_label_pos, beginning_label_pos + beginning_label_size, + text_buf, NULL, &beginning_label_size); + } + window->DrawList->AddLine( timeline_bbox.GetTL(), timeline_bbox.GetBL(), text_color, 1.5f); + + ImGui::PopFont(); + + // render the tick marks along TIMELINE + ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] -ImVec4(0.f,0.f,0.f,0.4f)); + ImVec2 pos = verticalflip ? timeline_bbox.GetBL() : timeline_bbox.GetTL(); + + // SMALL ticks every step + float tick_length = style.FramePadding.y; + guint64 tick = 0; + while ( tick < end ) + { + // draw the tick mark each step + window->DrawList->AddLine( pos, pos + ImVec2(0.f, verticalflip ? -tick_length : tick_length), text_color); + + // next tick + tick += tick_step; + float tick_percent = static_cast ( static_cast(tick-begin) / static_cast(duration) ); + if (verticalflip) + pos = ImLerp(timeline_bbox.GetBL(), timeline_bbox.GetBR(), tick_percent); + else + pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent); + + } + + // LARGE tick marks every tempo + pos = verticalflip ? timeline_bbox.GetBL() : timeline_bbox.GetTL(); + tick_length = fontsize - style.FramePadding.y; + tick = 0; + while ( tick < end ) + { + // label tick mark + if ( (tick%label_tick_step) < 1 ) + { + // beat count label + guint64 ticklabel = tick / time_beat; + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%ld", ticklabel); + ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL); + ImVec2 mini = ImVec2( pos.x - label_size.x / 2.f, pos.y); + ImVec2 maxi = ImVec2( pos.x + label_size.x / 2.f, pos.y); + + if (verticalflip) { + mini.y -= tick_length + label_size.y; + maxi.y -= tick_length; + } + else { + mini.y += tick_length; + maxi.y += tick_length + label_size.y; + } + + // do not overlap with labels for beginning and duration + if (mini.x - style.ItemSpacing.x > (beginning_label_pos.x + beginning_label_size.x) && maxi.x + style.ItemSpacing.x < duration_label_pos.x) + ImGui::RenderTextClipped(mini, maxi, text_buf, NULL, &label_size); + } + + // draw the tick mark each step + window->DrawList->AddLine( pos, pos + ImVec2(0.f, verticalflip ? -tick_length : tick_length), text_color); + + // next tick + tick += large_tick_step; + float tick_percent = static_cast ( static_cast(tick-begin) / static_cast(duration) ); + if (verticalflip) + pos = ImLerp(timeline_bbox.GetBL(), timeline_bbox.GetBR(), tick_percent); + else + pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent); + + } + + ImGui::PopStyleColor(1); +} + + +bool ImGuiToolkit::TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, + guint64 end, guint64 step, const float width, double tempo, double quantum) { // get window ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -788,7 +933,7 @@ bool ImGuiToolkit::TimelineSlider (const char* label, guint64 *time, guint64 beg &time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb); if (value_changed){ *time = static_cast ( 0.1 * static_cast(time_slider) * static_cast(end - begin) ); - if (first != -1) + if (first != GST_CLOCK_TIME_NONE) *time -= first; grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive); } @@ -802,7 +947,10 @@ bool ImGuiToolkit::TimelineSlider (const char* label, guint64 *time, guint64 beg ImGui::RenderFrame(bbox.Min, bbox.Max, frame_col, true, style.FrameRounding); // render the timeline - RenderTimeline(window, timeline_bbox, begin, end, step); + if (tempo > 0 && quantum > 0) + RenderTimelineBPM(window, timeline_bbox, tempo, quantum, begin, end, step); + else + RenderTimeline(window, timeline_bbox, begin, end, step); // draw slider grab handle if (grab_slider_bb.Max.x > grab_slider_bb.Min.x) { @@ -817,59 +965,59 @@ bool ImGuiToolkit::TimelineSlider (const char* label, guint64 *time, guint64 beg } -void ImGuiToolkit::Timeline (const char* label, guint64 time, guint64 begin, guint64 end, guint64 step, const float width) -{ - // get window - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return; +//void ImGuiToolkit::Timeline (const char* label, guint64 time, guint64 begin, guint64 end, guint64 step, const float width) +//{ +// // get window +// ImGuiWindow* window = ImGui::GetCurrentWindow(); +// if (window->SkipItems) +// return; - // get style & id - const ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const float fontsize = g.FontSize; - const ImGuiID id = window->GetID(label); +// // get style & id +// const ImGuiContext& g = *GImGui; +// const ImGuiStyle& style = g.Style; +// const float fontsize = g.FontSize; +// const ImGuiID id = window->GetID(label); - // - // FIRST PREPARE ALL data structures - // +// // +// // FIRST PREPARE ALL data structures +// // - // widget bounding box - const float height = 2.f * (fontsize + style.FramePadding.y); - ImVec2 pos = window->DC.CursorPos; - ImVec2 size = ImVec2(width, height); - ImRect bbox(pos, pos + size); - ImGui::ItemSize(size, style.FramePadding.y); - if (!ImGui::ItemAdd(bbox, id)) - return; +// // widget bounding box +// const float height = 2.f * (fontsize + style.FramePadding.y); +// ImVec2 pos = window->DC.CursorPos; +// ImVec2 size = ImVec2(width, height); +// ImRect bbox(pos, pos + size); +// ImGui::ItemSize(size, style.FramePadding.y); +// if (!ImGui::ItemAdd(bbox, id)) +// return; - // cursor size - const float cursor_width = 0.5f * fontsize; +// // cursor size +// const float cursor_width = 0.5f * fontsize; - // TIMELINE is inside the bbox, in a slightly smaller bounding box - ImRect timeline_bbox(bbox); - timeline_bbox.Expand( ImVec2() - style.FramePadding ); +// // TIMELINE is inside the bbox, in a slightly smaller bounding box +// ImRect timeline_bbox(bbox); +// timeline_bbox.Expand( ImVec2() - style.FramePadding ); - // units conversion: from time to float (calculation made with higher precision first) - guint64 duration = end - begin; - float time_ = static_cast ( static_cast(time - begin) / static_cast(duration) ); +// // units conversion: from time to float (calculation made with higher precision first) +// guint64 duration = end - begin; +// float time_ = static_cast ( static_cast(time - begin) / static_cast(duration) ); - // - // THIRD RENDER - // +// // +// // THIRD RENDER +// // - // Render the bounding box - ImGui::RenderFrame(bbox.Min, bbox.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); +// // Render the bounding box +// ImGui::RenderFrame(bbox.Min, bbox.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); - // render the timeline - RenderTimeline(window, timeline_bbox, begin, end, step); +// // render the timeline +// RenderTimeline(window, timeline_bbox, begin, end, step); - // draw the cursor - if ( time_ > -FLT_EPSILON && time_ < 1.f ) { - pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f); - ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up); - } -} +// // draw the cursor +// if ( time_ > -FLT_EPSILON && time_ < 1.f ) { +// pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f); +// ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up); +// } +//} bool ImGuiToolkit::InvisibleSliderInt (const char* label, uint *index, uint min, uint max, ImVec2 size) { diff --git a/ImGuiToolkit.h b/ImGuiToolkit.h index 8ccc442..fa0e9d1 100644 --- a/ImGuiToolkit.h +++ b/ImGuiToolkit.h @@ -3,10 +3,8 @@ #include #include -#include #include - #include "imgui.h" #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS @@ -45,9 +43,10 @@ namespace ImGuiToolkit // sliders bool SliderTiming (const char* label, uint *ms, uint v_min, uint v_max, uint v_step, const char* text_max = nullptr); - bool TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, guint64 end, guint64 step, const float width); + bool TimelineSlider (const char* label, guint64 *time, guint64 begin, guint64 first, guint64 end, guint64 step, const float width, double tempo = 0, double quantum = 0); void RenderTimeline (struct ImGuiWindow* window, struct ImRect timeline_bbox, guint64 begin, guint64 end, guint64 step, bool verticalflip = false); - void Timeline (const char* label, guint64 time, guint64 begin, guint64 end, guint64 step, const float width); + void RenderTimelineBPM (struct ImGuiWindow* window, struct ImRect timeline_bbox, double tempo, double quantum, guint64 begin, guint64 end, guint64 step, bool verticalflip = false); +// void Timeline (const char* label, guint64 time, guint64 begin, guint64 end, guint64 step, const float width); bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size); bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size); bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, guint64 begin, guint64 end, bool cut, bool *released, const ImVec2 size); diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 1deee74..1fec00b 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -1119,7 +1119,8 @@ void UserInterface::RenderTimer() 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::Text("%d beats per phase\n= %s at %d BPM", (int) ceil(float_value), + GstToolkit::time_to_string(time_phase, GstToolkit::TIME_STRING_READABLE).c_str(), (int) t); ImGui::EndTooltip(); } @@ -1210,7 +1211,7 @@ void UserInterface::RenderTimer() } 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::Text("%s\ncountdown", GstToolkit::time_to_string(duration_hand_, GstToolkit::TIME_STRING_READABLE).c_str() ); ImGui::EndTooltip(); } @@ -3320,8 +3321,13 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) } // custom timeline slider - mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(), - tl->first(), tl->end(), tl->step(), size.x); + if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE) + mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(), + tl->first(), tl->end(), tl->step(), size.x, + Metronome::manager().tempo(), Metronome::manager().quantum()); + else + mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(), + tl->first(), tl->end(), tl->step(), size.x); } }