diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index bf6dfa7a2c..681be7520e 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -90,6 +90,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) + , no_sound_notes (false) { _note_group->raise_to_top(); } @@ -112,6 +113,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) + , no_sound_notes (false) { _note_group->raise_to_top(); @@ -135,6 +137,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) + , no_sound_notes (false) { Gdk::Color c; int r,g,b,a; @@ -161,6 +164,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr note) { - if (!trackview.editor().sound_notes()) { + if (no_sound_notes || !trackview.editor().sound_notes()) { return; } @@ -1530,6 +1534,91 @@ MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) } } +void +MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend) +{ + uint8_t low_note = 127; + uint8_t high_note = 0; + MidiModel::Notes& notes (_model->notes()); + _optimization_iterator = _events.begin(); + + if (extend && _selection.empty()) { + extend = false; + } + + if (extend) { + + /* scan existing selection to get note range */ + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + if ((*i)->note()->note() < low_note) { + low_note = (*i)->note()->note(); + } + if ((*i)->note()->note() > high_note) { + high_note = (*i)->note()->note(); + } + } + + low_note = min (low_note, notenum); + high_note = max (high_note, notenum); + } + + no_sound_notes = true; + + for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { + + boost::shared_ptr note (*n); + CanvasNoteEvent* cne; + bool select = false; + + if (((0x0001 << note->channel()) & channel_mask) != 0) { + if (extend) { + if ((note->note() >= low_note && note->note() <= high_note)) { + select = true; + } + } else if (note->note() == notenum) { + select = true; + } + } + + if (select) { + if ((cne = find_canvas_note (note)) != 0) { + // extend is false because we've taken care of it, + // since it extends by time range, not pitch. + note_selected (cne, add, false); + } + } + + add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set") + + } + + no_sound_notes = false; +} + +void +MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask) +{ + MidiModel::Notes& notes (_model->notes()); + _optimization_iterator = _events.begin(); + + for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { + + boost::shared_ptr note (*n); + CanvasNoteEvent* cne; + + if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) { + if ((cne = find_canvas_note (note)) != 0) { + if (cne->selected()) { + note_deselected (cne); + } else { + note_selected (cne, true, false); + } + } + } + } +} + void MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend) { diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index 9a9ea3c265..77e046d9cd 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -198,6 +198,9 @@ class MidiRegionView : public RegionView void move_selection(double dx, double dy); void note_dropped(ArdourCanvas::CanvasNoteEvent* ev, double d_pixels, int8_t d_note); + void select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend); + void toggle_matching_notes (uint8_t notenum, uint16_t channel_mask); + /** Return true iff the note is within the extent of the region. * @param visible will be set to true if the note is within the visible note range, false otherwise. */ @@ -399,6 +402,7 @@ class MidiRegionView : public RegionView void update_hit (ArdourCanvas::CanvasHit*); MidiListEditor* _list_editor; + bool no_sound_notes; }; diff --git a/gtk2_ardour/midi_scroomer.h b/gtk2_ardour/midi_scroomer.h index 30f77b79e1..5f758c727d 100644 --- a/gtk2_ardour/midi_scroomer.h +++ b/gtk2_ardour/midi_scroomer.h @@ -24,12 +24,11 @@ #include class MidiScroomer : public Gtkmm2ext::Scroomer { -public: + public: MidiScroomer(Gtk::Adjustment&); ~MidiScroomer(); bool on_expose_event(GdkEventExpose*); - void on_size_request(Gtk::Requisition*); void on_size_allocate(Gtk::Allocation&); diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 32a69ba218..f647dd9dcc 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -52,6 +52,8 @@ #include "ardour/tempo.h" #include "ardour/utils.h" +#include "midi++/names.h" + #include "add_midi_cc_track_dialog.h" #include "ardour_ui.h" #include "automation_line.h" @@ -142,6 +144,11 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess, if (is_track()) { _piano_roll_header = new PianoRollHeader(*midi_view()); + + _piano_roll_header->AddNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection)); + _piano_roll_header->ExtendNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection)); + _piano_roll_header->ToggleNoteSelection.connect (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection)); + _range_scroomer = new MidiScroomer(midi_view()->note_range_adjustment); controls_hbox.pack_start(*_range_scroomer); @@ -208,6 +215,8 @@ MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session* sess, _percussion_mode_item->set_active (_note_mode == Percussive); } } + + build_controller_menu (); } MidiTimeAxisView::~MidiTimeAxisView () @@ -217,6 +226,8 @@ MidiTimeAxisView::~MidiTimeAxisView () delete _range_scroomer; _range_scroomer = 0; + + delete controller_menu; } void MidiTimeAxisView::model_changed() @@ -372,12 +383,131 @@ MidiTimeAxisView::build_automation_action_menu () MenuList& automation_items = automation_action_menu->items(); automation_items.push_back (SeparatorElem()); - automation_items.push_back (MenuElem (_("Show Controller..."), - sigc::mem_fun(*this, &MidiTimeAxisView::add_cc_track))); add_basic_parameter_menu_item (automation_items, _("Program Change"), Evoral::Parameter (MidiPgmChangeAutomation)); add_basic_parameter_menu_item (automation_items, _("Bender"), Evoral::Parameter (MidiPitchBenderAutomation)); add_basic_parameter_menu_item (automation_items, _("Pressure"), Evoral::Parameter (MidiChannelPressureAutomation)); + + if (controller_menu) { + + if (controller_menu->gobj()) { + if (controller_menu->get_attach_widget()) { + controller_menu->detach(); + } + } + + automation_items.push_back (MenuElem (_("Controllers"), *controller_menu)); + } +} + +void +MidiTimeAxisView::build_controller_menu () +{ + using namespace Menu_Helpers; + + controller_menu = new Menu; // not managed + MenuList& items (controller_menu->items()); + + /* create several "top level" menu items for sets of controllers (16 at a time), and populate each one with a submenu + for each controller+channel combination covering the currently selected channels for this track + */ + + uint16_t selected_channels = _channel_selector.get_selected_channels(); + + /* count the number of selected channels because we will build a different menu structure if there is more than 1 selected. + */ + + int chn_cnt = 0; + + for (uint8_t chn = 0; chn < 16; chn++) { + if (selected_channels & (0x0001 << chn)) { + if (++chn_cnt > 1) { + break; + } + } + } + + for (int i = 0; i < 127; i += 16) { + + Menu* ctl_menu = manage (new Menu); + MenuList& ctl_items (ctl_menu->items()); + + for (int ctl = i; ctl < i+16; ++ctl) { + + + if (chn_cnt > 1) { + + /* multiple channels - create a submenu, with 1 item per channel */ + + Menu* chn_menu = manage (new Menu); + MenuList& chn_items (chn_menu->items()); + + for (uint8_t chn = 0; chn < 16; chn++) { + if (selected_channels & (0x0001 << chn)) { + + /* for each selected channel, add a menu item for this controller */ + + Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl); + chn_items.push_back (CheckMenuElem (string_compose (_("Channel %1"), chn+1), + sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_parameter_track), + fully_qualified_param))); + + RouteAutomationNode* node = automation_track (fully_qualified_param); + bool visible = false; + + if (node) { + if (node->track->marked_for_display()) { + visible = true; + } + } + + CheckMenuItem* cmi = static_cast(&chn_items.back()); + cmi->set_active (visible); + + parameter_menu_map[fully_qualified_param] = cmi; + } + } + + /* add the per-channel menu to the list of controllers, with the name of the controller */ + + ctl_items.push_back (MenuElem (midi_name (ctl), *chn_menu)); + + } else { + + /* just one channel - create a single menu item for this ctl+channel combination*/ + + for (uint8_t chn = 0; chn < 16; chn++) { + if (selected_channels & (0x0001 << chn)) { + + Evoral::Parameter fully_qualified_param (MidiCCAutomation, chn, ctl); + ctl_items.push_back (CheckMenuElem (_route->describe_parameter (fully_qualified_param), + sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_parameter_track), + fully_qualified_param))); + + RouteAutomationNode* node = automation_track (fully_qualified_param); + bool visible = false; + + if (node) { + if (node->track->marked_for_display()) { + visible = true; + } + } + + CheckMenuItem* cmi = static_cast(&ctl_items.back()); + cmi->set_active (visible); + + parameter_menu_map[fully_qualified_param] = cmi; + /* one channel only */ + break; + } + } + } + } + + /* add the menu for this block of controllers to the overall controller menu */ + + items.push_back (MenuElem (string_compose (_("Controllers %1-%2"), i+1, i+16), *ctl_menu)); + } } void @@ -811,3 +941,69 @@ MidiTimeAxisView::add_region (nframes64_t pos) return region; } + +void +MidiTimeAxisView::add_note_selection (uint8_t note) +{ + if (!_editor.internal_editing()) { + return; + } + + uint16_t chn_mask = _channel_selector.get_selected_channels(); + + if (_view->num_selected_regionviews() == 0) { + _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask)); + } else { + _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask)); + } +} + +void +MidiTimeAxisView::extend_note_selection (uint8_t note) +{ + if (!_editor.internal_editing()) { + return; + } + + uint16_t chn_mask = _channel_selector.get_selected_channels(); + + if (_view->num_selected_regionviews() == 0) { + _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask)); + } else { + _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask)); + } +} + +void +MidiTimeAxisView::toggle_note_selection (uint8_t note) +{ + if (!_editor.internal_editing()) { + return; + } + + uint16_t chn_mask = _channel_selector.get_selected_channels(); + + if (_view->num_selected_regionviews() == 0) { + _view->foreach_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask)); + } else { + _view->foreach_selected_regionview (sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask)); + } +} + +void +MidiTimeAxisView::add_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask) +{ + dynamic_cast(rv)->select_matching_notes (note, chn_mask, false, false); +} + +void +MidiTimeAxisView::extend_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask) +{ + dynamic_cast(rv)->select_matching_notes (note, chn_mask, true, true); +} + +void +MidiTimeAxisView::toggle_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask) +{ + dynamic_cast(rv)->toggle_matching_notes (note, chn_mask); +} diff --git a/gtk2_ardour/midi_time_axis.h b/gtk2_ardour/midi_time_axis.h index 3aa4abb158..2d2c012811 100644 --- a/gtk2_ardour/midi_time_axis.h +++ b/gtk2_ardour/midi_time_axis.h @@ -146,6 +146,16 @@ class MidiTimeAxisView : public RouteTimeAxisView typedef std::map ParameterMenuMap; ParameterMenuMap parameter_menu_map; + + Gtk::Menu* controller_menu; + void build_controller_menu (); + + void add_note_selection (uint8_t note); + void extend_note_selection (uint8_t note); + void toggle_note_selection (uint8_t note); + void add_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask); + void extend_note_selection_region_view (RegionView*, uint8_t note, uint16_t chn_mask); + void toggle_note_selection_region_view (RegionView*, uint8_t note, uint16_t chn_mask); }; #endif /* __ardour_midi_time_axis_h__ */ diff --git a/gtk2_ardour/piano_roll_header.cc b/gtk2_ardour/piano_roll_header.cc index 39c48f0164..e88f551e91 100644 --- a/gtk2_ardour/piano_roll_header.cc +++ b/gtk2_ardour/piano_roll_header.cc @@ -20,6 +20,8 @@ #include "evoral/midi_events.h" #include "ardour/midi_track.h" +#include "gtkmm2ext/keyboard.h" + #include "piano_roll_header.h" #include "midi_time_axis.h" #include "midi_streamview.h" @@ -27,6 +29,7 @@ const int no_note = 0xff; 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(0.87f, 0.88f, 0.86f); @@ -458,28 +461,31 @@ PianoRollHeader::on_expose_event (GdkEventExpose* ev) bool PianoRollHeader::on_motion_notify_event (GdkEventMotion* ev) { - int note = _view.y_to_note(ev->y); + if (_dragging) { - if (_highlighted_note != no_note) { - if (note > _highlighted_note) { - invalidate_note_range(_highlighted_note, note); - } else { - invalidate_note_range(note, _highlighted_note); + int note = _view.y_to_note(ev->y); + + if (_highlighted_note != no_note) { + if (note > _highlighted_note) { + invalidate_note_range(_highlighted_note, note); + } else { + invalidate_note_range(note, _highlighted_note); + } + + _highlighted_note = note; } - - _highlighted_note = note; - } - - /* redraw already taken care of above */ - if (_clicked_note != no_note && _clicked_note != note) { - _active_notes[_clicked_note] = false; - send_note_off(_clicked_note); - - _clicked_note = note; - - if (!_active_notes[note]) { - _active_notes[note] = true; - send_note_on(note); + + /* redraw already taken care of above */ + if (_clicked_note != no_note && _clicked_note != note) { + _active_notes[_clicked_note] = false; + send_note_off(_clicked_note); + + _clicked_note = note; + + if (!_active_notes[note]) { + _active_notes[note] = true; + send_note_on(note); + } } } @@ -493,18 +499,25 @@ PianoRollHeader::on_button_press_event (GdkEventButton* ev) { int note = _view.y_to_note(ev->y); - if (ev->type == GDK_BUTTON_PRESS && note >= 0 && note < 128) { - add_modal_grab(); - _dragging = true; - - if (!_active_notes[note]) { - _active_notes[note] = true; - _clicked_note = note; - send_note_on(note); - - invalidate_note_range(note, note); - } else { - _clicked_note = no_note; + if (ev->button == 2) { + send_note_on (note); + /* relax till release */ + } else { + + if (ev->type == GDK_BUTTON_PRESS && note >= 0 && note < 128) { + + add_modal_grab(); + _dragging = true; + + if (!_active_notes[note]) { + _active_notes[note] = true; + _clicked_note = note; + send_note_on(note); + + invalidate_note_range(note, note); + } else { + _clicked_note = no_note; + } } } @@ -516,17 +529,31 @@ PianoRollHeader::on_button_release_event (GdkEventButton* ev) { int note = _view.y_to_note(ev->y); - if (_dragging) { - remove_modal_grab(); - _dragging = false; + if (ev->button == 2) { + send_note_off (note); - if (note == _clicked_note) { - _active_notes[note] = false; - _clicked_note = no_note; - send_note_off(note); - - invalidate_note_range(note, note); + if (Keyboard::no_modifiers_active (ev->state)) { + AddNoteSelection (note); // EMIT SIGNAL + } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + ToggleNoteSelection (note); // EMIT SIGNAL + } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::RangeSelectModifier)) { + ExtendNoteSelection (note); // EMIT SIGNAL } + + } else { + + if (_dragging) { + remove_modal_grab(); + _dragging = false; + + if (note == _clicked_note) { + _active_notes[note] = false; + _clicked_note = no_note; + send_note_off(note); + + invalidate_note_range(note, note); + } + } } return true; @@ -672,3 +699,4 @@ PianoRollHeader::send_note_off(uint8_t note) track->write_immediate_event(3, _event); } } + diff --git a/gtk2_ardour/piano_roll_header.h b/gtk2_ardour/piano_roll_header.h index 7369303e5d..453c32468d 100644 --- a/gtk2_ardour/piano_roll_header.h +++ b/gtk2_ardour/piano_roll_header.h @@ -1,5 +1,6 @@ /* Copyright (C) 2008 Paul Davis + Author: Audan Holland ?? This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -57,6 +58,10 @@ public: double b; }; + sigc::signal AddNoteSelection; + sigc::signal ToggleNoteSelection; + sigc::signal ExtendNoteSelection; + private: static Color white; static Color white_highlight; diff --git a/gtk2_ardour/streamview.cc b/gtk2_ardour/streamview.cc index e7914051a7..b93606fbe8 100644 --- a/gtk2_ardour/streamview.cc +++ b/gtk2_ardour/streamview.cc @@ -470,6 +470,19 @@ StreamView::find_view (boost::shared_ptr region) return 0; } +uint32_t +StreamView::num_selected_regionviews () const +{ + uint32_t cnt = 0; + + for (list::const_iterator i = region_views.begin(); i != region_views.end(); ++i) { + if ((*i)->get_selected()) { + ++cnt; + } + } + return cnt; +} + void StreamView::foreach_regionview (sigc::slot slot) { @@ -478,6 +491,16 @@ StreamView::foreach_regionview (sigc::slot slot) } } +void +StreamView::foreach_selected_regionview (sigc::slot slot) +{ + for (list::iterator i = region_views.begin(); i != region_views.end(); ++i) { + if ((*i)->get_selected()) { + slot (*i); + } + } +} + void StreamView::set_selected_regionviews (RegionSelection& regions) { diff --git a/gtk2_ardour/streamview.h b/gtk2_ardour/streamview.h index d28dc37133..b32d75d5ac 100644 --- a/gtk2_ardour/streamview.h +++ b/gtk2_ardour/streamview.h @@ -87,8 +87,11 @@ public: Gdk::Color get_region_color () const { return region_color; } void apply_color (Gdk::Color&, ColorTarget t); + uint32_t num_selected_regionviews () const; + RegionView* find_view (boost::shared_ptr); void foreach_regionview (sigc::slot slot); + void foreach_selected_regionview (sigc::slot slot); void set_selected_regionviews (RegionSelection&); void get_selectables (nframes_t, nframes_t, double, double, std::list&);