diff --git a/gtk2_ardour/cue_editor.cc b/gtk2_ardour/cue_editor.cc index a9c43f4b98..1f76afaaff 100644 --- a/gtk2_ardour/cue_editor.cc +++ b/gtk2_ardour/cue_editor.cc @@ -168,6 +168,12 @@ CueEditor::reset_zoom (samplecnt_t n) ZoomChanged(); /* EMIT SIGNAL */ } +samplecnt_t +CueEditor::get_current_zoom () const +{ + return samples_per_pixel; +} + void CueEditor::reposition_and_zoom (samplepos_t, double) { diff --git a/gtk2_ardour/midi_cue_editor.cc b/gtk2_ardour/midi_cue_editor.cc index 3c340bbd4d..4f2d3d9cf9 100644 --- a/gtk2_ardour/midi_cue_editor.cc +++ b/gtk2_ardour/midi_cue_editor.cc @@ -50,6 +50,9 @@ MidiCueEditor::MidiCueEditor() , horizontal_adjustment (0.0, 0.0, 1e16) , view (nullptr) , mouse_mode (Editing::MouseDraw) + , bbt_metric (*this) + , timebar_height (15.) + , n_timebars (1) { build_canvas (); @@ -117,7 +120,23 @@ MidiCueEditor::build_canvas () rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect")); CANVAS_DEBUG_NAME (rubberband_rect, X_("cue rubberband rect")); - bg = new CueMidiBackground (hv_scroll_group); + + Pango::FontDescription font (UIConfiguration::instance().get_SmallerFont()); + Pango::FontDescription larger_font (UIConfiguration::instance().get_SmallBoldFont()); + bbt_ruler = new ArdourCanvas::Ruler (time_line_group, &bbt_metric, ArdourCanvas::Rect (0, 0, ArdourCanvas::COORD_MAX, timebar_height)); + bbt_ruler->set_font_description (font); + bbt_ruler->set_second_font_description (larger_font); + Gtkmm2ext::Color base = UIConfiguration::instance().color ("ruler base"); + Gtkmm2ext::Color text = UIConfiguration::instance().color ("ruler text"); + bbt_ruler->set_fill_color (base); + bbt_ruler->set_outline_color (text); + CANVAS_DEBUG_NAME (bbt_ruler, "bbt ruler"); + + data_group = new ArdourCanvas::Container (hv_scroll_group); + data_group->move (ArdourCanvas::Duple (0., timebar_height * n_timebars)); + CANVAS_DEBUG_NAME (data_group, "cue data group"); + + bg = new CueMidiBackground (data_group); _canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &MidiCueEditor::canvas_allocate)); _canvas->set_name ("MidiCueCanvas"); @@ -139,7 +158,6 @@ MidiCueEditor::canvas_enter_leave (GdkEventCrossing* ev) _canvas_viewport->canvas()->grab_focus (); ActionManager::set_sensitive (_midi_actions, true); EditingContext::push_editing_context (this); - std::cerr << "GRAB FOCUS\n"; } break; case GDK_LEAVE_NOTIFY: @@ -147,7 +165,6 @@ MidiCueEditor::canvas_enter_leave (GdkEventCrossing* ev) ActionManager::set_sensitive (_midi_actions, false); ARDOUR_UI::instance()->reset_focus (_canvas_viewport); EditingContext::pop_editing_context (); - std::cerr << "DROP FOCUS\n"; } default: break; @@ -159,8 +176,9 @@ void MidiCueEditor::canvas_allocate (Gtk::Allocation alloc) { bg->set_size (alloc.get_width(), alloc.get_height()); + if (view) { - view->set_size (alloc.get_width(), alloc.get_height()); + view->set_size (alloc.get_width(), alloc.get_height() - (timebar_height * n_timebars)); } } @@ -211,6 +229,8 @@ MidiCueEditor::reset_zoom (samplecnt_t spp) if (view) { view->set_samples_per_pixel (spp); } + + bbt_ruler->set_range (0, current_page_samples()); } samplecnt_t @@ -268,7 +288,7 @@ MidiCueEditor::set_region (std::shared_ptr t, std::shared_ptr return; } - view = new MidiCueView (t, *hv_scroll_group, *this, *bg, 0xff0000ff); + view = new MidiCueView (t, *data_group, *this, *bg, 0xff0000ff); view->set_region (r); bg->set_view (view); @@ -460,3 +480,349 @@ MidiCueEditor::region_selection() RegionSelection rs; return rs; } + +static void +edit_last_mark_label (std::vector& marks, const std::string& newlabel) +{ + ArdourCanvas::Ruler::Mark copy = marks.back(); + copy.label = newlabel; + marks.pop_back (); + marks.push_back (copy); +} + +void +MidiCueEditor::metric_get_bbt (std::vector& marks, samplepos_t leftmost, samplepos_t rightmost, gint /*maxchars*/) +{ + if (_session == 0) { + return; + } + + std::shared_ptr tmap; + + if (view) { + /* XXX MAKE MAP FROM REGION->SOURCE */ + tmap.reset (new Temporal::TempoMap (Temporal::Tempo (120, 4), Temporal::Meter (4, 4))); + } else { + tmap.reset (new Temporal::TempoMap (Temporal::Tempo (120, 4), Temporal::Meter (4, 4))); + } + + EditingContext::TempoMapScope tms (*this, tmap); + Temporal::TempoMapPoints::const_iterator i; + + char buf[64]; + Temporal::BBT_Time next_beat; + double bbt_position_of_helper; + bool helper_active = false; + ArdourCanvas::Ruler::Mark mark; + const samplecnt_t sr (_session->sample_rate()); + + Temporal::TempoMapPoints grid; + grid.reserve (4096); + + + /* prevent negative values of leftmost from creeping into tempomap + */ + + const Beats left = tmap->quarters_at_sample (leftmost).round_down_to_beat(); + const Beats lower_beat = (left < Beats() ? Beats() : left); + + using std::max; + + switch (bbt_ruler_scale) { + + case bbt_show_quarters: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 1); + break; + case bbt_show_eighths: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 2); + break; + case bbt_show_sixteenths: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 4); + break; + case bbt_show_thirtyseconds: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 8); + break; + case bbt_show_sixtyfourths: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 16); + break; + case bbt_show_onetwentyeighths: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 0, 32); + break; + + case bbt_show_1: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 1); + break; + + case bbt_show_4: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 4); + break; + + case bbt_show_16: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 16); + break; + + case bbt_show_64: + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 64); + break; + + default: + /* bbt_show_many */ + tmap->get_grid (grid, max (tmap->superclock_at (lower_beat), (superclock_t) 0), samples_to_superclock (rightmost, sr), 128); + break; + } + +#if 0 // DEBUG GRID + for (auto const& g : grid) { + std::cout << "Grid " << g.time() << " Beats: " << g.beats() << " BBT: " << g.bbt() << " sample: " << g.sample(_session->nominal_sample_rate ()) << "\n"; + } +#endif + + if (distance (grid.begin(), grid.end()) == 0) { + return; + } + + /* we can accent certain lines depending on the user's Grid choice */ + /* for example, even in a 4/4 meter we can draw a grid with triplet-feel */ + /* and in this case you will want the accents on '3s' not '2s' */ + uint32_t bbt_divisor = 2; + + using namespace Editing; + + switch (_grid_type) { + case GridTypeBeatDiv3: + bbt_divisor = 3; + break; + case GridTypeBeatDiv5: + bbt_divisor = 5; + break; + case GridTypeBeatDiv6: + bbt_divisor = 3; + break; + case GridTypeBeatDiv7: + bbt_divisor = 7; + break; + case GridTypeBeatDiv10: + bbt_divisor = 5; + break; + case GridTypeBeatDiv12: + bbt_divisor = 3; + break; + case GridTypeBeatDiv14: + bbt_divisor = 7; + break; + case GridTypeBeatDiv16: + break; + case GridTypeBeatDiv20: + bbt_divisor = 5; + break; + case GridTypeBeatDiv24: + bbt_divisor = 6; + break; + case GridTypeBeatDiv28: + bbt_divisor = 7; + break; + case GridTypeBeatDiv32: + break; + default: + bbt_divisor = 2; + break; + } + + uint32_t bbt_beat_subdivision = 1; + switch (bbt_ruler_scale) { + case bbt_show_quarters: + bbt_beat_subdivision = 1; + break; + case bbt_show_eighths: + bbt_beat_subdivision = 1; + break; + case bbt_show_sixteenths: + bbt_beat_subdivision = 2; + break; + case bbt_show_thirtyseconds: + bbt_beat_subdivision = 4; + break; + case bbt_show_sixtyfourths: + bbt_beat_subdivision = 8; + break; + case bbt_show_onetwentyeighths: + bbt_beat_subdivision = 16; + break; + default: + bbt_beat_subdivision = 1; + break; + } + + bbt_beat_subdivision *= bbt_divisor; + + switch (bbt_ruler_scale) { + + case bbt_show_many: + snprintf (buf, sizeof(buf), "cannot handle %" PRIu32 " bars", bbt_bars); + mark.style = ArdourCanvas::Ruler::Mark::Major; + mark.label = buf; + mark.position = leftmost; + marks.push_back (mark); + break; + + case bbt_show_64: + for (i = grid.begin(); i != grid.end(); i++) { + BBT_Time bbt ((*i).bbt()); + if (bbt.is_bar()) { + if (bbt.bars % 64 == 1) { + if (bbt.bars % 256 == 1) { + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars); + mark.style = ArdourCanvas::Ruler::Mark::Major; + } else { + buf[0] = '\0'; + if (bbt.bars % 256 == 129) { + mark.style = ArdourCanvas::Ruler::Mark::Minor; + } else { + mark.style = ArdourCanvas::Ruler::Mark::Micro; + } + } + mark.label = buf; + mark.position = (*i).sample (sr); + marks.push_back (mark); + } + } + } + break; + + case bbt_show_16: + for (i = grid.begin(); i != grid.end(); i++) { + BBT_Time bbt ((*i).bbt()); + if (bbt.is_bar()) { + if (bbt.bars % 16 == 1) { + if (bbt.bars % 64 == 1) { + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars); + mark.style = ArdourCanvas::Ruler::Mark::Major; + } else { + buf[0] = '\0'; + if (bbt.bars % 64 == 33) { + mark.style = ArdourCanvas::Ruler::Mark::Minor; + } else { + mark.style = ArdourCanvas::Ruler::Mark::Micro; + } + } + mark.label = buf; + mark.position = (*i).sample(sr); + marks.push_back (mark); + } + } + } + break; + + case bbt_show_4: + for (i = grid.begin(); i != grid.end(); ++i) { + BBT_Time bbt ((*i).bbt()); + if (bbt.is_bar()) { + if (bbt.bars % 4 == 1) { + if (bbt.bars % 16 == 1) { + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars); + mark.style = ArdourCanvas::Ruler::Mark::Major; + } else { + buf[0] = '\0'; + mark.style = ArdourCanvas::Ruler::Mark::Minor; + } + mark.label = buf; + mark.position = (*i).sample (sr); + marks.push_back (mark); + } + } + } + break; + + case bbt_show_1: + for (i = grid.begin(); i != grid.end(); ++i) { + BBT_Time bbt ((*i).bbt()); + if (bbt.is_bar()) { + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars); + mark.style = ArdourCanvas::Ruler::Mark::Major; + mark.label = buf; + mark.position = (*i).sample (sr); + marks.push_back (mark); + } + } + break; + + case bbt_show_quarters: + + mark.label = ""; + mark.position = leftmost; + mark.style = ArdourCanvas::Ruler::Mark::Micro; + marks.push_back (mark); + + for (i = grid.begin(); i != grid.end(); ++i) { + + BBT_Time bbt ((*i).bbt()); + + if ((*i).sample (sr) < leftmost && (bbt_bar_helper_on)) { + snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats); + edit_last_mark_label (marks, buf); + } else { + + if (bbt.is_bar()) { + mark.style = ArdourCanvas::Ruler::Mark::Major; + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars); + } else if ((bbt.beats % 2) == 1) { + mark.style = ArdourCanvas::Ruler::Mark::Minor; + buf[0] = '\0'; + } else { + mark.style = ArdourCanvas::Ruler::Mark::Micro; + buf[0] = '\0'; + } + mark.label = buf; + mark.position = (*i).sample (sr); + marks.push_back (mark); + } + } + break; + + case bbt_show_eighths: + case bbt_show_sixteenths: + case bbt_show_thirtyseconds: + case bbt_show_sixtyfourths: + case bbt_show_onetwentyeighths: + + bbt_position_of_helper = leftmost + (3 * get_current_zoom ()); + + mark.label = ""; + mark.position = leftmost; + mark.style = ArdourCanvas::Ruler::Mark::Micro; + marks.push_back (mark); + + for (i = grid.begin(); i != grid.end(); ++i) { + + BBT_Time bbt ((*i).bbt()); + + if ((*i).sample (sr) < leftmost && (bbt_bar_helper_on)) { + snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats); + edit_last_mark_label (marks, buf); + helper_active = true; + } else { + + if (bbt.is_bar()) { + mark.style = ArdourCanvas::Ruler::Mark::Major; + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.bars); + } else if (bbt.ticks == 0) { + mark.style = ArdourCanvas::Ruler::Mark::Minor; + snprintf (buf, sizeof(buf), "%" PRIu32, bbt.beats); + } else { + mark.style = ArdourCanvas::Ruler::Mark::Micro; + buf[0] = '\0'; + } + + if (((*i).sample(sr) < bbt_position_of_helper) && helper_active) { + buf[0] = '\0'; + } + mark.label = buf; + mark.position = (*i).sample (sr); + marks.push_back (mark); + } + } + + break; + } +} + diff --git a/gtk2_ardour/midi_cue_editor.h b/gtk2_ardour/midi_cue_editor.h index 2b1e45d78e..3360aacbdd 100644 --- a/gtk2_ardour/midi_cue_editor.h +++ b/gtk2_ardour/midi_cue_editor.h @@ -21,6 +21,8 @@ #include +#include "canvas/ruler.h" + #include "cue_editor.h" namespace Gtk { @@ -102,8 +104,6 @@ class MidiCueEditor : public CueEditor ArdourCanvas::GtkCanvasViewport* _canvas_viewport; ArdourCanvas::GtkCanvas* _canvas; - ArdourCanvas::Container* tempo_group; - /* The group containing all other groups that are scrolled vertically and horizontally. */ @@ -117,11 +117,11 @@ class MidiCueEditor : public CueEditor */ ArdourCanvas::ScrollGroup* cursor_scroll_group; - /* The group containing all trackviews. */ - ArdourCanvas::Container* no_scroll_group; - ArdourCanvas::Container* global_rect_group; + ArdourCanvas::Container* no_scroll_group; + ArdourCanvas::Container* data_group; ArdourCanvas::Container* time_line_group; + ArdourCanvas::Ruler* bbt_ruler; ArdourCanvas::Rectangle* transport_loop_range_rect; @@ -136,6 +136,25 @@ class MidiCueEditor : public CueEditor RegionSelection region_selection(); bool canvas_enter_leave (GdkEventCrossing* ev); + + void metric_get_bbt (std::vector&, samplepos_t, samplepos_t, gint); + + class BBTMetric : public ArdourCanvas::Ruler::Metric + { + public: + BBTMetric (MidiCueEditor& ec) : context (&ec) {} + + void get_marks (std::vector& marks, int64_t lower, int64_t upper, int maxchars) const { + context->metric_get_bbt (marks, lower, upper, maxchars); + } + + private: + MidiCueEditor* context; + }; + + BBTMetric bbt_metric; + double timebar_height; + size_t n_timebars; };