X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=5d8bca9d776aeedec877445318fb91482ad3de81;hb=9c986098a2b4492fbe238335f563559166ed765f;hp=57ad64e0149db2e1bfda711930d7f7cc1fd8b383;hpb=988348185ece58b9af4ecc3a919e7f4862fa9a54;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 57ad64e014..5d8bca9d77 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2001-2007 Paul Davis + Copyright (C) 2001-2011 Paul Davis Author: David Robillard This program is free software; you can redistribute it and/or modify @@ -24,15 +24,13 @@ #include -#include +#include "gtkmm2ext/gtk_ui.h" #include #include "pbd/memento_command.h" #include "pbd/stateful_diff_command.h" -#include "ardour/playlist.h" -#include "ardour/tempo.h" #include "ardour/midi_region.h" #include "ardour/midi_source.h" #include "ardour/midi_model.h" @@ -41,6 +39,7 @@ #include "evoral/Parameter.hpp" #include "evoral/MIDIParameters.hpp" +#include "evoral/MIDIEvent.hpp" #include "evoral/Control.hpp" #include "evoral/midi_util.h" @@ -51,15 +50,19 @@ #include "canvas_patch_change.h" #include "debug.h" #include "editor.h" +#include "editor_drag.h" #include "ghostregion.h" #include "gui_thread.h" #include "keyboard.h" +#include "midi_channel_dialog.h" #include "midi_cut_buffer.h" #include "midi_list_editor.h" #include "midi_region_view.h" #include "midi_streamview.h" #include "midi_time_axis.h" #include "midi_util.h" +#include "midi_velocity_dialog.h" +#include "mouse_cursors.h" #include "note_player.h" #include "public_editor.h" #include "rgb_macros.h" @@ -67,7 +70,6 @@ #include "simpleline.h" #include "streamview.h" #include "utils.h" -#include "mouse_cursors.h" #include "patch_change_dialog.h" #include "verbose_cursor.h" @@ -79,57 +81,60 @@ using namespace Editing; using namespace ArdourCanvas; using Gtkmm2ext::Keyboard; +PBD::Signal1 MidiRegionView::SelectionCleared; + +#define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1) + MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, - boost::shared_ptr r, double spu, Gdk::Color const & basic_color) + boost::shared_ptr r, double spu, Gdk::Color const & basic_color) : RegionView (parent, tv, r, spu, basic_color) - , _force_channel(-1) , _last_channel_selection(0xFFFF) , _current_range_min(0) , _current_range_max(0) - , _model_name(string()) - , _custom_device_mode(string()) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*group)) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) - , _step_edit_cursor (0) - , _step_edit_cursor_width (1.0) - , _step_edit_cursor_position (0.0) - , _channel_selection_scoped_note (0) + , _step_edit_cursor (0) + , _step_edit_cursor_width (1.0) + , _step_edit_cursor_position (0.0) + , _channel_selection_scoped_note (0) , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) - , no_sound_notes (false) + , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) - , pre_enter_cursor (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) + , _note_player (0) { _note_group->raise_to_top(); - PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); + PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); + Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); connect_to_diskstream (); + + SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); } MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, - boost::shared_ptr r, double spu, Gdk::Color& basic_color, - TimeAxisViewItem::Visibility visibility) + boost::shared_ptr r, double spu, Gdk::Color& basic_color, + TimeAxisViewItem::Visibility visibility) : RegionView (parent, tv, r, spu, basic_color, false, visibility) - , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _model_name(string()) - , _custom_device_mode(string()) + , _current_range_min(0) + , _current_range_max(0) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*parent)) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) - , _step_edit_cursor (0) - , _step_edit_cursor_width (1.0) - , _step_edit_cursor_position (0.0) + , _step_edit_cursor (0) + , _step_edit_cursor_width (1.0) + , _step_edit_cursor_position (0.0) , _channel_selection_scoped_note (0) , _temporary_note_group (0) , _mouse_state(None) @@ -137,41 +142,57 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) - , no_sound_notes (false) + , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) + , _note_player (0) { _note_group->raise_to_top(); - PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); + PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys)); connect_to_diskstream (); + + SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); +} + +void +MidiRegionView::parameter_changed (std::string const & p) +{ + if (p == "diplay-first-midi-bank-as-zero") { + if (_enable_display) { + redisplay_model(); + } + } } MidiRegionView::MidiRegionView (const MidiRegionView& other) : sigc::trackable(other) , RegionView (other) - , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _model_name(string()) - , _custom_device_mode(string()) + , _current_range_min(0) + , _current_range_max(0) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*get_canvas_group())) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) - , _step_edit_cursor (0) - , _step_edit_cursor_width (1.0) - , _step_edit_cursor_position (0.0) - , _channel_selection_scoped_note (0) + , _step_edit_cursor (0) + , _step_edit_cursor_width (1.0) + , _step_edit_cursor_position (0.0) + , _channel_selection_scoped_note (0) , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) - , no_sound_notes (false) + , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) + , _note_player (0) { Gdk::Color c; int r,g,b,a; @@ -184,28 +205,29 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr region) : RegionView (other, boost::shared_ptr (region)) - , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _model_name(string()) - , _custom_device_mode(string()) + , _current_range_min(0) + , _current_range_max(0) , _active_notes(0) , _note_group(new ArdourCanvas::Group(*get_canvas_group())) , _note_diff_command (0) , _ghost_note(0) - , _drag_rect (0) - , _step_edit_cursor (0) - , _step_edit_cursor_width (1.0) - , _step_edit_cursor_position (0.0) - , _channel_selection_scoped_note (0) + , _step_edit_cursor (0) + , _step_edit_cursor_width (1.0) + , _step_edit_cursor_position (0.0) + , _channel_selection_scoped_note (0) , _temporary_note_group (0) , _mouse_state(None) , _pressed_button(0) , _sort_needed (true) , _optimization_iterator (_events.end()) , _list_editor (0) - , no_sound_notes (false) + , _no_sound_notes (false) , _last_event_x (0) , _last_event_y (0) + , pre_enter_cursor (0) + , pre_press_cursor (0) + , _note_player (0) { Gdk::Color c; int r,g,b,a; @@ -219,11 +241,11 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrmidi_source(0)->load_model(); @@ -255,28 +277,52 @@ MidiRegionView::init (Gdk::Color const & basic_color, bool wfd) } group->raise_to_top(); - group->signal_event().connect (sigc::mem_fun (this, &MidiRegionView::canvas_event), false); + group->signal_event().connect( + sigc::mem_fun(this, &MidiRegionView::canvas_event), false); midi_view()->signal_channel_mode_changed().connect( - sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed)); + sigc::mem_fun(this, &MidiRegionView::midi_channel_mode_changed)); - midi_view()->signal_midi_patch_settings_changed().connect( - sigc::mem_fun(this, &MidiRegionView::midi_patch_settings_changed)); + instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this), + boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context()); - trackview.editor().SnapChanged.connect (snap_changed_connection, invalidator (*this), ui_bind (&MidiRegionView::snap_changed, this), gui_context ()); + trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this), + boost::bind (&MidiRegionView::snap_changed, this), + gui_context()); + Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context()); connect_to_diskstream (); + + SelectionCleared.connect (_selection_cleared_connection, invalidator (*this), boost::bind (&MidiRegionView::selection_cleared, this, _1), gui_context ()); +} + +InstrumentInfo& +MidiRegionView::instrument_info () const +{ + RouteUI* route_ui = dynamic_cast (&trackview); + return route_ui->route()->instrument_info(); +} + +const boost::shared_ptr +MidiRegionView::midi_region() const +{ + return boost::dynamic_pointer_cast(_region); } void MidiRegionView::connect_to_diskstream () { - midi_view()->midi_track()->DataRecorded.connect (*this, invalidator (*this), ui_bind (&MidiRegionView::data_recorded, this, _1, _2), gui_context ()); + midi_view()->midi_track()->DataRecorded.connect( + *this, invalidator(*this), + boost::bind (&MidiRegionView::data_recorded, this, _1), + gui_context()); } bool MidiRegionView::canvas_event(GdkEvent* ev) { + bool r; + switch (ev->type) { case GDK_ENTER_NOTIFY: case GDK_LEAVE_NOTIFY: @@ -290,41 +336,48 @@ MidiRegionView::canvas_event(GdkEvent* ev) default: break; } - - if (!trackview.editor().internal_editing()) { + + if (ev->type == GDK_2BUTTON_PRESS) { + return trackview.editor().toggle_internal_editing_from_double_click (ev); + } + + if ((!trackview.editor().internal_editing() && trackview.editor().current_mouse_mode() != MouseGain) || + (trackview.editor().current_mouse_mode() == MouseTimeFX) || + (trackview.editor().current_mouse_mode() == MouseZoom)) { + // handle non-draw modes elsewhere return false; } switch (ev->type) { case GDK_SCROLL: - return scroll (&ev->scroll); + return scroll (&ev->scroll); case GDK_KEY_PRESS: - return key_press (&ev->key); + return key_press (&ev->key); case GDK_KEY_RELEASE: - return key_release (&ev->key); + return key_release (&ev->key); case GDK_BUTTON_PRESS: - return button_press (&ev->button); - - case GDK_2BUTTON_PRESS: - return true; + return button_press (&ev->button); case GDK_BUTTON_RELEASE: - return button_release (&ev->button); - + r = button_release (&ev->button); + delete _note_player; + _note_player = 0; + return r; + case GDK_ENTER_NOTIFY: - return enter_notify (&ev->crossing); + return enter_notify (&ev->crossing); case GDK_LEAVE_NOTIFY: - return leave_notify (&ev->crossing); + return leave_notify (&ev->crossing); case GDK_MOTION_NOTIFY: - return motion (&ev->motion); + return motion (&ev->motion); - default: - break; + default: + break; } return false; @@ -333,471 +386,508 @@ MidiRegionView::canvas_event(GdkEvent* ev) void MidiRegionView::remove_ghost_note () { - delete _ghost_note; - _ghost_note = 0; + delete _ghost_note; + _ghost_note = 0; } bool MidiRegionView::enter_notify (GdkEventCrossing* ev) { trackview.editor().MouseModeChanged.connect ( - _mouse_mode_connection, invalidator (*this), ui_bind (&MidiRegionView::mouse_mode_changed, this), gui_context () + _mouse_mode_connection, invalidator (*this), boost::bind (&MidiRegionView::mouse_mode_changed, this), gui_context () ); - Keyboard::magic_widget_grab_focus(); - group->grab_focus(); - - if (trackview.editor().current_mouse_mode() == MouseRange) { + if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) { create_ghost_note (ev->x, ev->y); } - return false; + if (!trackview.editor().internal_editing()) { + Keyboard::magic_widget_drop_focus(); + } else { + Keyboard::magic_widget_grab_focus(); + group->grab_focus(); + } + + // if current operation is non-operational in a midi region, change the cursor to so indicate + if (trackview.editor().current_mouse_mode() == MouseGain) { + Editor* editor = dynamic_cast (&trackview.editor()); + pre_enter_cursor = editor->get_canvas_cursor(); + editor->set_canvas_cursor(editor->cursors()->timebar); + } + + return false; } bool MidiRegionView::leave_notify (GdkEventCrossing*) { _mouse_mode_connection.disconnect (); - - trackview.editor().verbose_cursor()->hide (); + + trackview.editor().verbose_cursor()->hide (); remove_ghost_note (); - return false; + + if (pre_enter_cursor) { + Editor* editor = dynamic_cast (&trackview.editor()); + editor->set_canvas_cursor(pre_enter_cursor); + } + + return false; } void MidiRegionView::mouse_mode_changed () { - if (trackview.editor().current_mouse_mode() == MouseRange && trackview.editor().internal_editing()) { + if (trackview.editor().current_mouse_mode() == MouseDraw && trackview.editor().internal_editing()) { create_ghost_note (_last_event_x, _last_event_y); } else { remove_ghost_note (); trackview.editor().verbose_cursor()->hide (); } + + if (!trackview.editor().internal_editing()) { + Keyboard::magic_widget_drop_focus(); + } else { + Keyboard::magic_widget_grab_focus(); + group->grab_focus(); + } } bool MidiRegionView::button_press (GdkEventButton* ev) { - if (ev->button != 1) { - return false; - } + if (ev->button != 1) { + return false; + } + + Editor* editor = dynamic_cast (&trackview.editor()); + MouseMode m = editor->current_mouse_mode(); + + if (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { + pre_press_cursor = editor->get_canvas_cursor (); + editor->set_canvas_cursor (editor->cursors()->midi_pencil); + } + + if (_mouse_state != SelectTouchDragging) { + + _pressed_button = ev->button; + _mouse_state = Pressed; + + return true; + } - _last_x = ev->x; - _last_y = ev->y; - - group->w2i (_last_x, _last_y); - - if (_mouse_state != SelectTouchDragging) { - - _pressed_button = ev->button; - _mouse_state = Pressed; - - return true; - } - - _pressed_button = ev->button; + _pressed_button = ev->button; - return true; + return true; } bool MidiRegionView::button_release (GdkEventButton* ev) { double event_x, event_y; - framepos_t event_frame = 0; - if (ev->button != 1) { - return false; - } + if (ev->button != 1) { + return false; + } - event_x = ev->x; - event_y = ev->y; - - group->w2i(event_x, event_y); - group->ungrab(ev->time); - - event_frame = trackview.editor().pixel_to_frame(event_x); - - switch (_mouse_state) { - case Pressed: // Clicked - - switch (trackview.editor().current_mouse_mode()) { - case MouseObject: - case MouseTimeFX: - { - clear_selection(); - - if (Keyboard::is_insert_note_event(ev)){ - - double event_x, event_y; - - event_x = ev->x; - event_y = ev->y; - group->w2i(event_x, event_y); + event_x = ev->x; + event_y = ev->y; + + group->w2i(event_x, event_y); + group->ungrab(ev->time); + + PublicEditor& editor = trackview.editor (); + + if (pre_press_cursor) { + dynamic_cast(&editor)->set_canvas_cursor (pre_press_cursor, false); + pre_press_cursor = 0; + } + + switch (_mouse_state) { + case Pressed: // Clicked + switch (editor.current_mouse_mode()) { + case MouseRange: + /* no motion occured - simple click */ + clear_selection (); + break; + + case MouseObject: + case MouseTimeFX: + { + clear_selection(); + + if (Keyboard::is_insert_note_event(ev)) { + + double event_x, event_y; + + event_x = ev->x; + event_y = ev->y; + group->w2i(event_x, event_y); + + bool success; + Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x)); + + if (!success) { + beats = 1; + } + + /* Shorten the length by 1 tick so that we can add a new note at the next + grid snap without it overlapping this one. + */ + beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat; + + create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true); + } + + break; + } + case MouseDraw: + { bool success; - Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x)); - + Evoral::MusicalTime beats = editor.get_grid_type_as_beats (success, editor.pixel_to_frame (event_x)); + if (!success) { beats = 1; } + + /* Shorten the length by 1 tick so that we can add a new note at the next + grid snap without it overlapping this one. + */ + beats -= 1.0 / Timecode::BBT_Time::ticks_per_beat; - create_note_at (event_x, event_y, beats, true); - } + create_note_at (editor.pixel_to_frame (event_x), event_y, beats, true); - break; - } - case MouseRange: - { - bool success; - Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, trackview.editor().pixel_to_frame (event_x)); - - if (!success) { - beats = 1; - } - - create_note_at (event_x, event_y, beats, true); - + break; + } + default: break; - } - default: - break; - } - - _mouse_state = None; - break; - - case SelectRectDragging: // Select drag done - - _mouse_state = None; - delete _drag_rect; - _drag_rect = 0; - break; - - case AddDragging: // Add drag done - - _mouse_state = None; - - if (Keyboard::is_insert_note_event(ev) || trackview.editor().current_mouse_mode() == MouseRange){ - - if (_drag_rect->property_x2() > _drag_rect->property_x1() + 2) { + } - const double x = _drag_rect->property_x1(); - const double length = trackview.editor().pixel_to_frame (_drag_rect->property_x2() - _drag_rect->property_x1()); + _mouse_state = None; + break; - create_note_at (x, _drag_rect->property_y1(), frames_to_beats(length), true); - } - } + case SelectRectDragging: + case AddDragging: + editor.drags()->end_grab ((GdkEvent *) ev); + _mouse_state = None; + create_ghost_note (ev->x, ev->y); + break; - delete _drag_rect; - _drag_rect = 0; - create_ghost_note (ev->x, ev->y); - - default: - break; - } + default: + break; + } - return false; + return false; } bool MidiRegionView::motion (GdkEventMotion* ev) { - double event_x, event_y; - framepos_t event_frame = 0; + PublicEditor& editor = trackview.editor (); - event_x = ev->x; - event_y = ev->y; - group->w2i(event_x, event_y); + if (!_ghost_note && editor.current_mouse_mode() == MouseObject && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) && + _mouse_state != AddDragging) { - // convert event_x to global frame - event_frame = snap_pixel_to_frame (event_x); - - if (!_ghost_note && trackview.editor().current_mouse_mode() != MouseRange - && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) - && _mouse_state != AddDragging){ - create_ghost_note (ev->x, ev->y); - } - else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange - && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())){ - - update_ghost_note (ev->x, ev->y); - } - else if (_ghost_note && trackview.editor().current_mouse_mode() != MouseRange){ - - delete _ghost_note; - _ghost_note = 0; - - trackview.editor().verbose_cursor()->hide (); - } - else if (_ghost_note && trackview.editor().current_mouse_mode() == MouseRange) { - update_ghost_note (ev->x, ev->y); - } - /* any motion immediately hides velocity text that may have been visible */ - - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - (*i)->hide_velocity (); - } + } else if (_ghost_note && editor.current_mouse_mode() == MouseObject && + Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { - switch (_mouse_state) { - case Pressed: // Maybe start a drag, if we've moved a bit - - if (fabs (event_x - _last_x) < 1 && fabs (event_y - _last_y) < 1) { - /* no appreciable movement since the button was pressed */ - return false; - } - - // Select drag start - if (_pressed_button == 1 && trackview.editor().current_mouse_mode() == MouseObject - && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) { - - group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, - Gdk::Cursor(Gdk::FLEUR), ev->time); - - _last_x = event_x; - _last_y = event_y; - _drag_start_x = event_x; - _drag_start_y = event_y; - - _drag_rect = new ArdourCanvas::SimpleRect(*group); - _drag_rect->property_x1() = event_x; - _drag_rect->property_y1() = event_y; - _drag_rect->property_x2() = event_x; - _drag_rect->property_y2() = event_y; - _drag_rect->property_outline_what() = 0xFF; - _drag_rect->property_outline_color_rgba() - = ARDOUR_UI::config()->canvasvar_MidiSelectRectOutline.get(); - _drag_rect->property_fill_color_rgba() - = ARDOUR_UI::config()->canvasvar_MidiSelectRectFill.get(); - - _mouse_state = SelectRectDragging; - return true; - - // Add note drag start - } else if (trackview.editor().internal_editing()) { - - delete _ghost_note; - _ghost_note = 0; - - group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, - Gdk::Cursor(Gdk::FLEUR), ev->time); - - _last_x = event_x; - _last_y = event_y; - _drag_start_x = event_x; - _drag_start_y = event_y; - - _drag_rect = new ArdourCanvas::SimpleRect(*group); - _drag_rect->property_x1() = trackview.editor().frame_to_pixel(event_frame); - - _drag_rect->property_y1() = midi_stream_view()->note_to_y( - midi_stream_view()->y_to_note(event_y)); - _drag_rect->property_x2() = trackview.editor().frame_to_pixel(event_frame); - _drag_rect->property_y2() = _drag_rect->property_y1() - + floor(midi_stream_view()->note_height()); - _drag_rect->property_outline_what() = 0xFF; - _drag_rect->property_outline_color_rgba() = 0xFFFFFF99; - _drag_rect->property_fill_color_rgba() = 0xFFFFFF66; - - _mouse_state = AddDragging; - - if (_ghost_note){ - - delete _ghost_note; - _ghost_note = 0; - - trackview.editor().verbose_cursor()->hide (); - } + update_ghost_note (ev->x, ev->y); + + } else if (_ghost_note && editor.current_mouse_mode() == MouseObject) { - return true; - } + remove_ghost_note (); + editor.verbose_cursor()->hide (); - return false; + } else if (_ghost_note && editor.current_mouse_mode() == MouseDraw) { - case SelectRectDragging: // Select drag motion - case AddDragging: // Add note drag motion + update_ghost_note (ev->x, ev->y); + } - if (ev->is_hint) { - int t_x; - int t_y; - GdkModifierType state; - gdk_window_get_pointer(ev->window, &t_x, &t_y, &state); - event_x = t_x; - event_y = t_y; - } + /* any motion immediately hides velocity text that may have been visible */ - if (_mouse_state == AddDragging){ - event_x = trackview.editor().frame_to_pixel(event_frame); - } + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + (*i)->hide_velocity (); + } - if (_drag_rect) { - - if (event_x > _drag_start_x){ - _drag_rect->property_x2() = event_x; - } - else { - _drag_rect->property_x1() = event_x; - } - } + switch (_mouse_state) { + case Pressed: - if (_drag_rect && _mouse_state == SelectRectDragging) { - - if (event_y > _drag_start_y){ - _drag_rect->property_y2() = event_y; - } - else { - _drag_rect->property_y1() = event_y; + if (_pressed_button == 1) { + + MouseMode m = editor.current_mouse_mode(); + + if (m == MouseDraw || (m == MouseObject && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) { + + editor.drags()->set (new NoteCreateDrag (dynamic_cast (&editor), group, this), (GdkEvent *) ev); + _mouse_state = AddDragging; + remove_ghost_note (); + editor.verbose_cursor()->hide (); + return true; + } else if (m == MouseObject) { + editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); + clear_selection (); + _mouse_state = SelectRectDragging; + return true; + } else if (m == MouseRange) { + editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast (&editor), this), (GdkEvent *) ev); + _mouse_state = SelectVerticalDragging; + return true; } + } - update_drag_selection(_drag_start_x, event_x, _drag_start_y, event_y); - } - - _last_x = event_x; - _last_y = event_y; + return false; - case SelectTouchDragging: - return false; + case SelectRectDragging: + case SelectVerticalDragging: + case AddDragging: + editor.drags()->motion_handler ((GdkEvent *) ev, false); + break; + + case SelectTouchDragging: + return false; - default: - break; - } + default: + break; + } - return false; + return false; } bool MidiRegionView::scroll (GdkEventScroll* ev) { - if (_selection.empty()) { - return false; - } + if (_selection.empty()) { + return false; + } + + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + /* XXX: bit of a hack; allow PrimaryModifier scroll through so that + it still works for zoom. + */ + return false; + } trackview.editor().verbose_cursor()->hide (); - bool fine = !Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier); - - if (ev->direction == GDK_SCROLL_UP) { - change_velocities (true, fine, false); - } else if (ev->direction == GDK_SCROLL_DOWN) { - change_velocities (false, fine, false); - } - return true; + bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); + bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); + + if (ev->direction == GDK_SCROLL_UP) { + change_velocities (true, fine, false, together); + } else if (ev->direction == GDK_SCROLL_DOWN) { + change_velocities (false, fine, false, together); + } + return true; } bool MidiRegionView::key_press (GdkEventKey* ev) -{ - /* since GTK bindings are generally activated on press, and since - detectable auto-repeat is the name of the game and only sends - repeated presses, carry out key actions at key press, not release. - */ - - if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R){ - _mouse_state = SelectTouchDragging; - return true; - - } else if (ev->keyval == GDK_Escape) { - clear_selection(); - _mouse_state = None; - - } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) { - - bool start = (ev->keyval == GDK_comma); - bool end = (ev->keyval == GDK_period); - bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier); - bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - - change_note_lengths (fine, shorter, 0.0, start, end); - - return true; - - } else if (ev->keyval == GDK_Delete) { - - delete_selection(); - return true; - - } else if (ev->keyval == GDK_Tab) { - - if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - goto_previous_note (); - } else { - goto_next_note (); - } - return true; - - } else if (ev->keyval == GDK_Up) { - - bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); - bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - - if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { - change_velocities (true, fine, allow_smush); - } else { - transpose (true, fine, allow_smush); - } - return true; - - } else if (ev->keyval == GDK_Down) { - - bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); - bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); - - if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { - change_velocities (false, fine, allow_smush); - } else { - transpose (false, fine, allow_smush); - } - return true; - - } else if (ev->keyval == GDK_Left) { - - nudge_notes (false); - return true; - - } else if (ev->keyval == GDK_Right) { - - nudge_notes (true); - return true; - - } else if (ev->keyval == GDK_Control_L) { - return true; - - } - - return false; +{ + /* since GTK bindings are generally activated on press, and since + detectable auto-repeat is the name of the game and only sends + repeated presses, carry out key actions at key press, not release. + */ + + bool unmodified = Keyboard::no_modifier_keys_pressed (ev); + + if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { + _mouse_state = SelectTouchDragging; + return true; + + } else if (ev->keyval == GDK_Escape && unmodified) { + clear_selection(); + _mouse_state = None; + + } else if (unmodified && (ev->keyval == GDK_comma || ev->keyval == GDK_period)) { + + bool start = (ev->keyval == GDK_comma); + bool end = (ev->keyval == GDK_period); + bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier); + bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); + + change_note_lengths (fine, shorter, 0.0, start, end); + + return true; + + } else if (ev->keyval == GDK_Delete && unmodified) { + + delete_selection(); + return true; + + } else if (ev->keyval == GDK_Tab) { + + if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { + goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); + } else { + goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); + } + return true; + + } else if (ev->keyval == GDK_ISO_Left_Tab) { + + /* Shift-TAB generates ISO Left Tab, for some reason */ + + if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { + goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); + } else { + goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)); + } + return true; + + + + } else if (ev->keyval == GDK_Up) { + + bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); + bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); + bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier); + + if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { + change_velocities (true, fine, allow_smush, together); + } else { + transpose (true, fine, allow_smush); + } + return true; + + } else if (ev->keyval == GDK_Down) { + + bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier); + bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier); + bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier); + + if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) { + change_velocities (false, fine, allow_smush, together); + } else { + transpose (false, fine, allow_smush); + } + return true; + + } else if (ev->keyval == GDK_Left && unmodified) { + + nudge_notes (false); + return true; + + } else if (ev->keyval == GDK_Right && unmodified) { + + nudge_notes (true); + return true; + + } else if (ev->keyval == GDK_c && unmodified) { + channel_edit (); + return true; + + } else if (ev->keyval == GDK_v && unmodified) { + velocity_edit (); + return true; + } + + return false; } bool MidiRegionView::key_release (GdkEventKey* ev) { - if (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R) { - _mouse_state = None; - return true; - } - return false; + if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) { + _mouse_state = None; + return true; + } + return false; +} + +void +MidiRegionView::channel_edit () +{ + if (_selection.empty()) { + return; + } + + /* pick a note somewhat at random (since Selection is a set<>) to + * provide the "current" channel for the dialog. + */ + + uint8_t current_channel = (*_selection.begin())->note()->channel (); + MidiChannelDialog channel_dialog (current_channel); + int ret = channel_dialog.run (); + + switch (ret) { + case Gtk::RESPONSE_OK: + break; + default: + return; + } + + uint8_t new_channel = channel_dialog.active_channel (); + + start_note_diff_command (_("channel edit")); + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { + Selection::iterator next = i; + ++next; + change_note_channel (*i, new_channel); + i = next; + } + + apply_diff (); +} + +void +MidiRegionView::velocity_edit () +{ + if (_selection.empty()) { + return; + } + + /* pick a note somewhat at random (since Selection is a set<>) to + * provide the "current" velocity for the dialog. + */ + + uint8_t current_velocity = (*_selection.begin())->note()->velocity (); + MidiVelocityDialog velocity_dialog (current_velocity); + int ret = velocity_dialog.run (); + + switch (ret) { + case Gtk::RESPONSE_OK: + break; + default: + return; + } + + uint8_t new_velocity = velocity_dialog.velocity (); + + start_note_diff_command (_("velocity edit")); + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { + Selection::iterator next = i; + ++next; + change_note_velocity (*i, new_velocity, false); + i = next; + } + + apply_diff (); } void MidiRegionView::show_list_editor () { if (!_list_editor) { - _list_editor = new MidiListEditor (trackview.session(), midi_region()); + _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track()); } _list_editor->present (); } /** Add a note to the model, and the view, at a canvas (click) coordinate. - * \param x horizontal position in pixels + * \param t time in frames relative to the position of the region * \param y vertical position in pixels - * \param length duration of the note in beats, which will be snapped to the grid - * \param sh true to make the note 1 frame shorter than the snapped version of \a length. + * \param length duration of the note in beats + * \param snap_t true to snap t to the grid, otherwise false. */ void -MidiRegionView::create_note_at(double x, double y, double length, bool sh) +MidiRegionView::create_note_at (framepos_t t, double y, double length, bool snap_t) { MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); @@ -808,30 +898,26 @@ MidiRegionView::create_note_at(double x, double y, double length, bool sh) assert(note <= 127.0); // Start of note in frames relative to region start - framepos_t const start_frames = snap_pixel_to_frame (x); - assert(start_frames >= 0); - - // Snap length - length = frames_to_beats( - snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames); + if (snap_t) { + framecnt_t grid_frames; + t = snap_frame_to_grid_underneath (t, grid_frames); + } + assert (t >= 0); assert (length != 0); - if (sh) { - length = frames_to_beats (beats_to_frames (length) - 1); - } - - const boost::shared_ptr new_note (new NoteType (get_channel_for_add (), - frames_to_beats(start_frames + _region->start()), length, - (uint8_t)note, 0x40)); + const boost::shared_ptr new_note (new NoteType (mtv->get_channel_for_add (), + region_frames_to_region_beats(t + _region->start()), + length, + (uint8_t)note, 0x40)); - if (_model->contains (new_note)) { - return; - } + if (_model->contains (new_note)) { + return; + } view->update_note_range(new_note->note()); - MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command("add note"); + MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note")); cmd->add (new_note); _model->apply_command(*trackview.session(), cmd); @@ -864,7 +950,7 @@ void MidiRegionView::display_model(boost::shared_ptr model) { _model = model; - + content_connection.disconnect (); _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context()); @@ -907,8 +993,8 @@ MidiRegionView::note_diff_remove_note (ArdourCanvas::CanvasNoteEvent* ev) void MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev, - MidiModel::NoteDiffCommand::Property property, - uint8_t val) + MidiModel::NoteDiffCommand::Property property, + uint8_t val) { if (_note_diff_command) { _note_diff_command->change (ev->note(), property, val); @@ -917,8 +1003,8 @@ MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev, void MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev, - MidiModel::NoteDiffCommand::Property property, - Evoral::MusicalTime val) + MidiModel::NoteDiffCommand::Property property, + Evoral::MusicalTime val) { if (_note_diff_command) { _note_diff_command->change (ev->note(), property, val); @@ -928,31 +1014,31 @@ MidiRegionView::note_diff_add_change (ArdourCanvas::CanvasNoteEvent* ev, void MidiRegionView::apply_diff (bool as_subcommand) { - bool add_or_remove; + bool add_or_remove; if (!_note_diff_command) { return; } - if ((add_or_remove = _note_diff_command->adds_or_removes())) { - // Mark all selected notes for selection when model reloads - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - _marked_for_selection.insert((*i)->note()); - } - } + if ((add_or_remove = _note_diff_command->adds_or_removes())) { + // Mark all selected notes for selection when model reloads + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + _marked_for_selection.insert((*i)->note()); + } + } if (as_subcommand) { _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command); } else { _model->apply_command (*trackview.session(), _note_diff_command); } - + _note_diff_command = 0; midi_view()->midi_track()->playlist_modified(); - - if (add_or_remove) { - _marked_for_selection.clear(); - } + + if (add_or_remove) { + _marked_for_selection.clear(); + } _marked_for_velocity.clear(); } @@ -988,15 +1074,15 @@ MidiRegionView::find_canvas_note (boost::shared_ptr note) void MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) { - MidiModel::Notes notes; - _model->get_notes (notes, op, val, chan_mask); + MidiModel::Notes notes; + _model->get_notes (notes, op, val, chan_mask); - for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { - CanvasNoteEvent* cne = find_canvas_note (*n); - if (cne) { - e.push_back (cne); - } - } + for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { + CanvasNoteEvent* cne = find_canvas_note (*n); + if (cne) { + e.push_back (cne); + } + } } void @@ -1066,8 +1152,17 @@ MidiRegionView::redisplay_model() for (Events::iterator i = _events.begin(); i != _events.end(); ) { if (!(*i)->valid ()) { + + for (vector::iterator j = ghosts.begin(); j != ghosts.end(); ++j) { + MidiGhostRegion* gr = dynamic_cast (*j); + if (gr) { + gr->remove_note (*i); + } + } + delete *i; i = _events.erase (i); + } else { ++i; } @@ -1075,7 +1170,7 @@ MidiRegionView::redisplay_model() _patch_changes.clear(); _sys_exes.clear(); - + display_sysexes(); display_patch_changes (); @@ -1096,45 +1191,83 @@ MidiRegionView::display_patch_changes () MidiTimeAxisView* const mtv = dynamic_cast(&trackview); uint16_t chn_mask = mtv->channel_selector().get_selected_channels(); - for (uint8_t i = 0; i < 16; ++i) { - if (chn_mask & (1<patch_changes().begin(); i != _model->patch_changes().end(); ++i) { if ((*i)->channel() != channel) { continue; } - - MIDI::Name::PatchPrimaryKey patch_key ((*i)->bank_msb (), (*i)->bank_lsb (), (*i)->program ()); - boost::shared_ptr patch = - MIDI::Name::MidiPatchManager::instance().find_patch( - _model_name, _custom_device_mode, channel, patch_key); - - if (patch != 0) { - add_canvas_patch_change (*i, patch->name()); - } else { - char buf[16]; - /* program and bank numbers are zero-based: convert to one-based */ - snprintf (buf, 16, "%d\n%d", (*i)->program() + 1, (*i)->bank() + 1); - add_canvas_patch_change (*i, buf); - } + string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel); + add_canvas_patch_change (*i, patch_name, active_channel); } } void MidiRegionView::display_sysexes() { + bool have_periodic_system_messages = false; + bool display_periodic_messages = true; + + if (!Config->get_never_display_periodic_midi()) { + + for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { + const boost::shared_ptr > mev = + boost::static_pointer_cast > (*i); + + if (mev) { + if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { + have_periodic_system_messages = true; + break; + } + } + } + + if (have_periodic_system_messages) { + double zoom = trackview.editor().get_current_zoom (); // frames per pixel + + /* get an approximate value for the number of samples per video frame */ + + double video_frame = trackview.session()->frame_rate() * (1.0/30); + + /* if we are zoomed out beyond than the cutoff (i.e. more + * frames per pixel than frames per 4 video frames), don't + * show periodic sysex messages. + */ + + if (zoom > (video_frame*4)) { + display_periodic_messages = false; + } + } + } else { + display_periodic_messages = false; + } + for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { + + const boost::shared_ptr > mev = + boost::static_pointer_cast > (*i); + Evoral::MusicalTime time = (*i)->time(); - assert(time >= 0); + assert (time >= 0); + + if (mev) { + if (mev->is_spp() || mev->is_mtc_quarter() || mev->is_mtc_full()) { + if (!display_periodic_messages) { + continue; + } + } + } ostringstream str; str << hex; @@ -1146,14 +1279,14 @@ MidiRegionView::display_sysexes() } string text = str.str(); - const double x = trackview.editor().frame_to_pixel(beats_to_frames(time)); + const double x = trackview.editor().frame_to_pixel(source_beats_to_absolute_frames(time)); double height = midi_stream_view()->contents_height(); boost::shared_ptr sysex = boost::shared_ptr( - new CanvasSysEx(*this, *_note_group, text, height, x, 1.0)); + new CanvasSysEx(*this, *_note_group, text, height, x, 1.0)); - // Show unless patch change is beyond the region bounds + // Show unless message is beyond the region bounds if (time - _region->start() >= _region->length() || time < _region->start()) { sysex->hide(); } else { @@ -1164,14 +1297,13 @@ MidiRegionView::display_sysexes() } } - MidiRegionView::~MidiRegionView () { in_destructor = true; trackview.editor().verbose_cursor()->hide (); - note_delete_connection.disconnect (); + note_delete_connection.disconnect (); delete _list_editor; @@ -1181,12 +1313,14 @@ MidiRegionView::~MidiRegionView () end_write(); } + _selection_cleared_connection.disconnect (); + _selection.clear(); clear_events(); delete _note_group; delete _note_diff_command; - delete _step_edit_cursor; + delete _step_edit_cursor; delete _temporary_note_group; } @@ -1212,9 +1346,17 @@ MidiRegionView::reset_width_dependent_items (double pixel_width) if (_enable_display) { redisplay_model(); } - - move_step_edit_cursor (_step_edit_cursor_position); - set_step_edit_cursor_width (_step_edit_cursor_width); + + for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { + if ((*x)->width() >= _pixel_width) { + (*x)->hide(); + } else { + (*x)->show(); + } + } + + move_step_edit_cursor (_step_edit_cursor_position); + set_step_edit_cursor_width (_step_edit_cursor_width); } void @@ -1232,14 +1374,14 @@ MidiRegionView::set_height (double height) if (name_pixbuf) { name_pixbuf->raise_to_top(); } - - for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { - (*x)->set_height (midi_stream_view()->contents_height()); - } - if (_step_edit_cursor) { - _step_edit_cursor->property_y2() = midi_stream_view()->contents_height(); - } + for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) { + (*x)->set_height (midi_stream_view()->contents_height()); + } + + if (_step_edit_cursor) { + _step_edit_cursor->property_y2() = midi_stream_view()->contents_height(); + } } @@ -1300,7 +1442,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv) if (mtv && mtv->midi_view()) { /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group to allow having midi notes on top of note lines and waveforms. - */ + */ ghost = new MidiGhostRegion (*mtv->midi_view(), trackview, unit_position); } else { ghost = new MidiGhostRegion (tv, trackview, unit_position); @@ -1316,7 +1458,7 @@ MidiRegionView::add_ghost (TimeAxisView& tv) ghost->set_duration (_region->length() / samples_per_unit); ghosts.push_back (ghost); - GhostRegion::CatchDeletion.connect (*this, invalidator (*this), ui_bind (&RegionView::remove_ghost, this, _1), gui_context()); + GhostRegion::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&RegionView::remove_ghost, this, _1), gui_context()); return ghost; } @@ -1358,7 +1500,11 @@ MidiRegionView::resolve_note(uint8_t note, double end_time) if (_active_notes && _active_notes[note]) { - const framepos_t end_time_frames = beats_to_frames(end_time); + /* XXX is end_time really region-centric? I think so, because + this is a new region that we're recording, so source zero is + the same as region zero + */ + const framepos_t end_time_frames = region_beats_to_region_frames(end_time); _active_notes[note]->property_x2() = trackview.editor().frame_to_pixel(end_time_frames); _active_notes[note]->property_outline_what() = (guint32) 0xF; // all edges @@ -1387,51 +1533,71 @@ MidiRegionView::extend_active_notes() void MidiRegionView::play_midi_note(boost::shared_ptr note) { - if (no_sound_notes || !trackview.editor().sound_notes()) { + if (_no_sound_notes || !Config->get_sound_midi_notes()) { + return; + } + + RouteUI* route_ui = dynamic_cast (&trackview); + + if (!route_ui || !route_ui->midi_track()) { + return; + } + + NotePlayer* np = new NotePlayer (route_ui->midi_track ()); + np->add (note); + np->play (); + + /* NotePlayer deletes itself */ +} + +void +MidiRegionView::start_playing_midi_note(boost::shared_ptr note) +{ + if (_no_sound_notes || !Config->get_sound_midi_notes()) { return; } RouteUI* route_ui = dynamic_cast (&trackview); - - if (!route_ui || !route_ui->midi_track()) { - return; - } - NotePlayer* np = new NotePlayer (route_ui->midi_track()); - np->add (note); - np->play (); + if (!route_ui || !route_ui->midi_track()) { + return; + } + + delete _note_player; + _note_player = new NotePlayer (route_ui->midi_track ()); + _note_player->add (note); + _note_player->on (); } void -MidiRegionView::play_midi_chord (vector > notes) +MidiRegionView::start_playing_midi_chord (vector > notes) { - if (no_sound_notes || !trackview.editor().sound_notes()) { + if (_no_sound_notes || !Config->get_sound_midi_notes()) { return; } RouteUI* route_ui = dynamic_cast (&trackview); - - if (!route_ui || !route_ui->midi_track()) { - return; - } - NotePlayer* np = new NotePlayer (route_ui->midi_track()); + if (!route_ui || !route_ui->midi_track()) { + return; + } + + delete _note_player; + _note_player = new NotePlayer (route_ui->midi_track()); - for (vector >::iterator n = notes.begin(); n != notes.end(); ++n) { - np->add (*n); - } + for (vector >::iterator n = notes.begin(); n != notes.end(); ++n) { + _note_player->add (*n); + } - np->play (); + _note_player->on (); } bool -MidiRegionView::note_in_region_range(const boost::shared_ptr note, bool& visible) const +MidiRegionView::note_in_region_range (const boost::shared_ptr note, bool& visible) const { - const framepos_t note_start_frames = beats_to_frames(note->time()); - - bool outside = (note_start_frames - _region->start() >= _region->length()) || - (note_start_frames < _region->start()); + const framepos_t note_start_frames = source_beats_to_region_frames (note->time()); + bool outside = (note_start_frames < 0) || (note_start_frames > _region->last_frame()); visible = (note->note() >= midi_stream_view()->lowest_note()) && (note->note() <= midi_stream_view()->highest_note()); @@ -1447,25 +1613,21 @@ void MidiRegionView::update_note (CanvasNote* ev, bool update_ghost_regions) { boost::shared_ptr note = ev->note(); - - const framepos_t note_start_frames = beats_to_frames(note->time()); - - /* trim note display to not overlap the end of its region */ - const framepos_t note_end_frames = min (beats_to_frames (note->end_time()), _region->start() + _region->length()); - - const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start()); + const double x = trackview.editor().frame_to_pixel (source_beats_to_region_frames (note->time())); const double y1 = midi_stream_view()->note_to_y(note->note()); - const double note_endpixel = trackview.editor().frame_to_pixel(note_end_frames - _region->start()); ev->property_x1() = x; ev->property_y1() = y1; - + + /* trim note display to not overlap the end of its region */ + if (note->length() > 0) { - ev->property_x2() = note_endpixel; + const framepos_t note_end_frames = min (source_beats_to_region_frames (note->end_time()), _region->length()); + ev->property_x2() = trackview.editor().frame_to_pixel (note_end_frames); } else { - ev->property_x2() = trackview.editor().frame_to_pixel(_region->length()); + ev->property_x2() = trackview.editor().frame_to_pixel (_region->length()); } - + ev->property_y2() = y1 + floor(midi_stream_view()->note_height()); if (note->length() == 0) { @@ -1503,8 +1665,8 @@ MidiRegionView::update_hit (CanvasHit* ev) { boost::shared_ptr note = ev->note(); - const framepos_t note_start_frames = beats_to_frames(note->time()); - const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start()); + const framepos_t note_start_frames = source_beats_to_region_frames(note->time()); + const double x = trackview.editor().frame_to_pixel(note_start_frames); const double diamond_size = midi_stream_view()->note_height() / 2.0; const double y = midi_stream_view()->note_to_y(note->note()) + ((diamond_size-2) / 4.0); @@ -1549,7 +1711,7 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) const double diamond_size = midi_stream_view()->note_height() / 2.0; - CanvasHit* ev_diamond = new CanvasHit(*this, *_note_group, diamond_size, note); + CanvasHit* ev_diamond = new CanvasHit (*this, *_note_group, diamond_size, note); update_hit (ev_diamond); @@ -1562,12 +1724,12 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) if (event) { if (_marked_for_selection.find(note) != _marked_for_selection.end()) { note_selected(event, true); - } + } if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) { event->show_velocity(); } - + event->on_channel_selection_change(_last_channel_selection); _events.push_back(event); @@ -1577,11 +1739,11 @@ MidiRegionView::add_note(const boost::shared_ptr note, bool visible) event->hide (); } } - + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); - view->update_note_range(note->note()); + view->update_note_range (note->note()); } void @@ -1592,55 +1754,61 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity /* potentially extend region to hold new note */ - framepos_t end_frame = _region->position() + beats_to_frames (new_note->end_time()); - framepos_t region_end = _region->position() + _region->length() - 1; + framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time()); + framepos_t region_end = _region->last_frame(); if (end_frame > region_end) { - _region->set_length (end_frame - _region->position(), this); + _region->set_length (end_frame - _region->position()); } - + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); MidiStreamView* const view = mtv->midi_view(); view->update_note_range(new_note->note()); - _marked_for_selection.clear (); - clear_selection (); + _marked_for_selection.clear (); + clear_selection (); start_note_diff_command (_("step add")); note_diff_add_note (new_note, true, false); apply_diff(); - // last_step_edit_note = new_note; + // last_step_edit_note = new_note; } void MidiRegionView::step_sustain (Evoral::MusicalTime beats) { - change_note_lengths (false, false, beats, false, true); + change_note_lengths (false, false, beats, false, true); } +/** Add a new patch change flag to the canvas. + * @param patch the patch change to add + * @param the text to display in the flag + * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel. + */ void -MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext) +MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool active_channel) { assert (patch->time() >= 0); - const double x = trackview.editor().frame_to_pixel (beats_to_frames (patch->time())); + framecnt_t region_frames = source_beats_to_region_frames (patch->time()); + const double x = trackview.editor().frame_to_pixel (region_frames); double const height = midi_stream_view()->contents_height(); boost::shared_ptr patch_change = boost::shared_ptr( new CanvasPatchChange(*this, *_note_group, - displaytext, - height, - x, 1.0, - _model_name, - _custom_device_mode, - patch) - ); + displaytext, + height, + x, 1.0, + instrument_info(), + patch, + active_channel) + ); // Show unless patch change is beyond the region bounds - if (patch->time() - _region->start() >= _region->length() || patch->time() < _region->start()) { + if (region_frames < 0 || region_frames >= _region->length()) { patch_change->hide(); } else { patch_change->show(); @@ -1649,51 +1817,55 @@ MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const _patch_changes.push_back (patch_change); } -void -MidiRegionView::get_patch_key_at (Evoral::MusicalTime time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) +MIDI::Name::PatchPrimaryKey +MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p) +{ + return MIDI::Name::PatchPrimaryKey (p->program(), p->bank()); +} + +void +MidiRegionView::get_patch_key_at (double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) { MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time); while (i != _model->patch_changes().end() && (*i)->channel() != channel) { ++i; } - + if (i != _model->patch_changes().end()) { - key.msb = (*i)->bank_msb (); - key.lsb = (*i)->bank_lsb (); + key.bank_number = (*i)->bank(); key.program_number = (*i)->program (); } else { - key.msb = key.lsb = key.program_number = 0; + key.bank_number = key.program_number = 0; } - + assert (key.is_sane()); } - void MidiRegionView::change_patch_change (CanvasPatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch) { MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); - + if (pc.patch()->program() != new_patch.program_number) { c->change_program (pc.patch (), new_patch.program_number); } - int const new_bank = (new_patch.msb << 7) | new_patch.lsb; + int const new_bank = new_patch.bank_number; if (pc.patch()->bank() != new_bank) { c->change_bank (pc.patch (), new_bank); } _model->apply_command (*trackview.session(), c); - _patch_changes.clear (); - display_patch_changes (); + _patch_changes.clear (); + display_patch_changes (); } void MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange & new_change) { MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change")); - + if (old_change->time() != new_change.time()) { c->change_time (old_change, new_change.time()); } @@ -1701,7 +1873,7 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const if (old_change->channel() != new_change.channel()) { c->change_channel (old_change, new_change.channel()); } - + if (old_change->program() != new_change.program()) { c->change_program (old_change, new_change.program()); } @@ -1712,27 +1884,31 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const _model->apply_command (*trackview.session(), c); - _patch_changes.clear (); - display_patch_changes (); + _patch_changes.clear (); + display_patch_changes (); } /** Add a patch change to the region. * @param t Time in frames relative to region position * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from - * get_channel_for_add()) + * MidiTimeAxisView::get_channel_for_add()) */ void MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange const & patch) { + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change")); c->add (MidiModel::PatchChangePtr ( - new Evoral::PatchChange ( - frames_to_beats (t + midi_region()->start()), get_channel_for_add(), patch.program(), patch.bank() + new Evoral::PatchChange ( + absolute_frames_to_source_beats (_region->position() + t), + mtv->get_channel_for_add(), patch.program(), patch.bank() ) - )); - + ) + ); + _model->apply_command (*trackview.session(), c); - + _patch_changes.clear (); display_patch_changes (); } @@ -1758,39 +1934,59 @@ MidiRegionView::delete_patch_change (CanvasPatchChange* pc) _patch_changes.clear (); display_patch_changes (); } - + void MidiRegionView::previous_patch (CanvasPatchChange& patch) { - if (patch.patch()->program() < 127) { - MIDI::Name::PatchPrimaryKey key; - get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key); - key.program_number++; - change_patch_change (patch, key); - } + if (patch.patch()->program() < 127) { + MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); + key.program_number++; + change_patch_change (patch, key); + } } void MidiRegionView::next_patch (CanvasPatchChange& patch) { - if (patch.patch()->program() > 0) { - MIDI::Name::PatchPrimaryKey key; - get_patch_key_at (patch.patch()->time(), patch.patch()->channel(), key); - key.program_number--; - change_patch_change (patch, key); - } + if (patch.patch()->program() > 0) { + MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); + key.program_number--; + change_patch_change (patch, key); + } +} + +void +MidiRegionView::previous_bank (CanvasPatchChange& patch) +{ + if (patch.patch()->program() < 127) { + MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); + if (key.bank_number > 0) { + key.bank_number--; + change_patch_change (patch, key); + } + } +} + +void +MidiRegionView::next_bank (CanvasPatchChange& patch) +{ + if (patch.patch()->program() > 0) { + MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key (patch.patch()); + if (key.bank_number < 127) { + key.bank_number++; + change_patch_change (patch, key); + } + } } void MidiRegionView::maybe_remove_deleted_note_from_selection (CanvasNoteEvent* cne) { - if (_selection.empty()) { - return; - } - - if (_selection.erase (cne) > 0) { - cerr << "Erased a CNE from selection\n"; - } + if (_selection.empty()) { + return; + } + + _selection.erase (cne); } void @@ -1824,40 +2020,41 @@ MidiRegionView::delete_note (boost::shared_ptr n) } void -MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev) +MidiRegionView::clear_selection_except (ArdourCanvas::CanvasNoteEvent* ev, bool signal) { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - if ((*i)->selected() && (*i) != ev) { - (*i)->set_selected(false); - (*i)->hide_velocity(); - } - } - - _selection.clear(); -} - -void -MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) -{ - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { if ((*i) != ev) { - Selection::iterator tmp = i; ++tmp; (*i)->set_selected (false); + (*i)->hide_velocity (); _selection.erase (i); i = tmp; - } else { ++i; } } - /* don't bother with removing this regionview from the editor selection, - since we're about to add another note, and thus put/keep this - regionview in the editor selection. + /* this does not change the status of this regionview w.r.t the editor + selection. + */ + + if (signal) { + SelectionCleared (this); /* EMIT SIGNAL */ + } +} + +void +MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) +{ + clear_selection_except (ev); + + /* don't bother with checking to see if we should remove this + regionview from the editor selection, since we're about to add + another note, and thus put/keep this regionview in the editor + selection anyway. */ if (!ev->selected()) { @@ -1868,11 +2065,36 @@ MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) void MidiRegionView::select_all_notes () { - clear_selection (); + clear_selection (); + + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + add_to_selection (*i); + } +} + +void +MidiRegionView::select_range (framepos_t start, framepos_t end) +{ + clear_selection (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - add_to_selection (*i); - } + framepos_t t = source_beats_to_absolute_frames((*i)->note()->time()); + if (t >= start && t <= end) { + add_to_selection (*i); + } + } +} + +void +MidiRegionView::invert_selection () +{ + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + if ((*i)->selected()) { + remove_from_selection(*i); + } else { + add_to_selection (*i); + } + } } void @@ -1883,9 +2105,9 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b MidiModel::Notes& notes (_model->notes()); _optimization_iterator = _events.begin(); - if (!add) { - clear_selection (); - } + if (!add) { + clear_selection (); + } if (extend && _selection.empty()) { extend = false; @@ -1908,7 +2130,7 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b high_note = max (high_note, notenum); } - no_sound_notes = true; + _no_sound_notes = true; for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) { @@ -1928,17 +2150,17 @@ MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, b if (select) { if ((cne = find_canvas_note (note)) != 0) { - // extend is false because we've taken care of it, + // 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; + _no_sound_notes = false; } void @@ -1965,10 +2187,14 @@ MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask) } void -MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend) +MidiRegionView::note_selected (ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend) { if (!add) { - clear_selection_except(ev); + clear_selection_except (ev); + if (!_selection.empty()) { + PublicEditor& editor (trackview.editor()); + editor.get_selection().add (this); + } } if (!extend) { @@ -2009,15 +2235,6 @@ MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool add_to_selection (*i); } -#if 0 - /* if events were guaranteed to be time sorted, we could do this. - but as of sept 10th 2009, they no longer are. - */ - - if ((*i)->note()->time() > latest) { - break; - } -#endif } } } @@ -2029,7 +2246,7 @@ MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev) } void -MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2) +MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2, bool extend) { if (x1 > x2) { swap (x1, x2); @@ -2048,9 +2265,9 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2 /* check if any corner of the note is inside the rect Notes: - 1) this is computing "touched by", not "contained by" the rect. - 2) this does not require that events be sorted in time. - */ + 1) this is computing "touched by", not "contained by" the rect. + 2) this does not require that events be sorted in time. + */ const double ix1 = (*i)->x1(); const double ix2 = (*i)->x2(); @@ -2066,7 +2283,39 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2 if (!(*i)->selected()) { add_to_selection (*i); } - } else if ((*i)->selected()) { + } else if ((*i)->selected() && !extend) { + // Not inside rectangle + remove_from_selection (*i); + } + } +} + +void +MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend) +{ + if (y1 > y2) { + swap (y1, y2); + } + + // TODO: Make this faster by storing the last updated selection rect, and only + // adjusting things that are in the area that appears/disappeared. + // We probably need a tree to be able to find events in O(log(n)) time. + + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + + /* check if any corner of the note is inside the rect + + Notes: + 1) this is computing "touched by", not "contained by" the rect. + 2) this does not require that events be sorted in time. + */ + + if (((*i)->y1() >= y1 && (*i)->y1() <= y2)) { + // within y- (note-) range + if (!(*i)->selected()) { + add_to_selection (*i); + } + } else if ((*i)->selected() && !extend) { // Not inside rectangle remove_from_selection (*i); } @@ -2102,8 +2351,8 @@ MidiRegionView::add_to_selection (CanvasNoteEvent* ev) if (_selection.insert (ev).second) { ev->set_selected (true); - play_midi_note ((ev)->note()); - } + start_playing_midi_note ((ev)->note()); + } if (add_mrv_selection) { PublicEditor& editor (trackview.editor()); @@ -2114,44 +2363,44 @@ MidiRegionView::add_to_selection (CanvasNoteEvent* ev) void MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) { - typedef vector > PossibleChord; - PossibleChord to_play; - Evoral::MusicalTime earliest = Evoral::MaxMusicalTime; + typedef vector > PossibleChord; + PossibleChord to_play; + Evoral::MusicalTime earliest = Evoral::MaxMusicalTime; for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - if ((*i)->note()->time() < earliest) { - earliest = (*i)->note()->time(); - } - } + if ((*i)->note()->time() < earliest) { + earliest = (*i)->note()->time(); + } + } for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) { - to_play.push_back ((*i)->note()); - } + if (Evoral::musical_time_equal ((*i)->note()->time(), earliest)) { + to_play.push_back ((*i)->note()); + } (*i)->move_event(dx, dy); - } + } - if (dy && !_selection.empty() && !no_sound_notes && trackview.editor().sound_notes()) { + if (dy && !_selection.empty() && !_no_sound_notes && Config->get_sound_midi_notes()) { - if (to_play.size() > 1) { + if (to_play.size() > 1) { - PossibleChord shifted; + PossibleChord shifted; - for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) { - boost::shared_ptr moved_note (new NoteType (**n)); - moved_note->set_note (moved_note->note() + cumulative_dy); - shifted.push_back (moved_note); - } + for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) { + boost::shared_ptr moved_note (new NoteType (**n)); + moved_note->set_note (moved_note->note() + cumulative_dy); + shifted.push_back (moved_note); + } - play_midi_chord (shifted); + start_playing_midi_chord (shifted); - } else if (!to_play.empty()) { + } else if (!to_play.empty()) { - boost::shared_ptr moved_note (new NoteType (*to_play.front())); - moved_note->set_note (moved_note->note() + cumulative_dy); - play_midi_note (moved_note); - } - } + boost::shared_ptr moved_note (new NoteType (*to_play.front())); + moved_note->set_note (moved_note->note() + cumulative_dy); + start_playing_midi_note (moved_note); + } + } } void @@ -2172,13 +2421,13 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) } /* - cerr << "dnote: " << (int) dnote << endl; - cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note()) - << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl; - cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): " - << int(highest_note_in_selection) << endl; - cerr << "selection size: " << _selection.size() << endl; - cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl; + cerr << "dnote: " << (int) dnote << endl; + cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note()) + << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl; + cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): " + << int(highest_note_in_selection) << endl; + cerr << "selection size: " << _selection.size() << endl; + cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl; */ // Make sure the note pitch does not exceed the MIDI standard range @@ -2189,8 +2438,9 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) start_note_diff_command (_("move notes")); for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { - - Evoral::MusicalTime new_time = frames_to_beats (beats_to_frames ((*i)->note()->time()) + dt); + + framepos_t new_frames = source_beats_to_absolute_frames ((*i)->note()->time()) + dt; + Evoral::MusicalTime new_time = absolute_frames_to_source_beats (new_frames); if (new_time < 0) { continue; @@ -2204,12 +2454,6 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) // keep notes in standard midi range clamp_to_0_127(new_pitch); - // keep original pitch if note is dragged outside valid midi range - if ((original_pitch != 0 && new_pitch == 0) - || (original_pitch != 127 && new_pitch == 127)) { - new_pitch = original_pitch; - } - lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); @@ -2225,6 +2469,9 @@ MidiRegionView::note_dropped(CanvasNoteEvent *, frameoffset_t dt, int8_t dnote) } } +/** @param x Pixel relative to the region position. + * @return Snapped frame relative to the region position. + */ framepos_t MidiRegionView::snap_pixel_to_frame(double x) { @@ -2232,32 +2479,9 @@ MidiRegionView::snap_pixel_to_frame(double x) return snap_frame_to_frame (editor.pixel_to_frame (x)); } -/** Snap a frame offset within our region using the current snap settings. - * @param x Frame offset from this region's position. - * @return Snapped frame offset from this region's position. +/** @param x Pixel relative to the region position. + * @return Snapped pixel relative to the region position. */ -frameoffset_t -MidiRegionView::snap_frame_to_frame (frameoffset_t x) -{ - PublicEditor& editor = trackview.editor(); - - /* x is region relative, convert it to global absolute frames */ - framepos_t const session_frame = x + _region->position(); - - /* try a snap in either direction */ - framepos_t frame = session_frame; - editor.snap_to (frame, 0); - - /* if we went off the beginning of the region, snap forwards */ - if (frame < _region->position ()) { - frame = session_frame; - editor.snap_to (frame, 1); - } - - /* back to region relative */ - return frame - _region->position(); -} - double MidiRegionView::snap_to_pixel(double x) { @@ -2279,15 +2503,37 @@ MidiRegionView::get_end_position_pixels() } framepos_t -MidiRegionView::beats_to_frames(double beats) const +MidiRegionView::source_beats_to_absolute_frames(double beats) const +{ + /* the time converter will return the frame corresponding to `beats' + relative to the start of the source. The start of the source + is an implied position given by region->position - region->start + */ + const framepos_t source_start = _region->position() - _region->start(); + return source_start + _source_relative_time_converter.to (beats); +} + +double +MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const +{ + /* the `frames' argument needs to be converted into a frame count + relative to the start of the source before being passed in to the + converter. + */ + const framepos_t source_start = _region->position() - _region->start(); + return _source_relative_time_converter.from (frames - source_start); +} + +framepos_t +MidiRegionView::region_beats_to_region_frames(double beats) const { - return _time_converter.to(beats); + return _region_relative_time_converter.to(beats); } double -MidiRegionView::frames_to_beats(framepos_t frames) const +MidiRegionView::region_frames_to_region_beats(framepos_t frames) const { - return _time_converter.from(frames); + return _region_relative_time_converter.from(frames); } void @@ -2305,24 +2551,24 @@ MidiRegionView::begin_resizing (bool /*at_front*/) // create a new SimpleRect from the note which will be the resize preview SimpleRect *resize_rect = new SimpleRect( - *_note_group, note->x1(), note->y1(), note->x2(), note->y2()); + *_note_group, note->x1(), note->y1(), note->x2(), note->y2()); // calculate the colors: get the color settings uint32_t fill_color = UINT_RGBA_CHANGE_A( - ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(), - 128); + ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get(), + 128); // make the resize preview notes more transparent and bright fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5); // calculate color based on note velocity resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE( - CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()), - fill_color, - 0.85); + CanvasNoteEvent::meter_style_fill_color(note->note()->velocity(), note->selected()), + fill_color, + 0.85); resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline( - ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get()); + ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get()); resize_data->resize_rect = resize_rect; _resize_data.push_back(resize_data); @@ -2333,7 +2579,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) /** Update resizing notes while user drags. * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode. * @param at_front which end of the note (true == note on, false == note off) - * @param delta_x change in mouse position since the start of the drag + * @param delta_x change in mouse position since the start of the drag * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point @@ -2342,7 +2588,7 @@ MidiRegionView::begin_resizing (bool /*at_front*/) void MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at_front, double delta_x, bool relative) { - bool cursor_set = false; + bool cursor_set = false; for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { SimpleRect* resize_rect = (*i)->resize_rect; @@ -2371,35 +2617,35 @@ MidiRegionView::update_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at resize_rect->property_x1() = canvas_note->x1(); } - if (!cursor_set) { - double beats; + if (!cursor_set) { + double beats; + + beats = snap_pixel_to_frame (current_x); + beats = region_frames_to_region_beats (beats); - beats = snap_pixel_to_frame (current_x); - beats = frames_to_beats (beats); - - double len; + double len; - if (at_front) { - if (beats < canvas_note->note()->end_time()) { - len = canvas_note->note()->time() - beats; - len += canvas_note->note()->length(); - } else { - len = 0; - } - } else { - if (beats >= canvas_note->note()->time()) { - len = beats - canvas_note->note()->time(); - } else { - len = 0; - } - } + if (at_front) { + if (beats < canvas_note->note()->end_time()) { + len = canvas_note->note()->time() - beats; + len += canvas_note->note()->length(); + } else { + len = 0; + } + } else { + if (beats >= canvas_note->note()->time()) { + len = beats - canvas_note->note()->time(); + } else { + len = 0; + } + } - char buf[16]; - snprintf (buf, sizeof (buf), "%.3g beats", len); - show_verbose_cursor (buf, 0, 0); + char buf[16]; + snprintf (buf, sizeof (buf), "%.3g beats", len); + show_verbose_cursor (buf, 0, 0); - cursor_set = true; - } + cursor_set = true; + } } } @@ -2416,6 +2662,11 @@ MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { CanvasNote* canvas_note = (*i)->canvas_note; SimpleRect* resize_rect = (*i)->resize_rect; + + /* Get the new x position for this resize, which is in pixels relative + * to the region position. + */ + double current_x; if (at_front) { @@ -2432,8 +2683,11 @@ MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at } } - current_x = snap_pixel_to_frame (current_x); - current_x = frames_to_beats (current_x); + /* Convert that to a frame within the source */ + current_x = snap_pixel_to_frame (current_x) + _region->start (); + + /* and then to beats */ + current_x = region_frames_to_region_beats (current_x); if (at_front && current_x < canvas_note->note()->end_time()) { note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, current_x); @@ -2465,9 +2719,14 @@ MidiRegionView::commit_resizing (ArdourCanvas::CanvasNoteEvent* primary, bool at } void -MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t channel) +MidiRegionView::abort_resizing () { - note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, (uint8_t) channel); + for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { + delete (*i)->resize_rect; + delete *i; + } + + _resize_data.clear (); } void @@ -2482,8 +2741,8 @@ MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bo new_velocity = velocity; } - event->set_selected (event->selected()); // change color - + event->set_selected (event->selected()); // change color + note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity); } @@ -2513,11 +2772,11 @@ MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_del /* NOTE: the semantics of the two delta arguments are slightly subtle: front_delta: if positive - move the start of the note later in time (shortening it) - if negative - move the start of the note earlier in time (lengthening it) + if negative - move the start of the note earlier in time (lengthening it) end_delta: if positive - move the end of the note later in time (lengthening it) - if negative - move the end of the note earlier in time (shortening it) - */ + if negative - move the end of the note earlier in time (shortening it) + */ if (front_delta) { if (front_delta < 0) { @@ -2574,6 +2833,28 @@ MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_del } } +void +MidiRegionView::change_note_channel (CanvasNoteEvent* event, int8_t chn, bool relative) +{ + uint8_t new_channel; + + if (relative) { + if (chn < 0.0) { + if (event->note()->channel() < -chn) { + new_channel = 0; + } else { + new_channel = event->note()->channel() + chn; + } + } else { + new_channel = event->note()->channel() + chn; + } + } else { + new_channel = (uint8_t) chn; + } + + note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel); +} + void MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative) { @@ -2603,9 +2884,10 @@ MidiRegionView::change_note_length (CanvasNoteEvent* event, Evoral::MusicalTime } void -MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush) +MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together) { int8_t delta; + int8_t value; if (_selection.empty()) { return; @@ -2634,18 +2916,30 @@ MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush) for (Selection::iterator i = _selection.begin(); i != _selection.end();) { Selection::iterator next = i; ++next; - change_note_velocity (*i, delta, true); + + if (all_together) { + if (i == _selection.begin()) { + change_note_velocity (*i, delta, true); + value = (*i)->note()->velocity() + delta; + } else { + change_note_velocity (*i, value, false); + } + + } else { + change_note_velocity (*i, delta, true); + } + i = next; } apply_diff(); - - if (!_selection.empty()) { - char buf[24]; - snprintf (buf, sizeof (buf), "Vel %d", - (int) (*_selection.begin())->note()->velocity()); - show_verbose_cursor (buf, 10, 10); - } + + if (!_selection.empty()) { + char buf[24]; + snprintf (buf, sizeof (buf), "Vel %d", + (int) (*_selection.begin())->note()->velocity()); + show_verbose_cursor (buf, 10, 10); + } } @@ -2697,20 +2991,20 @@ MidiRegionView::transpose (bool up, bool fine, bool allow_smush) void MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::MusicalTime delta, bool start, bool end) { - if (delta == 0.0) { - if (fine) { - delta = 1.0/128.0; - } else { - /* grab the current grid distance */ - bool success; - delta = trackview.editor().get_grid_type_as_beats (success, _region->position()); - if (!success) { - /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */ - cerr << "Grid type not available as beats - TO BE FIXED\n"; - return; - } - } - } + if (delta == 0.0) { + if (fine) { + delta = 1.0/128.0; + } else { + /* grab the current grid distance */ + bool success; + delta = trackview.editor().get_grid_type_as_beats (success, _region->position()); + if (!success) { + /* XXX cannot get grid type as beats ... should always be possible ... FIX ME */ + error << string_compose (_("programming error: %1"), "Grid type not available as beats - TO BE FIXED") << endmsg; + return; + } + } + } if (shorter) { delta = -delta; @@ -2744,12 +3038,12 @@ MidiRegionView::nudge_notes (bool forward) into a vector and sort before using the first one. */ - framepos_t ref_point = _region->position() + beats_to_frames ((*(_selection.begin()))->note()->time()); + framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time()); framepos_t unused; - framepos_t distance; + framecnt_t distance; if (trackview.editor().snap_mode() == Editing::SnapOff) { - + /* grid is off - use nudge distance */ distance = trackview.editor().get_nudge_distance (ref_point, unused); @@ -2762,8 +3056,8 @@ MidiRegionView::nudge_notes (bool forward) if (forward) { if (max_framepos - 1 < next_pos) { - next_pos += 1; - } + next_pos += 1; + } } else { if (next_pos == 0) { return; @@ -2779,7 +3073,7 @@ MidiRegionView::nudge_notes (bool forward) return; } - Evoral::MusicalTime delta = frames_to_beats (fabs (distance)); + Evoral::MusicalTime delta = region_frames_to_region_beats (fabs (distance)); if (!forward) { delta = -delta; @@ -2813,8 +3107,8 @@ void MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev) { Editor* editor = dynamic_cast(&trackview.editor()); - - pre_enter_cursor = editor->get_canvas_cursor (); + + pre_enter_cursor = editor->get_canvas_cursor (); if (_mouse_state == SelectTouchDragging) { note_selected (ev, true); @@ -2834,17 +3128,18 @@ MidiRegionView::note_left (ArdourCanvas::CanvasNoteEvent*) editor->verbose_cursor()->hide (); - if (pre_enter_cursor) { - editor->set_canvas_cursor (pre_enter_cursor); - pre_enter_cursor = 0; - } + if (pre_enter_cursor) { + editor->set_canvas_cursor (pre_enter_cursor); + pre_enter_cursor = 0; + } } void MidiRegionView::patch_entered (ArdourCanvas::CanvasPatchChange* ev) { ostringstream s; - s << ((int) ev->patch()->program() + 1) << ":" << (ev->patch()->bank() + 1); + /* XXX should get patch name if we can */ + s << _("Bank:") << (ev->patch()->bank() + MIDI_BP_ZERO) << '\n' << _("Program:") << ((int) ev->patch()->program()) + MIDI_BP_ZERO << '\n' << _("Channel:") << ((int) ev->patch()->channel() + 1); show_verbose_cursor (s.str(), 10, 20); } @@ -2858,24 +3153,26 @@ void MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor) { Editor* editor = dynamic_cast(&trackview.editor()); - - if (x_fraction > 0.0 && x_fraction < 0.25) { - editor->set_canvas_cursor (editor->cursors()->left_side_trim); - } else if (x_fraction >= 0.75 && x_fraction < 1.0) { - editor->set_canvas_cursor (editor->cursors()->right_side_trim); - } else { - if (pre_enter_cursor && can_set_cursor) { - editor->set_canvas_cursor (pre_enter_cursor); - } - } + Editing::MouseMode mm = editor->current_mouse_mode(); + bool trimmable = (mm == MouseObject || mm == MouseTimeFX || mm == MouseDraw); + + if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) { + editor->set_canvas_cursor (editor->cursors()->left_side_trim); + } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) { + editor->set_canvas_cursor (editor->cursors()->right_side_trim); + } else { + if (pre_enter_cursor && can_set_cursor) { + editor->set_canvas_cursor (pre_enter_cursor); + } + } } void MidiRegionView::set_frame_color() { - uint32_t f; + uint32_t f; - TimeAxisViewItem::set_frame_color (); + TimeAxisViewItem::set_frame_color (); if (!frame) { return; @@ -2888,26 +3185,20 @@ MidiRegionView::set_frame_color() } else { f = fill_color; } - - if (!rect_visible) { - f = UINT_RGBA_CHANGE_A (f, 0); - } - frame->property_fill_color_rgba() = f; + if (!rect_visible) { + f = UINT_RGBA_CHANGE_A (f, 0); + } + + frame->property_fill_color_rgba() = f; } void MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask) { - switch (mode) { - case AllChannels: - case FilterChannels: - _force_channel = -1; - break; - case ForceChannel: - _force_channel = mask; + if (mode == ForceChannel) { mask = 0xFFFF; // Show all notes as active (below) - }; + } // Update notes for selection for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { @@ -2921,10 +3212,8 @@ MidiRegionView::midi_channel_mode_changed(ChannelMode mode, uint16_t mask) } void -MidiRegionView::midi_patch_settings_changed(std::string model, std::string custom_device_mode) +MidiRegionView::instrument_settings_changed () { - _model_name = model; - _custom_device_mode = custom_device_mode; redisplay_model(); } @@ -2938,6 +3227,9 @@ MidiRegionView::cut_copy_clear (Editing::CutCopyOp op) PublicEditor& editor (trackview.editor()); switch (op) { + case Delete: + /* XXX what to do ? */ + break; case Cut: case Copy: editor.get_cut_buffer().add (selection_as_cut_buffer()); @@ -2946,23 +3238,24 @@ MidiRegionView::cut_copy_clear (Editing::CutCopyOp op) break; } - if (op != Copy) { + if (op != Copy) { + + start_note_diff_command(); + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + switch (op) { + case Copy: + break; + case Delete: + case Cut: + case Clear: + note_diff_remove_note (*i); + break; + } + } - start_note_diff_command(); - - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - switch (op) { - case Copy: - break; - case Cut: - case Clear: - note_diff_remove_note (*i); - break; - } - } - - apply_diff(); - } + apply_diff(); + } } MidiCutBuffer* @@ -2971,7 +3264,7 @@ MidiRegionView::selection_as_cut_buffer () const Notes notes; for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - NoteType* n = (*i)->note().get(); + NoteType* n = (*i)->note().get(); notes.insert (boost::shared_ptr (new NoteType (*n))); } @@ -2989,7 +3282,7 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) return; } - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times)); + DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("MIDI paste @ %1 times %2\n", pos, times)); trackview.session()->begin_reversible_command (_("paste")); @@ -3001,17 +3294,17 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) Evoral::MusicalTime end_point = 0; duration = (*mcb.notes().rbegin())->end_time() - (*mcb.notes().begin())->time(); - paste_pos_beats = frames_to_beats (pos - _region->position()); + paste_pos_beats = absolute_frames_to_source_beats (pos); beat_delta = (*mcb.notes().begin())->time() - paste_pos_beats; paste_pos_beats = 0; - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n", - (*mcb.notes().begin())->time(), - (*mcb.notes().rbegin())->end_time(), - duration, pos, _region->position(), - paste_pos_beats, beat_delta)); - - clear_selection (); + DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6 ; beat delta = %7\n", + (*mcb.notes().begin())->time(), + (*mcb.notes().rbegin())->end_time(), + duration, pos, _region->position(), + paste_pos_beats, beat_delta)); + + clear_selection (); for (int n = 0; n < (int) times; ++n) { @@ -3031,27 +3324,27 @@ MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb) /* if we pasted past the current end of the region, extend the region */ - framepos_t end_frame = _region->position() + beats_to_frames (end_point); + framepos_t end_frame = source_beats_to_absolute_frames (end_point); framepos_t region_end = _region->position() + _region->length() - 1; if (end_frame > region_end) { - DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame)); + DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame)); - _region->clear_changes (); - _region->set_length (end_frame, this); + _region->clear_changes (); + _region->set_length (end_frame - _region->position()); trackview.session()->add_command (new StatefulDiffCommand (_region)); } apply_diff (true); - + trackview.session()->commit_reversible_command (); } struct EventNoteTimeEarlyFirstComparator { - bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) { - return a->note()->time() < b->note()->time(); - } + bool operator() (CanvasNoteEvent* a, CanvasNoteEvent* b) { + return a->note()->time() < b->note()->time(); + } }; void @@ -3068,9 +3361,8 @@ MidiRegionView::time_sort_events () } void -MidiRegionView::goto_next_note () +MidiRegionView::goto_next_note (bool add_to_selection) { - // framepos_t pos = -1; bool use_next = false; if (_events.back()->selected()) { @@ -3079,27 +3371,35 @@ MidiRegionView::goto_next_note () time_sort_events (); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + uint16_t const channel_mask = mtv->channel_selector().get_selected_channels (); + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { if ((*i)->selected()) { use_next = true; continue; } else if (use_next) { - unique_select (*i); - // pos = _region->position() + beats_to_frames ((*i)->note()->time()); - return; + if (channel_mask & (1 << (*i)->note()->channel())) { + if (!add_to_selection) { + unique_select (*i); + } else { + note_selected (*i, true, false); + } + return; + } } } /* use the first one */ - unique_select (_events.front()); - + if (!_events.empty() && (channel_mask & (1 << _events.front()->note()->channel ()))) { + unique_select (_events.front()); + } } void -MidiRegionView::goto_previous_note () +MidiRegionView::goto_previous_note (bool add_to_selection) { - // framepos_t pos = -1; bool use_next = false; if (_events.front()->selected()) { @@ -3108,39 +3408,49 @@ MidiRegionView::goto_previous_note () time_sort_events (); + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + uint16_t const channel_mask = mtv->channel_selector().get_selected_channels (); + for (Events::reverse_iterator i = _events.rbegin(); i != _events.rend(); ++i) { if ((*i)->selected()) { use_next = true; continue; } else if (use_next) { - unique_select (*i); - // pos = _region->position() + beats_to_frames ((*i)->note()->time()); - return; + if (channel_mask & (1 << (*i)->note()->channel())) { + if (!add_to_selection) { + unique_select (*i); + } else { + note_selected (*i, true, false); + } + return; + } } } /* use the last one */ - unique_select (*(_events.rbegin())); + if (!_events.empty() && (channel_mask & (1 << (*_events.rbegin())->note()->channel ()))) { + unique_select (*(_events.rbegin())); + } } void MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected) { - bool had_selected = false; + bool had_selected = false; time_sort_events (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { if ((*i)->selected()) { selected.insert ((*i)->note()); - had_selected = true; + had_selected = true; } } - - if (allow_all_if_none_selected && !had_selected) { - for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - selected.insert ((*i)->note()); + + if (allow_all_if_none_selected && !had_selected) { + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { + selected.insert ((*i)->note()); } } } @@ -3148,24 +3458,36 @@ MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_s void MidiRegionView::update_ghost_note (double x, double y) { + MidiTimeAxisView* const mtv = dynamic_cast(&trackview); + _last_ghost_x = x; _last_ghost_y = y; - + _note_group->w2i (x, y); - framepos_t const f = snap_pixel_to_frame (x); + PublicEditor& editor = trackview.editor (); + + framepos_t const unsnapped_frame = editor.pixel_to_frame (x); + framecnt_t grid_frames; + framepos_t const f = snap_frame_to_grid_underneath (unsnapped_frame, grid_frames); + + /* use region_frames... because we are converting a delta within the region + */ + bool success; - Evoral::MusicalTime beats = trackview.editor().get_grid_type_as_beats (success, f); + double length = editor.get_grid_type_as_beats (success, unsnapped_frame); if (!success) { - beats = 1; + length = 1; } - - double length = frames_to_beats (snap_frame_to_frame (f + beats_to_frames (beats)) - f); - - _ghost_note->note()->set_time (frames_to_beats (f + _region->start())); + + /* note that this sets the time of the ghost note in beats relative to + the start of the source; that is how all note times are stored. + */ + _ghost_note->note()->set_time (absolute_frames_to_source_beats (f + _region->position ())); _ghost_note->note()->set_length (length); _ghost_note->note()->set_note (midi_stream_view()->y_to_note (y)); + _ghost_note->note()->set_channel (mtv->get_channel_for_add ()); /* the ghost note does not appear in ghost regions, so pass false in here */ update_note (_ghost_note, false); @@ -3176,8 +3498,7 @@ MidiRegionView::update_ghost_note (double x, double y) void MidiRegionView::create_ghost_note (double x, double y) { - delete _ghost_note; - _ghost_note = 0; + remove_ghost_note (); boost::shared_ptr g (new NoteType); _ghost_note = new NoEventCanvasNote (*this, *_note_group, g); @@ -3197,32 +3518,32 @@ MidiRegionView::snap_changed () if (!_ghost_note) { return; } - + create_ghost_note (_last_ghost_x, _last_ghost_y); } void MidiRegionView::drop_down_keys () { - _mouse_state = None; + _mouse_state = None; } void MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y) { double note = midi_stream_view()->y_to_note(y); - Events e; + Events e; MidiTimeAxisView* const mtv = dynamic_cast(&trackview); uint16_t chn_mask = mtv->channel_selector().get_selected_channels(); - if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { - get_events (e, Evoral::Sequence::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask); - } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - get_events (e, Evoral::Sequence::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask); - } else { - return; - } + if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + get_events (e, Evoral::Sequence::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask); + } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + get_events (e, Evoral::Sequence::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask); + } else { + return; + } bool add_mrv_selection = false; @@ -3230,28 +3551,28 @@ MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, doub add_mrv_selection = true; } - for (Events::iterator i = e.begin(); i != e.end(); ++i) { - if (_selection.insert (*i).second) { - (*i)->set_selected (true); - } + for (Events::iterator i = e.begin(); i != e.end(); ++i) { + if (_selection.insert (*i).second) { + (*i)->set_selected (true); + } } if (add_mrv_selection) { PublicEditor& editor (trackview.editor()); editor.get_selection().add (this); } -} +} void MidiRegionView::color_handler () { - RegionView::color_handler (); + RegionView::color_handler (); for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { - (*i)->set_selected ((*i)->selected()); // will change color - } + (*i)->set_selected ((*i)->selected()); // will change color + } - /* XXX probably more to do here */ + /* XXX probably more to do here */ } void @@ -3266,62 +3587,61 @@ MidiRegionView::enable_display (bool yn) void MidiRegionView::show_step_edit_cursor (Evoral::MusicalTime pos) { - if (_step_edit_cursor == 0) { - ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); + if (_step_edit_cursor == 0) { + ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); - _step_edit_cursor = new ArdourCanvas::SimpleRect (*group); - _step_edit_cursor->property_y1() = 0; - _step_edit_cursor->property_y2() = midi_stream_view()->contents_height(); - _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90); - _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90); - } + _step_edit_cursor = new ArdourCanvas::SimpleRect (*group); + _step_edit_cursor->property_y1() = 0; + _step_edit_cursor->property_y2() = midi_stream_view()->contents_height(); + _step_edit_cursor->property_fill_color_rgba() = RGBA_TO_UINT (45,0,0,90); + _step_edit_cursor->property_outline_color_rgba() = RGBA_TO_UINT (85,0,0,90); + } - move_step_edit_cursor (pos); - _step_edit_cursor->show (); + move_step_edit_cursor (pos); + _step_edit_cursor->show (); } void MidiRegionView::move_step_edit_cursor (Evoral::MusicalTime pos) { - _step_edit_cursor_position = pos; + _step_edit_cursor_position = pos; - if (_step_edit_cursor) { - double pixel = trackview.editor().frame_to_pixel (beats_to_frames (pos)); - _step_edit_cursor->property_x1() = pixel; - set_step_edit_cursor_width (_step_edit_cursor_width); - } + if (_step_edit_cursor) { + double pixel = trackview.editor().frame_to_pixel (region_beats_to_region_frames (pos)); + _step_edit_cursor->property_x1() = pixel; + set_step_edit_cursor_width (_step_edit_cursor_width); + } } void MidiRegionView::hide_step_edit_cursor () { - if (_step_edit_cursor) { - _step_edit_cursor->hide (); - } + if (_step_edit_cursor) { + _step_edit_cursor->hide (); + } } void MidiRegionView::set_step_edit_cursor_width (Evoral::MusicalTime beats) { - _step_edit_cursor_width = beats; + _step_edit_cursor_width = beats; - if (_step_edit_cursor) { - _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (beats_to_frames (beats)); - } + if (_step_edit_cursor) { + _step_edit_cursor->property_x2() = _step_edit_cursor->property_x1() + trackview.editor().frame_to_pixel (region_beats_to_region_frames (beats)); + } } /** Called when a diskstream on our track has received some data. Update the view, if applicable. - * @param buf Data that has been recorded. - * @param w Source that this data will end up in. + * @param w Source that the data will end up in. */ void -MidiRegionView::data_recorded (boost::shared_ptr buf, boost::weak_ptr w) +MidiRegionView::data_recorded (boost::weak_ptr w) { if (!_active_notes) { /* we aren't actively being recorded to */ return; } - + boost::shared_ptr src = w.lock (); if (!src || src != midi_region()->midi_source()) { /* recorded data was not destined for our source */ @@ -3329,21 +3649,29 @@ MidiRegionView::data_recorded (boost::shared_ptr buf, boost::weak_pt } MidiTimeAxisView* mtv = dynamic_cast (&trackview); + + boost::shared_ptr buf = mtv->midi_track()->get_gui_feed_buffer (); + BeatsFramesConverter converter (trackview.session()->tempo_map(), mtv->midi_track()->get_capture_start_frame (0)); framepos_t back = max_framepos; - + for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) { Evoral::MIDIEvent const ev (*i, false); assert (ev.buffer ()); + /* ev.time() is in session frames, so (ev.time() - converter.origin_b()) is + frames from the start of the source, and so time_beats is in terms of the + source. + */ + Evoral::MusicalTime const time_beats = converter.from (ev.time () - converter.origin_b ()); if (ev.type() == MIDI_CMD_NOTE_ON) { boost::shared_ptr note ( new NoteType (ev.channel(), time_beats, 0, ev.note(), ev.velocity()) - ); + ); add_note (note, true); @@ -3353,7 +3681,7 @@ MidiRegionView::data_recorded (boost::shared_ptr buf, boost::weak_pt } else if (ev.note() > _current_range_max) { midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true); } - + } else if (ev.type() == MIDI_CMD_NOTE_OFF) { resolve_note (ev.note (), time_beats); } @@ -3388,39 +3716,10 @@ MidiRegionView::trim_front_ending () } } -/** @return channel (counted from 0) to add an event to, based on the current setting - * of the channel selector. - */ -uint8_t -MidiRegionView::get_channel_for_add () const -{ - MidiTimeAxisView* const mtv = dynamic_cast(&trackview); - uint16_t const chn_mask = mtv->channel_selector().get_selected_channels(); - int chn_cnt = 0; - uint8_t channel = 0; - - /* pick the highest selected channel, unless all channels are selected, - which is interpreted to mean channel 1 (zero) - */ - - for (uint16_t i = 0; i < 16; ++i) { - if (chn_mask & (1<patch (), Gtk::Stock::APPLY); + PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY); if (d.run () != Gtk::RESPONSE_ACCEPT) { return; } @@ -3433,12 +3732,12 @@ void MidiRegionView::show_verbose_cursor (boost::shared_ptr n) const { char buf[24]; - snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d", - Evoral::midi_note_name (n->note()).c_str(), - (int) n->note (), - (int) n->channel() + 1, - (int) n->velocity()); - + snprintf (buf, sizeof (buf), "%s (%d) Chn %d\nVel %d", + Evoral::midi_note_name (n->note()).c_str(), + (int) n->note (), + (int) n->channel() + 1, + (int) n->velocity()); + show_verbose_cursor (buf, 10, 20); } @@ -3452,6 +3751,64 @@ MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double wx += xoffset; wy += yoffset; + /* Flip the cursor above the mouse pointer if it would overlap the bottom of the canvas */ + + double x1, y1, x2, y2; + trackview.editor().verbose_cursor()->canvas_item()->get_bounds (x1, y1, x2, y2); + + if ((wy + y2 - y1) > trackview.editor().canvas_height()) { + wy -= (y2 - y1) + 2 * yoffset; + } + trackview.editor().verbose_cursor()->set (text, wx, wy); trackview.editor().verbose_cursor()->show (); } + +/** @param p A session framepos. + * @param grid_frames Filled in with the number of frames that a grid interval is at p. + * @return p snapped to the grid subdivision underneath it. + */ +framepos_t +MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, framecnt_t& grid_frames) const +{ + PublicEditor& editor = trackview.editor (); + + bool success; + Evoral::MusicalTime grid_beats = editor.get_grid_type_as_beats (success, p); + + if (!success) { + grid_beats = 1; + } + + grid_frames = region_beats_to_region_frames (grid_beats); + + /* Hack so that we always snap to the note that we are over, instead of snapping + to the next one if we're more than halfway through the one we're over. + */ + if (editor.snap_mode() == SnapNormal && p >= grid_frames / 2) { + p -= grid_frames / 2; + } + + return snap_frame_to_frame (p); +} + +/** Called when the selection has been cleared in any MidiRegionView. + * @param rv MidiRegionView that the selection was cleared in. + */ +void +MidiRegionView::selection_cleared (MidiRegionView* rv) +{ + if (rv == this) { + return; + } + + /* Clear our selection in sympathy; but don't signal the fact */ + clear_selection (false); +} + +void +MidiRegionView::note_button_release () +{ + delete _note_player; + _note_player = 0; +}