From baa32aa5bd9928075c34a141c95003e1899a2438 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 17 Aug 2023 12:19:07 -0600 Subject: [PATCH] merged commit from alexander mitchell, implementing a new piano roll header --- gtk2_ardour/midi_scroomer.cc | 49 +- gtk2_ardour/midi_time_axis.cc | 92 +++- gtk2_ardour/midi_time_axis.h | 5 + gtk2_ardour/piano_roll_header.cc | 806 ++++++++++++++++--------------- gtk2_ardour/piano_roll_header.h | 36 +- 5 files changed, 547 insertions(+), 441 deletions(-) diff --git a/gtk2_ardour/midi_scroomer.cc b/gtk2_ardour/midi_scroomer.cc index 8f88470da7..4f1b793d69 100644 --- a/gtk2_ardour/midi_scroomer.cc +++ b/gtk2_ardour/midi_scroomer.cc @@ -68,50 +68,24 @@ MidiScroomer::on_expose_event(GdkEventExpose* ev) Component comp = (Component) i; set_comp_rect(comp_rect, comp); - if (gdk_rectangle_intersect(&comp_rect, &ev->area, &clip_rect)) { + //if (gdk_rectangle_intersect(&comp_rect, &ev->area, &clip_rect)) { + + get_colors(colors, comp); + cc->set_source_rgb (1.f,0.f,0.f);//(colors[3], colors[4], colors[5]); cc->rectangle(clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); - cc->set_source_rgb (colors[3], colors[4], colors[5]); - cc->fill_preserve(); - cc->clip(); + //cc->fill_preserve(); + //cc->clip(); + cc->fill (); + + //cc->set_source_rgb(colors[0], colors[1], colors[2]); + //cc->set_line_width(note_height); - cc->set_source_rgb(colors[0], colors[1], colors[2]); - cc->set_line_width(note_height); lnote = 127 - (int) floor((double) (clip_rect.y + clip_rect.height) * y2note) - 1; hnote = 127 - (int) floor((double) clip_rect.y * y2note) + 1; - for (int note = lnote; note < hnote + 1; ++note) { - double y = height - note * note2y; - bool draw = false; - - switch (note % 12) { - case 1: - case 6: - y -= black_shift; - draw = true; - break; - case 3: - case 10: - y += black_shift; - draw = true; - break; - case 8: - draw = true; - break; - default: - break; - } - - if(draw) { - cc->set_line_width(1.4 * note2y); - cc->move_to(0, y); - cc->line_to(note_width, y); - cc->stroke(); - } - } - if (i == Handle1 || i == Handle2) { cc->rectangle(comp_rect.x + 0.5f, comp_rect.y + 0.5f, comp_rect.width - 1.0f, comp_rect.height - 1.0f); cc->set_line_width(1.0f); @@ -120,7 +94,8 @@ MidiScroomer::on_expose_event(GdkEventExpose* ev) } cc->reset_clip(); - } + //} + } return true; diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index c00648ae58..1385186dbd 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -140,6 +140,7 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess, ArdourCanva { _midnam_model_selector.disable_scrolling(); _midnam_custom_device_mode_selector.disable_scrolling(); + _midnam_channel_selector.disable_scrolling(); } void @@ -156,10 +157,10 @@ MidiTimeAxisView::set_route (std::shared_ptr rt) if (is_track ()) { _piano_roll_header = new PianoRollHeader(*midi_view()); - _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment); - _range_scroomer->DoubleClicked.connect ( - sigc::bind (sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range), - MidiStreamView::ContentsRange, false)); + //_range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment); + //_piano_roll_header->DoubleClicked.connect ( + // sigc::bind (sigc::mem_fun(*this, &MidiTimeAxisView::set_note_range), + // MidiStreamView::ContentsRange, false)); } /* This next call will result in our height being set up, so it must come after @@ -231,7 +232,7 @@ MidiTimeAxisView::set_route (std::shared_ptr rt) VBox* v = manage (new VBox); HBox* h = manage (new HBox); h->pack_end (*_piano_roll_header); - h->pack_end (*_range_scroomer); + //h->pack_end (*_range_scroomer); v->pack_start (*separator, false, false); v->pack_start (*h, true, true); v->show (); @@ -261,9 +262,11 @@ MidiTimeAxisView::set_route (std::shared_ptr rt) ArdourWidgets::set_tooltip (_midnam_model_selector, _("External MIDI Device")); ArdourWidgets::set_tooltip (_midnam_custom_device_mode_selector, _("External Device Mode")); + ArdourWidgets::set_tooltip (_midnam_channel_selector, _("MIDNAM Channel Display")); _midi_controls_box.pack_start (_midnam_model_selector, false, false, 2); _midi_controls_box.pack_start (_midnam_custom_device_mode_selector, false, false, 2); + _midi_controls_box.pack_start (_midnam_channel_selector, false, false, 2); _midi_controls_box.set_homogeneous(false); _midi_controls_box.set_border_width (2); @@ -308,6 +311,29 @@ MidiTimeAxisView::set_route (std::shared_ptr rt) create_automation_child (parameter, string_to (visible)); } } + //Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(_("Plugin Provided"), + // sigc::bind(sigc::mem_fun(*this, &MidiTimeAxisView::model_changed), + // model_name)); + + for (int i = 1; i < 17; i++){ + //std::string text = "Channel " + std::to_string(i+1); + _midnam_channel_selector.append_text_item(std::to_string(i)); + } + _midnam_channel_selector.StateChanged.connect (sigc::mem_fun (*this, &MidiTimeAxisView::_midnam_channel_changed)); + if (gui_property (X_("midnam-channel")).empty()) { + set_gui_property (X_("midnam-channel"), "1"); + _midnam_channel_selector.set_active("1"); + } else { + _midnam_channel_selector.set_active(gui_property (X_("midnam-channel"))); + } +} + +void +MidiTimeAxisView::_midnam_channel_changed () +{ + set_gui_property (X_("midnam-channel"), _midnam_channel_selector.get_text()); + //std::cout << "midnam_changed(): " << _midnam_channel_selector.get_text() << std::endl; + _piano_roll_header->queue_draw(); } void @@ -344,6 +370,7 @@ MidiTimeAxisView::processors_changed (RouteProcessorChange c) { RouteTimeAxisView::processors_changed (c); maybe_trigger_model_change (); + update_patch_selector (); } void @@ -449,6 +476,56 @@ MidiTimeAxisView::setup_midnam_patches () } else { model_changed (model); } + + _piano_roll_header->queue_draw(); +} + +void +MidiTimeAxisView::update_patch_selector () +{ + typedef MIDI::Name::MidiPatchManager PatchManager; + PatchManager& patch_manager = PatchManager::instance(); + + if (_route) { + std::shared_ptr pi = std::dynamic_pointer_cast (_route->the_instrument ()); + if (pi && pi->plugin ()->has_midnam ()) { + std::string model_name = pi->plugin ()->midnam_model (); + if (gui_property (X_("midnam-model-name")) != model_name) { + /* ensure that "Plugin Provided" is prefixed at the top of the list */ + if (_midnam_model_selector.items().empty () || _midnam_model_selector.items().begin()->get_label() != _("Plugin Provided")) { + setup_midnam_patches (); + } + model_changed (model_name); + } + } + } + + if (patch_manager.all_models().empty()) { + _midnam_model_selector.hide (); + _midnam_custom_device_mode_selector.hide (); + _midnam_channel_selector.hide (); + } else { + _midnam_model_selector.show (); + _midnam_channel_selector.show (); + if (_midnam_custom_device_mode_selector.items().size() > 1) { + _midnam_custom_device_mode_selector.show (); + } + } + _piano_roll_header->queue_draw(); + + /* call _midnam_model_selector.set_text () + * and show/hide _midnam_custom_device_mode_selector + */ + std::string model = gui_property (X_("midnam-model-name")); + if (model.empty() && _route->instrument_info().have_custom_plugin_info ()) { + /* use plugin's MIDNAM */ + model_changed (""); + } else if (model.empty() || ! MIDI::Name::MidiPatchManager::instance ().master_device_by_model (model)) { + /* invalid model, switch to use default */ + model_changed (""); + } else { + model_changed (model); + } } void @@ -540,6 +617,7 @@ MidiTimeAxisView::model_changed (const std::string& m) if (patch_change_dialog ()) { patch_change_dialog ()->refresh (); } + _piano_roll_header->queue_draw(); } void @@ -555,6 +633,8 @@ MidiTimeAxisView::custom_device_mode_changed(const std::string& mode) } /* inform the backend, route owned instrument info */ _route->instrument_info().set_external_instrument (model, mode); + + _piano_roll_header->queue_draw(); } MidiStreamView* @@ -1805,7 +1885,7 @@ MidiTimeAxisView::note_range_changed () void MidiTimeAxisView::contents_height_changed () { - _range_scroomer->queue_resize (); + //_range_scroomer->queue_resize (); } bool diff --git a/gtk2_ardour/midi_time_axis.h b/gtk2_ardour/midi_time_axis.h index 8c86c4a81d..1d52c7f239 100644 --- a/gtk2_ardour/midi_time_axis.h +++ b/gtk2_ardour/midi_time_axis.h @@ -120,6 +120,8 @@ protected: void processors_changed (ARDOUR::RouteProcessorChange); private: + void _midnam_channel_changed(); + sigc::signal _midi_patch_settings_changed; void setup_midnam_patches (); @@ -164,6 +166,7 @@ private: MidiChannelSelectorWindow* _channel_selector; ArdourWidgets::ArdourDropdown _midnam_model_selector; ArdourWidgets::ArdourDropdown _midnam_custom_device_mode_selector; + ArdourWidgets::ArdourDropdown _midnam_channel_selector; Gtk::CheckMenuItem* _step_edit_item; Gtk::Menu* default_channel_menu; @@ -204,6 +207,8 @@ private: std::shared_ptr velocity_track; Gtk::CheckMenuItem* velocity_menu_item; void create_velocity_automation_child (Evoral::Parameter const &, bool show); + + void update_patch_selector (); }; #endif /* __ardour_midi_time_axis_h__ */ diff --git a/gtk2_ardour/piano_roll_header.cc b/gtk2_ardour/piano_roll_header.cc index 7e2e660436..3f94f05b92 100644 --- a/gtk2_ardour/piano_roll_header.cc +++ b/gtk2_ardour/piano_roll_header.cc @@ -31,24 +31,25 @@ #include "piano_roll_header.h" #include "public_editor.h" #include "ui_config.h" +#include "midi++/midnam_patch.h" + +#include "pbd/i18n.h" using namespace std; using namespace Gtkmm2ext; -PianoRollHeader::Color PianoRollHeader::white = PianoRollHeader::Color (0.77f, 0.78f, 0.76f); -PianoRollHeader::Color PianoRollHeader::white_highlight = PianoRollHeader::Color (1.00f, 0.40f, 0.40f); -PianoRollHeader::Color PianoRollHeader::white_shade_light = PianoRollHeader::Color (0.95f, 0.95f, 0.95f); -PianoRollHeader::Color PianoRollHeader::white_shade_dark = PianoRollHeader::Color (0.56f, 0.56f, 0.56f); +PianoRollHeader::Color PianoRollHeader::white = PianoRollHeader::Color(0.77f, 0.78f, 0.76f); +PianoRollHeader::Color PianoRollHeader::white_highlight = PianoRollHeader::Color(1.00f, 0.40f, 0.40f); -PianoRollHeader::Color PianoRollHeader::black = PianoRollHeader::Color (0.24f, 0.24f, 0.24f); -PianoRollHeader::Color PianoRollHeader::black_highlight = PianoRollHeader::Color (0.60f, 0.10f, 0.10f); -PianoRollHeader::Color PianoRollHeader::black_shade_light = PianoRollHeader::Color (0.46f, 0.46f, 0.46f); -PianoRollHeader::Color PianoRollHeader::black_shade_dark = PianoRollHeader::Color (0.1f, 0.1f, 0.1f); +PianoRollHeader::Color PianoRollHeader::black = PianoRollHeader::Color(0.14f, 0.14f, 0.14f); +PianoRollHeader::Color PianoRollHeader::black_highlight = PianoRollHeader::Color(0.60f, 0.10f, 0.10f); -PianoRollHeader::Color::Color () - : r (1.0f) - , g (1.0f) - , b (1.0f) +PianoRollHeader::Color PianoRollHeader::gray = PianoRollHeader::Color(0.50f, 0.50f, 0.50f); + +PianoRollHeader::Color::Color() + : r(1.0f) + , g(1.0f) + , b(1.0f) { } @@ -67,12 +68,38 @@ PianoRollHeader::Color::set (const PianoRollHeader::Color& c) b = c.b; } -PianoRollHeader::PianoRollHeader (MidiStreamView& v) - : _view (v) +PianoRollHeader::PianoRollHeader(MidiStreamView& v) + : _adj(v.note_range_adjustment) + , _view(v) + , _font_descript ("Sans Bold") + , _font_descript_big_c ("Sans") + , _font_descript_midnam ("Sans") , _highlighted_note (NO_MIDI_NOTE) , _clicked_note (NO_MIDI_NOTE) , _dragging (false) + , _scroomer_size (60.f) + , _scroomer_drag (false) + , _old_y (0.0) + , _fract (0.0) + , _scroomer_state (NONE) + , _scroomer_button_state (NONE) + , _saved_top_val (0.0) + , _saved_bottom_val (127.0) + , _mini_map_display (false) { + _layout = Pango::Layout::create (get_pango_context()); + _big_c_layout = Pango::Layout::create (get_pango_context()); + _font_descript_big_c.set_absolute_size (10.0 * Pango::SCALE); + _big_c_layout->set_font_description(_font_descript_big_c); + _midnam_layout = Pango::Layout::create (get_pango_context()); + + _adj.set_lower(0); + _adj.set_upper(127); + + /* set minimum view range to one octave */ + //set_min_page_size(12); + + //_adj = v->note_range_adjustment; add_events (Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK | @@ -98,370 +125,375 @@ create_path (Cairo::RefPtr cr, double x[], double y[], int start } inline void -render_rect (Cairo::RefPtr cr, int /*note*/, double x[], double y[], - PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) +render_rect(Cairo::RefPtr cr, int note, double x[], double y[], + PianoRollHeader::Color& bg) { - cr->set_source_rgb (bg.r, bg.g, bg.b); - create_path (cr, x, y, 0, 4); - cr->fill (); - - cr->set_source_rgb (tl_shadow.r, tl_shadow.g, tl_shadow.b); - create_path (cr, x, y, 0, 2); - cr->stroke (); - - cr->set_source_rgb (br_shadow.r, br_shadow.g, br_shadow.b); - create_path (cr, x, y, 2, 4); - cr->stroke (); -} - -inline void -render_cf (Cairo::RefPtr cr, int /*note*/, double x[], double y[], - PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) -{ - cr->set_source_rgb (bg.r, bg.g, bg.b); - create_path (cr, x, y, 0, 6); - cr->fill (); - - cr->set_source_rgb (tl_shadow.r, tl_shadow.g, tl_shadow.b); - create_path (cr, x, y, 0, 4); - cr->stroke (); - - cr->set_source_rgb (br_shadow.r, br_shadow.g, br_shadow.b); - create_path (cr, x, y, 4, 6); - cr->stroke (); -} - -inline void -render_eb (Cairo::RefPtr cr, int /*note*/, double x[], double y[], - PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) -{ - cr->set_source_rgb (bg.r, bg.g, bg.b); - create_path (cr, x, y, 0, 6); - cr->fill (); - - cr->set_source_rgb (tl_shadow.r, tl_shadow.g, tl_shadow.b); - create_path (cr, x, y, 0, 2); - cr->stroke (); - create_path (cr, x, y, 4, 5); - cr->stroke (); - - cr->set_source_rgb (br_shadow.r, br_shadow.g, br_shadow.b); - create_path (cr, x, y, 2, 4); - cr->stroke (); - create_path (cr, x, y, 5, 6); - cr->stroke (); -} - -inline void -render_dga (Cairo::RefPtr cr, int /*note*/, double x[], double y[], - PianoRollHeader::Color& bg, PianoRollHeader::Color& tl_shadow, PianoRollHeader::Color& br_shadow) -{ - cr->set_source_rgb (bg.r, bg.g, bg.b); - create_path (cr, x, y, 0, 8); - cr->fill (); - - cr->set_source_rgb (tl_shadow.r, tl_shadow.g, tl_shadow.b); - create_path (cr, x, y, 0, 4); - cr->stroke (); - create_path (cr, x, y, 6, 7); - cr->stroke (); - - cr->set_source_rgb (br_shadow.r, br_shadow.g, br_shadow.b); - create_path (cr, x, y, 4, 6); - cr->stroke (); - create_path (cr, x, y, 7, 8); - cr->stroke (); + cr->set_source_rgb(bg.r, bg.g, bg.b); + create_path(cr, x, y, 0, 4); + cr->fill(); } void -PianoRollHeader::get_path (PianoRollHeader::ItemType note_type, int note, double x[], double y[]) +PianoRollHeader::render_scroomer(Cairo::RefPtr cr) { - double y_pos = floor (_view.note_to_y (note)) + 1.5f; + double scroomer_top = max(1.0, (1.0 - ((_adj.get_value()+_adj.get_page_size()) / 127.0)) * get_height () ); + double scroomer_bottom = (1.0 - (_adj.get_value () / 127.0)) * get_height (); + double scroomer_width = _scroomer_size; + + cr->set_source_rgba(white.r, white.g, white.b, 0.15); + cr->move_to(1.f, scroomer_top); + cr->line_to(scroomer_width - 1.f, scroomer_top); + cr->line_to(scroomer_width - 1.f, scroomer_bottom); + cr->line_to(1.f, scroomer_bottom); + cr->line_to(1.f, scroomer_top); + cr->fill(); +} + +bool +PianoRollHeader::on_scroll_event (GdkEventScroll* ev) +{ + int note_range = _adj.get_page_size (); + int note_lower = _adj.get_value (); + + if(ev->state == GDK_SHIFT_MASK){ + switch (ev->direction) { + case GDK_SCROLL_UP: //ZOOM IN + _view.apply_note_range (min(note_lower + 1, 127), max(note_lower + note_range - 1,0), true); + break; + case GDK_SCROLL_DOWN: //ZOOM OUT + _view.apply_note_range (max(note_lower - 1,0), min(note_lower + note_range + 1, 127), true); + break; + default: + return false; + } + }else{ + switch (ev->direction) { + case GDK_SCROLL_UP: + _adj.set_value (min (note_lower + 1, 127 - note_range)); + break; + case GDK_SCROLL_DOWN: + _adj.set_value (note_lower - 1.0); + break; + default: + return false; + } + } + std::cout << "hilight_note: " << std::to_string(_highlighted_note) << " Hov_Note: " << std::to_string(_view.y_to_note(ev->y)) << " Val: " << _adj.get_value() << " upper: " << _adj.get_upper() << " lower: " << _adj.get_lower() << " page_size: "<< _adj.get_page_size () << std::endl; + _adj.value_changed (); + queue_draw (); + return true; +} + + +void +PianoRollHeader::get_path (int note, double x[], double y[]) +{ + double scroomer_size = _scroomer_size; + double y_pos = floor(_view.note_to_y(note)); double note_height; - double other_y1 = floor (_view.note_to_y (note + 1)) + floor (_note_height / 2.0f) + 2.5f; - double other_y2 = floor (_view.note_to_y (note - 1)) + floor (_note_height / 2.0f) + 1.0f; - double width = get_width (); + _raw_note_height = floor(_view.note_to_y(note - 1)) - y_pos; + double width = get_width() - 1.0f; if (note == 0) { - note_height = floor (_view.contents_height ()) - y_pos + 2.; + note_height = floor(_view.contents_height()) - y_pos; } else { - note_height = floor (_view.note_to_y (note - 1)) - y_pos + 2.; + note_height = _raw_note_height <= 3 ? _raw_note_height : _raw_note_height - 1.f; } - switch (note_type) { - case BLACK_SEPARATOR: - x[0] = 1.5f; - y[0] = y_pos; - x[1] = _black_note_width; - y[1] = y_pos; - break; - case BLACK_MIDDLE_SEPARATOR: - x[0] = _black_note_width; - y[0] = y_pos + floor (_note_height / 2.0f); - x[1] = width - 1.0f; - y[1] = y[0]; - break; - case BLACK: - x[0] = 1.5f; - y[0] = y_pos + note_height - 0.5f; - x[1] = 1.5f; - y[1] = y_pos + 1.0f; - x[2] = _black_note_width; - y[2] = y_pos + 1.0f; - x[3] = _black_note_width; - y[3] = y_pos + note_height - 0.5f; - x[4] = 1.5f; - y[4] = y_pos + note_height - 0.5f; - return; - case WHITE_SEPARATOR: - x[0] = 1.5f; - y[0] = y_pos; - x[1] = width - 1.5f; - y[1] = y_pos; - break; - case WHITE_RECT: - x[0] = 1.5f; - y[0] = y_pos + note_height - 0.5f; - x[1] = 1.5f; - y[1] = y_pos + 1.0f; - x[2] = width - 1.5f; - y[2] = y_pos + 1.0f; - x[3] = width - 1.5f; - y[3] = y_pos + note_height - 0.5f; - x[4] = 1.5f; - y[4] = y_pos + note_height - 0.5f; - return; - case WHITE_CF: - x[0] = 1.5f; - y[0] = y_pos + note_height - 1.5f; - x[1] = 1.5f; - y[1] = y_pos + 1.0f; - x[2] = _black_note_width + 1.0f; - y[2] = y_pos + 1.0f; - x[3] = _black_note_width + 1.0f; - y[3] = other_y1; - x[4] = width - 1.5f; - y[4] = other_y1; - x[5] = width - 1.5f; - y[5] = y_pos + note_height - 1.5f; - x[6] = 1.5f; - y[6] = y_pos + note_height - 1.5f; - return; - case WHITE_EB: - x[0] = 1.5f; - y[0] = y_pos + note_height - 1.5f; - x[1] = 1.5f; - y[1] = y_pos + 1.0f; - x[2] = width - 1.5f; - y[2] = y_pos + 1.0f; - x[3] = width - 1.5f; - y[3] = other_y2; - x[4] = _black_note_width + 1.0f; - y[4] = other_y2; - x[5] = _black_note_width + 1.0f; - y[5] = y_pos + note_height - 1.5f; - x[6] = 1.5f; - y[6] = y_pos + note_height - 1.5f; - return; - case WHITE_DGA: - x[0] = 1.5f; - y[0] = y_pos + note_height - 1.5f; - x[1] = 1.5f; - y[1] = y_pos + 1.0f; - x[2] = _black_note_width + 1.0f; - y[2] = y_pos + 1.0f; - x[3] = _black_note_width + 1.0f; - y[3] = other_y1; - x[4] = width - 1.5f; - y[4] = other_y1; - x[5] = width - 1.5f; - y[5] = other_y2; - x[6] = _black_note_width + 1.0f; - y[6] = other_y2; - x[7] = _black_note_width + 1.0f; - y[7] = y_pos + note_height - 1.5f; - x[8] = 1.5f; - y[8] = y_pos + note_height - 1.5f; - return; - default: - return; - } + x[0] = scroomer_size; + y[0] = y_pos + note_height; + + x[1] = scroomer_size; + y[1] = y_pos; + + x[2] = width; + y[2] = y_pos; + + x[3] = width; + y[3] = y_pos + note_height; + + x[4] = scroomer_size; + y[4] = y_pos + note_height; + return; } bool PianoRollHeader::on_expose_event (GdkEventExpose* ev) { GdkRectangle& rect = ev->area; - double font_size; - int lowest, highest; - double x[9]; - double y[9]; - Color bg, tl_shadow, br_shadow; - int oct_rel; + int lowest, highest; + PianoRollHeader::Color bg; + Cairo::RefPtr cr = get_window()->create_cairo_context(); + double x[9]; + double y[9]; + int oct_rel; + int y1 = max(rect.y, 0); + int y2 = min(rect.y + rect.height, (int) floor(_view.contents_height())); + double av_note_height = get_height () / _adj.get_page_size (); + int bc_height, bc_width; - int y1 = max (rect.y, 0); - int y2 = min (rect.y + rect.height, (int)floor (_view.contents_height () - 1.0f)); + //Reduce the frequency of Pango layout resizing + //if (int(_old_av_note_height) != int(av_note_height)) { + //Set Pango layout keyboard c's size + _font_descript.set_absolute_size (av_note_height * 0.7 * Pango::SCALE); + _layout->set_font_description(_font_descript); - Cairo::RefPtr cr = get_window ()->create_cairo_context (); - Cairo::RefPtr pat = Cairo::LinearGradient::create (0, 0, _black_note_width, 0); + //change mode of midnam display + if (av_note_height >= 8.0) { + _mini_map_display = false; + } else { + _mini_map_display = true; + } - //Cairo::TextExtents te; - lowest = max (_view.lowest_note (), _view.y_to_note (y2)); - highest = min (_view.highest_note (), _view.y_to_note (y1)); + //Set Pango layout midnam size + _font_descript_midnam.set_absolute_size (max(8.0 * 0.7 * Pango::SCALE, (int)av_note_height * 0.7 * Pango::SCALE)); + + _midnam_layout->set_font_description(_font_descript_midnam); + + lowest = max(_view.lowest_note(), _view.y_to_note(y2)); + highest = min(_view.highest_note(), _view.y_to_note(y1)); if (lowest > 127) { lowest = 0; } - cr->select_font_face ("Georgia", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD); - font_size = min ((double)10.0f, _note_height - 4.0f); - cr->set_font_size (font_size); - /* fill the entire rect with the color for non-highlighted white notes. * then we won't have to draw the background for those notes, * and would only have to draw the background for the one highlighted white note*/ - //cr->rectangle(rect.x, rect.y, rect.width, rect.height); - //cr->set_source_rgb(white.r, white.g, white.b); + // cr->rectangle(rect.x, rect.y, rect.width, rect.height); + // cr->set_source_rgb(white.r, white.g, white.b); //cr->fill(); cr->set_line_width (1.0f); - /* draw vertical lines with shade at both ends of the widget */ - cr->set_source_rgb (0.0f, 0.0f, 0.0f); - cr->move_to (0.5f, rect.y); - cr->line_to (0.5f, rect.y + rect.height); - cr->stroke (); - cr->move_to (get_width () + 0.5f, rect.y); - cr->line_to (get_width () + 0.5f, rect.y + rect.height); - cr->stroke (); + /* draw vertical lines on both sides of the widget */ + cr->set_source_rgb(0.0f, 0.0f, 0.0f); + cr->move_to(0.f, rect.y); + cr->line_to(0.f, rect.y + rect.height); + cr->stroke(); + cr->move_to(get_width(),rect.y); + cr->line_to(get_width(), rect.y + get_height ()); + cr->stroke(); - //pat->add_color_stop_rgb(0.0, 0.33, 0.33, 0.33); - //pat->add_color_stop_rgb(0.2, 0.39, 0.39, 0.39); - //pat->add_color_stop_rgb(1.0, 0.22, 0.22, 0.22); - //cr->set_source(pat); + // Render the MIDNAM text or its equivalent. First, set up a clip + // region so that the text doesn't spill, regardless of its length. + + cr->save(); + + cr->rectangle (0,0,_scroomer_size, get_height () ); + cr->clip(); + + /* Now draw the actual text */ + for (int i = lowest; i <= highest; ++i) { + int size_x, size_y; + double y = floor(_view.note_to_y(i)) - 0.5f; + midnamName note = get_note_name (i); + + _midnam_layout->set_text (note.name); + + cr->set_source_rgb(white.r, white.g, white.b); + cr->move_to(2.f, y); + + if (!_mini_map_display) { + _midnam_layout->show_in_cairo_context (cr); + } else { + /* Too small for text, just show a thing rect where the + text would have been. + */ + if (!note.from_midnam) { + cr->set_source_rgb(gray.r, gray.g, gray.b); + } + pango_layout_get_pixel_size (_midnam_layout->gobj (), &size_x, &size_y); + cr->rectangle (2.f, y + (av_note_height * 0.5), size_x, av_note_height * 0.2); + cr->fill (); + } + } + + /* Add a gradient over the text, to act as a sort of "visual + elision". This avoids using text elision with "..." which takes up too + much space. + */ + auto gradient_ptr = Cairo::LinearGradient::create (_scroomer_size - 20., 0, _scroomer_size, 0); + gradient_ptr->add_color_stop_rgba (0,.23,.23,.23,0); + gradient_ptr->add_color_stop_rgba (1,.23,.23,.23,1); + cr->set_source (gradient_ptr); + cr->rectangle (_scroomer_size - 20., 0, _scroomer_size, get_height () ); + cr->fill(); + + /* Now draw the semi-transparent scroomer over the top */ + + render_scroomer(cr); + + /* Done with clip region */ + + cr->restore(); + + /* Now draw black/white rects for each note, following standard piano + layout, but without a setback/offset for the black keys + */ for (int i = lowest; i <= highest; ++i) { oct_rel = i % 12; switch (oct_rel) { - case 1: - case 3: - case 6: - case 8: - case 10: - /* black note */ - if (i == _highlighted_note) { - bg.set (black_highlight); - } else { - bg.set (black); - } + case 1: + case 3: + case 6: + case 8: + case 10: + /* black note */ + if (i == _highlighted_note) { + bg.set (black_highlight); + } else { + bg.set (black); + } - if (_active_notes[i]) { - tl_shadow.set (black_shade_dark); - br_shadow.set (black_shade_light); - } else { - tl_shadow.set (black_shade_light); - br_shadow.set (black_shade_dark); - } + /* draw black separators */ + cr->set_source_rgb (0.0f, 0.0f, 0.0f); + get_path (i, x, y); + create_path (cr, x, y, 0, 1); + cr->stroke(); - /* draw black separators */ - cr->set_source_rgb (0.0f, 0.0f, 0.0f); - get_path (BLACK_SEPARATOR, i, x, y); - create_path (cr, x, y, 0, 1); - cr->stroke (); + get_path (i, x, y); + create_path (cr, x, y, 0, 1); + cr->stroke(); - get_path (BLACK_MIDDLE_SEPARATOR, i, x, y); - create_path (cr, x, y, 0, 1); - cr->stroke (); - - get_path (BLACK, i, x, y); - render_rect (cr, i, x, y, bg, tl_shadow, br_shadow); - break; - - default: - /* white note */ - if (i == _highlighted_note) { - bg.set (white_highlight); - } else { - bg.set (white); - } - - if (_active_notes[i]) { - tl_shadow.set (white_shade_dark); - br_shadow.set (white_shade_light); - } else { - tl_shadow.set (white_shade_light); - br_shadow.set (white_shade_dark); - } - - switch (oct_rel) { - case 0: - case 5: - if (i == _view.highest_note ()) { - get_path (WHITE_RECT, i, x, y); - render_rect (cr, i, x, y, bg, tl_shadow, br_shadow); - } else { - get_path (WHITE_CF, i, x, y); - render_cf (cr, i, x, y, bg, tl_shadow, br_shadow); - } - break; - - case 2: - case 7: - case 9: - if (i == _view.highest_note ()) { - get_path (WHITE_EB, i, x, y); - render_eb (cr, i, x, y, bg, tl_shadow, br_shadow); - } else if (i == _view.lowest_note ()) { - get_path (WHITE_CF, i, x, y); - render_cf (cr, i, x, y, bg, tl_shadow, br_shadow); - } else { - get_path (WHITE_DGA, i, x, y); - render_dga (cr, i, x, y, bg, tl_shadow, br_shadow); - } - break; - - case 4: - case 11: - cr->set_source_rgb (0.0f, 0.0f, 0.0f); - get_path (WHITE_SEPARATOR, i, x, y); - create_path (cr, x, y, 0, 1); - cr->stroke (); - - if (i == _view.lowest_note ()) { - get_path (WHITE_RECT, i, x, y); - render_rect (cr, i, x, y, bg, tl_shadow, br_shadow); - } else { - get_path (WHITE_EB, i, x, y); - render_eb (cr, i, x, y, bg, tl_shadow, br_shadow); - } - break; - - default: - break; - } - break; + get_path (i, x, y); + render_rect (cr, i, x, y, bg); + break; } - /* render the name of which C this is */ - if (oct_rel == 0) { + switch(oct_rel) { + case 0: + case 2: + case 4: + case 5: + case 7: + case 9: + case 11: + if (i == _highlighted_note) { + bg.set (white_highlight); + } else { + bg.set (white); + } + get_path (i, x, y); + render_rect (cr, i, x, y, bg); + break; + default: + break; + + } + } + + /* render the C of the key, when key is too small to contain text we + place the C on the midnam scroomer area. + + we render an additional 5 notes below the lowest note displayed + so that the top of the C is shown to maintain visual context + */ + for (int i = lowest - 5; i <= highest; ++i) { + double y = floor(_view.note_to_y(i)) - 0.5f; + double note_height = i == 0? av_note_height : floor(_view.note_to_y(i - 1)) - y; + oct_rel = i % 12; + + if (oct_rel == 0 || (oct_rel == 7 && _adj.get_page_size() <=10) ) { std::stringstream s; - double y = floor (_view.note_to_y (i)) + 0.5f; - double note_height = floor (_view.note_to_y (i - 1)) - y; int cn = i / 12 - 1; - s << "C" << cn; - //cr->get_text_extents(s.str(), te); - cr->set_source_rgb (0.30f, 0.30f, 0.30f); - cr->move_to (2.0f, y + note_height - 1.0f - (note_height - font_size) / 2.0f); - cr->show_text (s.str ()); + if (oct_rel == 0){ + s << "C" << cn; + }else{ + s << "G" << cn; + } + + if (av_note_height > 12.0){ + cr->set_source_rgb(0.30f, 0.30f, 0.30f); + _layout->set_text (s.str()); + cr->move_to(_scroomer_size, y); + _layout->show_in_cairo_context (cr); + }else{ + cr->set_source_rgb(white.r, white.g, white.b); + _big_c_layout->set_text (s.str()); + pango_layout_get_pixel_size (_big_c_layout->gobj(), &bc_width, &bc_height); + cr->move_to(_scroomer_size - 18, y - bc_height + av_note_height); + _big_c_layout->show_in_cairo_context (cr); + cr->move_to(_scroomer_size - 18, y + note_height); + cr->line_to(_scroomer_size, y + note_height); + cr->stroke(); + } } } return true; } +PianoRollHeader::midnamName +PianoRollHeader::get_note_name (int note) +{ + using namespace MIDI::Name; + std::string name; + std::string note_n; + midnamName rtn; + + MidiTimeAxisView* mtv = dynamic_cast(&_view.trackview()); + if (mtv) { + int midnam_channel = stoi(mtv->gui_property (X_("midnam-channel")))-1; + name = mtv->route()->instrument_info ().get_note_name ( + 0, //bank + 0, //program + midnam_channel, //channel + note); //note + } + + int oct_rel = note % 12; + switch(oct_rel) { + case 0: + note_n = "C"; + break; + case 1: + note_n = "C♯"; + break; + case 2: + note_n = "D"; + break; + case 3: + note_n = "D♯"; + break; + case 4: + note_n = "E"; + break; + case 5: + note_n = "F"; + break; + case 6: + note_n = "F♯"; + break; + case 7: + note_n = "G"; + break; + case 8: + note_n = "G♯"; + break; + case 9: + note_n = "A"; + break; + case 10: + note_n = "A♯"; + break; + case 11: + note_n = "B"; + break; + default: + break; + } + + std::string new_string = std::string(3 - std::to_string(note).length(), '0') + std::to_string(note); + rtn.name = name.empty()? new_string + " " + note_n : name; + rtn.from_midnam = !name.empty(); + return rtn; +} + bool PianoRollHeader::on_motion_notify_event (GdkEventMotion* ev) { @@ -502,37 +534,57 @@ PianoRollHeader::on_motion_notify_event (GdkEventMotion* ev) bool PianoRollHeader::on_button_press_event (GdkEventButton* ev) { - int note = _view.y_to_note (ev->y); - bool tertiary = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); - - if (ev->button == 2 && Keyboard::no_modifiers_active (ev->state)) { - SetNoteSelection (note); // EMIT SIGNAL + _scroomer_button_state = _scroomer_state; + if (ev->button == 1 && ev->x <= _scroomer_size){ + _scroomer_drag = true; + _old_y = ev->y; + _fract = _adj.get_value(); + _fract_top = _adj.get_value() + _adj.get_page_size(); return true; - } else if (tertiary && (ev->button == 1 || ev->button == 2)) { - ExtendNoteSelection (note); // EMIT SIGNAL - return true; - } else if (ev->button == 1 && note >= 0 && note < 128) { - add_modal_grab (); - _dragging = true; + }else { + int note = _view.y_to_note(ev->y); + bool tertiary = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); - if (!_active_notes[note]) { - _active_notes[note] = true; - _clicked_note = note; - send_note_on (note); + if (ev->state == GDK_CONTROL_MASK){ + if (ev->type == GDK_2BUTTON_PRESS) { + _adj.set_value (0.0); + _adj.set_page_size (127.0); + _adj.value_changed (); + queue_draw (); + return false; + } + return false; + } else if (ev->button == 2 && Keyboard::no_modifiers_active (ev->state)) { + SetNoteSelection (note); // EMIT SIGNAL + return true; + } else if (tertiary && (ev->button == 1 || ev->button == 2)) { + ExtendNoteSelection (note); // EMIT SIGNAL + return true; + } else if (ev->button == 1 && note >= 0 && note < 128) { + add_modal_grab(); + _dragging = true; - invalidate_note_range (note, note); - } else { - reset_clicked_note (note); + if (!_active_notes[note]) { + _active_notes[note] = true; + _clicked_note = note; + send_note_on(note); + + invalidate_note_range(note, note); + } else { + reset_clicked_note(note); + } } } - return true; } bool PianoRollHeader::on_button_release_event (GdkEventButton* ev) { - int note = _view.y_to_note (ev->y); + if (_scroomer_drag){ + _scroomer_drag = false; + } + int note = _view.y_to_note(ev->y); if (false /*editor().current_mouse_mode() == Editing::MouseRange*/) { //Todo: this mode is buggy, and of questionable utility anyway @@ -590,10 +642,10 @@ PianoRollHeader::on_enter_notify_event (GdkEventCrossing* ev) bool PianoRollHeader::on_leave_notify_event (GdkEventCrossing*) { - if (has_grab () && _dragging) { - return true; + if (!_scroomer_drag){ + get_window()->set_cursor(); } - invalidate_note_range (_highlighted_note, _highlighted_note); + invalidate_note_range(_highlighted_note, _highlighted_note); if (_clicked_note != NO_MIDI_NOTE) { reset_clicked_note (_clicked_note, _clicked_note != _highlighted_note); @@ -603,12 +655,6 @@ PianoRollHeader::on_leave_notify_event (GdkEventCrossing*) return true; } -bool -PianoRollHeader::on_scroll_event (GdkEventScroll*) -{ - return true; -} - void PianoRollHeader::note_range_changed () { @@ -619,38 +665,11 @@ PianoRollHeader::note_range_changed () void PianoRollHeader::invalidate_note_range (int lowest, int highest) { - Glib::RefPtr win = get_window (); - Gdk::Rectangle rect; + Glib::RefPtr win = get_window(); + Gdk::Rectangle rect; - /* the non-rectangular geometry of some of the notes requires more - * redraws than the notes that actually changed. - */ - switch (lowest % 12) { - case 0: - case 5: - lowest = max ((int)_view.lowest_note (), lowest); - break; - default: - lowest = max ((int)_view.lowest_note (), lowest - 1); - break; - } - - switch (highest % 12) { - case 4: - case 11: - highest = min ((int)_view.highest_note (), highest); - break; - case 1: - case 3: - case 6: - case 8: - case 10: - highest = min ((int)_view.highest_note (), highest + 1); - break; - default: - highest = min ((int)_view.highest_note (), highest + 2); - break; - } + lowest = max((int) _view.lowest_note(), lowest - 1); + highest = min((int) _view.highest_note(), highest + 2); double y = _view.note_to_y (highest); double height = _view.note_to_y (lowest - 1) - y; @@ -663,20 +682,13 @@ PianoRollHeader::invalidate_note_range (int lowest, int highest) if (win) { win->invalidate_rect (rect, false); } + queue_draw (); } void PianoRollHeader::on_size_request (Gtk::Requisition* r) { - r->width = std::max (20.f, rintf (20.f * UIConfiguration::instance ().get_ui_scale ())); -} - -void -PianoRollHeader::on_size_allocate (Gtk::Allocation& a) -{ - DrawingArea::on_size_allocate (a); - - _black_note_width = floor (0.7 * get_width ()) + 0.5f; + r->width = std::max (80.f, rintf (80.f * UIConfiguration::instance().get_ui_scale())); } void @@ -727,3 +739,9 @@ PianoRollHeader::editor () const { return _view.trackview ().editor (); } + +void +PianoRollHeader::set_min_page_size(double page_size) +{ + _min_page_size = page_size; +}; diff --git a/gtk2_ardour/piano_roll_header.h b/gtk2_ardour/piano_roll_header.h index 04217c560a..a9825eebd3 100644 --- a/gtk2_ardour/piano_roll_header.h +++ b/gtk2_ardour/piano_roll_header.h @@ -45,7 +45,6 @@ public: bool on_leave_notify_event (GdkEventCrossing*); void on_size_request(Gtk::Requisition*); - void on_size_allocate(Gtk::Allocation& a); void note_range_changed(); void set_note_highlight (uint8_t note); @@ -66,6 +65,15 @@ public: sigc::signal ExtendNoteSelection; private: + struct midnamName { + std::string name; + bool from_midnam; + }; + void set_min_page_size(double page_size); + void render_scroomer(Cairo::RefPtr); + midnamName get_note_name (int note); + + Gtk::Adjustment& _adj; static Color white; static Color white_highlight; static Color white_shade_light; @@ -74,6 +82,7 @@ private: static Color black_highlight; static Color black_shade_light; static Color black_shade_dark; + static Color gray; PianoRollHeader(const PianoRollHeader&); @@ -90,7 +99,7 @@ private: void invalidate_note_range(int lowest, int highest); - void get_path(ItemType, int note, double x[], double y[]); + void get_path(int note, double x[], double y[]); void send_note_on(uint8_t note); void send_note_off(uint8_t note); @@ -101,15 +110,34 @@ private: uint8_t _event[3]; - Cairo::RefPtr cc; + Glib::RefPtr _layout; + Glib::RefPtr _big_c_layout; + Glib::RefPtr _midnam_layout; + + Pango::FontDescription _font_descript; + Pango::FontDescription _font_descript_big_c; + Pango::FontDescription _font_descript_midnam; bool _active_notes[128]; uint8_t _highlighted_note; uint8_t _clicked_note; double _grab_y; bool _dragging; + double _scroomer_size; + bool _scroomer_drag; + double _old_y; + double _fract; + double _fract_top; + double _raw_note_height; + double _min_page_size; + enum scr_pos {TOP, BOTTOM, MOVE, NONE}; + scr_pos _scroomer_state; + scr_pos _scroomer_button_state; + double _saved_top_val; + double _saved_bottom_val; + bool _mini_map_display; double _note_height; - double _black_note_width; + double _old_av_note_height; PublicEditor& editor() const; };