X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fmidi_region_view.cc;h=215f7c34da5fd6d769eea5d0b7b509b0eeb67053;hb=e57c4d4c43c0d302c0b06128e0ba10c4683ee8fe;hp=c81edd4e7b269ca794a5a5398570fe4c61027e9f;hpb=5e7ae490c1616696a733287b62c08416726653ae;p=ardour.git diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index c81edd4e7b..215f7c34da 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -27,34 +28,36 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include "ardour/playlist.h" +#include "ardour/tempo.h" +#include "ardour/midi_region.h" +#include "ardour/midi_source.h" +#include "ardour/midi_diskstream.h" +#include "ardour/midi_model.h" +#include "ardour/midi_patch_manager.h" -#include -#include +#include "evoral/Parameter.hpp" +#include "evoral/Control.hpp" -#include "streamview.h" -#include "midi_region_view.h" -#include "midi_streamview.h" -#include "midi_time_axis.h" -#include "simpleline.h" +#include "automation_region_view.h" +#include "automation_time_axis.h" #include "canvas-hit.h" #include "canvas-note.h" #include "canvas-program-change.h" -#include "public_editor.h" #include "ghostregion.h" -#include "midi_time_axis.h" -#include "automation_time_axis.h" -#include "automation_region_view.h" -#include "utils.h" -#include "midi_util.h" #include "gui_thread.h" #include "keyboard.h" +#include "midi_cut_buffer.h" +#include "midi_region_view.h" +#include "midi_streamview.h" +#include "midi_time_axis.h" +#include "midi_time_axis.h" +#include "midi_util.h" +#include "public_editor.h" +#include "selection.h" +#include "simpleline.h" +#include "streamview.h" +#include "utils.h" #include "i18n.h" @@ -64,11 +67,12 @@ using namespace PBD; using namespace Editing; using namespace ArdourCanvas; -MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, Gdk::Color& basic_color) +MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, + 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) - , _default_note_length(0.0) + , _default_note_length(1.0) , _current_range_min(0) , _current_range_max(0) , _model_name(string()) @@ -82,11 +86,13 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & _note_group->raise_to_top(); } -MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility) +MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, + 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) - , _default_note_length(0.0) + , _default_note_length(1.0) , _model_name(string()) , _custom_device_mode(string()) , _active_notes(0) @@ -101,10 +107,11 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & MidiRegionView::MidiRegionView (const MidiRegionView& other) - : RegionView (other) + : sigc::trackable(other) + , RegionView (other) , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _default_note_length(0.0) + , _default_note_length(1.0) , _model_name(string()) , _custom_device_mode(string()) , _active_notes(0) @@ -122,11 +129,11 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) init (c, false); } -MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr other_region) - : RegionView (other, boost::shared_ptr (other_region)) +MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr region) + : RegionView (other, boost::shared_ptr (region)) , _force_channel(-1) , _last_channel_selection(0xFFFF) - , _default_note_length(0.0) + , _default_note_length(1.0) , _model_name(string()) , _custom_device_mode(string()) , _active_notes(0) @@ -145,15 +152,11 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptrmidi_source(0)->load_model(); - - const Meter& m = trackview.session().tempo_map().meter_at(_region->position()); - const Tempo& t = trackview.session().tempo_map().tempo_at(_region->position()); - _default_note_length = m.frames_per_bar(t, trackview.session().frame_rate()) - / m.beats_per_bar(); + } _model = midi_region()->midi_source(0)->model(); _enable_display = false; @@ -170,7 +173,6 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd) region_locked (); reset_width_dependent_items (_pixel_width); - //reset_width_dependent_items ((double) _region->length() / samples_per_unit); set_colors (); @@ -195,8 +197,11 @@ MidiRegionView::init (Gdk::Color& basic_color, bool wfd) bool MidiRegionView::canvas_event(GdkEvent* ev) { - static bool delete_mod = false; - static Editing::MidiEditMode original_mode; + PublicEditor& editor (trackview.editor()); + + if (!editor.internal_editing()) { + return false; + } static double drag_start_x, drag_start_y; static double last_x, last_y; @@ -205,21 +210,9 @@ MidiRegionView::canvas_event(GdkEvent* ev) static ArdourCanvas::SimpleRect* drag_rect = NULL; - if (trackview.editor.current_mouse_mode() != MouseNote) - return false; - - // Mmmm, spaghetti - switch (ev->type) { case GDK_KEY_PRESS: - if (ev->key.keyval == GDK_Delete && !delete_mod) { - delete_mod = true; - original_mode = trackview.editor.current_midi_edit_mode(); - trackview.editor.set_midi_edit_mode(MidiEditErase); - start_delta_command(_("erase notes")); - _mouse_state = EraseTouchDragging; - return true; - } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) { + if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) { _mouse_state = SelectTouchDragging; return true; } else if (ev->key.keyval == GDK_Escape) { @@ -230,15 +223,8 @@ MidiRegionView::canvas_event(GdkEvent* ev) case GDK_KEY_RELEASE: if (ev->key.keyval == GDK_Delete) { - if (_mouse_state == EraseTouchDragging) { - delete_selection(); - apply_command(); - } - if (delete_mod) { - trackview.editor.set_midi_edit_mode(original_mode); - _mouse_state = None; - delete_mod = false; - } + delete_selection(); + apply_command(); return true; } else if (ev->key.keyval == GDK_Shift_L || ev->key.keyval == GDK_Control_L) { _mouse_state = None; @@ -247,9 +233,7 @@ MidiRegionView::canvas_event(GdkEvent* ev) return false; case GDK_BUTTON_PRESS: - if (_mouse_state != SelectTouchDragging && - _mouse_state != EraseTouchDragging && - ev->button.button == 1) { + if (_mouse_state != SelectTouchDragging && ev->button.button == 1) { _pressed_button = ev->button.button; _mouse_state = Pressed; return true; @@ -272,8 +256,8 @@ MidiRegionView::canvas_event(GdkEvent* ev) group->w2i(event_x, event_y); // convert event_x to global frame - event_frame = trackview.editor.pixel_to_frame(event_x) + _region->position(); - trackview.editor.snap_to(event_frame); + event_frame = trackview.editor().pixel_to_frame(event_x) + _region->position(); + trackview.editor().snap_to(event_frame); // convert event_frame back to local coordinates relative to position event_frame -= _region->position(); @@ -281,7 +265,7 @@ MidiRegionView::canvas_event(GdkEvent* ev) case Pressed: // Drag start // Select drag start - if (_pressed_button == 1 && trackview.editor.current_midi_edit_mode() == MidiEditSelect) { + if (_pressed_button == 1 && editor.current_mouse_mode() == MouseRange) { group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, Gdk::Cursor(Gdk::FLEUR), ev->motion.time); last_x = event_x; @@ -304,7 +288,7 @@ MidiRegionView::canvas_event(GdkEvent* ev) return true; // Add note drag start - } else if (trackview.editor.current_midi_edit_mode() == MidiEditPencil) { + } else if (editor.current_mouse_mode() == MouseObject) { group->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, Gdk::Cursor(Gdk::FLEUR), ev->motion.time); last_x = event_x; @@ -313,15 +297,16 @@ MidiRegionView::canvas_event(GdkEvent* ev) 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_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_y1() = midi_stream_view()->note_to_y( + midi_stream_view()->y_to_note(event_y)); drag_rect->property_x2() = event_x; - drag_rect->property_y2() = drag_rect->property_y1() + floor(midi_stream_view()->note_height()); + 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; + drag_rect->property_fill_color_rgba() = 0xFFFFFF66; _mouse_state = AddDragging; return true; @@ -341,7 +326,7 @@ MidiRegionView::canvas_event(GdkEvent* ev) } if (_mouse_state == AddDragging) - event_x = trackview.editor.frame_to_pixel(event_frame); + event_x = trackview.editor().frame_to_pixel(event_frame); if (drag_rect) { if (event_x > drag_start_x) @@ -362,7 +347,6 @@ MidiRegionView::canvas_event(GdkEvent* ev) last_x = event_x; last_y = event_y; - case EraseTouchDragging: case SelectTouchDragging: return false; @@ -376,7 +360,7 @@ MidiRegionView::canvas_event(GdkEvent* ev) event_y = ev->motion.y; group->w2i(event_x, event_y); group->ungrab(ev->button.time); - event_frame = trackview.editor.pixel_to_frame(event_x); + event_frame = trackview.editor().pixel_to_frame(event_x); if (_pressed_button != 1) { return false; @@ -384,12 +368,12 @@ MidiRegionView::canvas_event(GdkEvent* ev) switch (_mouse_state) { case Pressed: // Clicked - switch (trackview.editor.current_midi_edit_mode()) { - case MidiEditSelect: - case MidiEditResize: + switch (editor.current_mouse_mode()) { + case MouseRange: + case MouseTimeFX: clear_selection(); break; - case MidiEditPencil: + case MouseObject: create_note_at(event_x, event_y, _default_note_length); default: break; } @@ -404,10 +388,10 @@ MidiRegionView::canvas_event(GdkEvent* ev) _mouse_state = None; 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( + const double length = trackview.editor().pixel_to_frame( drag_rect->property_x2() - drag_rect->property_x1()); - create_note_at(x, drag_rect->property_y1(), length); + create_note_at(x, drag_rect->property_y1(), frames_to_beats(length)); } delete drag_rect; @@ -422,7 +406,10 @@ MidiRegionView::canvas_event(GdkEvent* ev) } -/** Add a note to the model, and the view, at a canvas (click) coordinate */ +/** Add a note to the model, and the view, at a canvas (click) coordinate. + * \param x horizontal position in pixels + * \param y vertical position in pixels + * \param length duration of the note in beats */ void MidiRegionView::create_note_at(double x, double y, double length) { @@ -434,29 +421,18 @@ MidiRegionView::create_note_at(double x, double y, double length) assert(note >= 0.0); assert(note <= 127.0); - nframes64_t new_note_time = trackview.editor.pixel_to_frame (x); - assert(new_note_time >= 0); - new_note_time += _region->start(); + // Start of note in frames relative to region start + nframes64_t start_frames = snap_frame_to_frame(trackview.editor().pixel_to_frame(x)); + assert(start_frames >= 0); - /* - const Meter& m = trackview.session().tempo_map().meter_at(new_note_time); - const Tempo& t = trackview.session().tempo_map().tempo_at(new_note_time); - double length = m.frames_per_bar(t, trackview.session().frame_rate()) / m.beats_per_bar(); - */ - - // we need to snap here again in nframes64_t in order to be sample accurate - // since note time is region-absolute but snap_to_frame expects position-relative - // time we have to coordinate transform back and forth here. - nframes64_t new_note_time_position_relative = new_note_time - _region->start(); - new_note_time = snap_to_frame(new_note_time_position_relative) + _region->start(); - - // we need to snap the length too to be sample accurate - nframes64_t new_note_length = nframes_t(length); - new_note_length = snap_to_frame(new_note_time_position_relative + new_note_length) + _region->start() - - new_note_time; + // Snap length + length = frames_to_beats( + snap_frame_to_frame(start_frames + beats_to_frames(length)) - start_frames); + + const boost::shared_ptr new_note(new NoteType(0, + frames_to_beats(start_frames + _region->start()), length, + (uint8_t)note, 0x40)); - const boost::shared_ptr new_note(new Evoral::Note( - 0, new_note_time, new_note_length, (uint8_t)note, 0x40)); view->update_note_range(new_note->note()); MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note"); @@ -477,11 +453,13 @@ MidiRegionView::clear_events() } } - for (Events::iterator i = _events.begin(); i != _events.end(); ++i) + for (Events::iterator i = _events.begin(); i != _events.end(); ++i) { delete *i; + } _events.clear(); _pgm_changes.clear(); + _sys_exes.clear(); } @@ -489,27 +467,32 @@ void MidiRegionView::display_model(boost::shared_ptr model) { _model = model; - - if (_enable_display) + if (_enable_display) { redisplay_model(); + } } void MidiRegionView::start_delta_command(string name) { - if (!_delta_command) + if (!_delta_command) { _delta_command = _model->new_delta_command(name); + } } void -MidiRegionView::command_add_note(const boost::shared_ptr note, bool selected) +MidiRegionView::command_add_note(const boost::shared_ptr note, bool selected, bool show_velocity) { - if (_delta_command) + if (_delta_command) { _delta_command->add(note); - - if (selected) + } + if (selected) { _marked_for_selection.insert(note); + } + if (show_velocity) { + _marked_for_velocity.insert(note); + } } void @@ -537,6 +520,7 @@ MidiRegionView::apply_command() midi_view()->midi_track()->diskstream()->playlist_modified(); _marked_for_selection.clear(); + _marked_for_velocity.clear(); } @@ -553,131 +537,116 @@ void MidiRegionView::redisplay_model() { // Don't redisplay the model if we're currently recording and displaying that - if (_active_notes) + if (_active_notes) { return; + } if (_model) { - clear_events(); _model->read_lock(); - MidiModel::Notes notes = _model->notes(); - /* - cerr << endl << _model->midi_source()->name() << " : redisplaying " << notes.size() << " notes:" << endl; - for (MidiModel::Notes::iterator i = notes.begin(); i != notes.end(); ++i) { - cerr << "NOTE time: " << (*i)->time() - << " pitch: " << int((*i)->note()) - << " length: " << (*i)->length() - << " end-time: " << (*i)->end_time() - << " velocity: " << int((*i)->velocity()) - << endl; - } - */ for (size_t i = 0; i < _model->n_notes(); ++i) { add_note(_model->note_at(i)); } - find_and_insert_program_change_flags(); + display_sysexes(); - // Is this necessary? - /*for (Automatable::Controls::const_iterator i = _model->controls().begin(); - i != _model->controls().end(); ++i) { + display_program_changes(); - assert(i->second); + _model->read_unlock(); - boost::shared_ptr at - = midi_view()->automation_child(i->second->parameter()); - if (!at) - continue; + } else { + cerr << "MidiRegionView::redisplay_model called without a model" << endmsg; + } +} - Gdk::Color col = midi_stream_view()->get_region_color(); +void +MidiRegionView::display_program_changes() +{ + boost::shared_ptr control = _model->control(MidiPgmChangeAutomation); + if (!control) { + return; + } - boost::shared_ptr arv; + Glib::Mutex::Lock lock (control->list()->lock()); - { - Glib::Mutex::Lock list_lock (i->second->list()->lock()); + uint8_t channel = control->parameter().channel(); - arv = boost::shared_ptr( - new AutomationRegionView(at->canvas_display, - *at.get(), _region, i->second->list(), - midi_stream_view()->get_samples_per_unit(), col)); - } + for (AutomationList::const_iterator event = control->list()->begin(); + event != control->list()->end(); ++event) { + double event_time = (*event)->when; + double program_number = floor((*event)->value + 0.5); - arv->set_duration(_region->length(), this); - arv->init(col, true); + // Get current value of bank select MSB at time of the program change + Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); + boost::shared_ptr msb_control = _model->control(bank_select_msb); + uint8_t msb = 0; + if (msb_control != 0) { + msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5)); + } - _automation_children.insert(std::make_pair(i->second->parameter(), arv)); - }*/ - _model->read_unlock(); + // Get current value of bank select LSB at time of the program change + Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK); + boost::shared_ptr lsb_control = _model->control(bank_select_lsb); + uint8_t lsb = 0; + if (lsb_control != 0) { + lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5)); + } - } else { - cerr << "MidiRegionView::redisplay_model called without a model" << endmsg; + MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number); + + boost::shared_ptr patch = + MIDI::Name::MidiPatchManager::instance().find_patch( + _model_name, _custom_device_mode, channel, patch_key); + + PCEvent program_change(event_time, uint8_t(program_number), channel); + + if (patch != 0) { + add_pgm_change(program_change, patch->name()); + } else { + char buf[4]; + snprintf(buf, 4, "%d", int(program_number)); + add_pgm_change(program_change, buf); + } } } -void -MidiRegionView::find_and_insert_program_change_flags() +void +MidiRegionView::display_sysexes() { - // Draw program change 'flags' - for (Automatable::Controls::iterator control = _model->controls().begin(); - control != _model->controls().end(); ++control) { - if (control->first.type() == MidiPgmChangeAutomation) { - Glib::Mutex::Lock list_lock (control->second->list()->lock()); - - uint8_t channel = control->first.channel(); - - for (AutomationList::const_iterator event = control->second->list()->begin(); - event != control->second->list()->end(); ++event) { - double event_time = (*event)->when; - double program_number = floor((*event)->value + 0.5); - - //cerr << " got program change on channel " << int(channel) << " time: " << event_time << " number: " << program_number << endl; - - // find bank select msb and lsb for the program change - Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); - boost::shared_ptr msb_control = _model->control(bank_select_msb); - uint8_t msb = 0; - if (msb_control != 0) { - msb = uint8_t(floor(msb_control->get_float(true, event_time) + 0.5)); - } - - Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK); - boost::shared_ptr lsb_control = _model->control(bank_select_lsb); - uint8_t lsb = 0; - if (lsb_control != 0) { - lsb = uint8_t(floor(lsb_control->get_float(true, event_time) + 0.5)); - } - - //cerr << " got msb " << int(msb) << " and lsb " << int(lsb) << " thread_id: " << pthread_self() << endl; - - MIDI::Name::PatchPrimaryKey patch_key(msb, lsb, program_number); - - boost::shared_ptr patch = - MIDI::Name::MidiPatchManager::instance().find_patch( - _model_name, - _custom_device_mode, - channel, - patch_key - ); - - ControlEvent program_change(nframes_t(event_time), uint8_t(program_number), channel); - - if (patch != 0) { - //cerr << " got patch with name " << patch->name() << " number " << patch->number() << endl; - add_pgm_change(program_change, patch->name()); - } else { - char buf[4]; - snprintf(buf, 4, "%d", int(program_number)); - add_pgm_change(program_change, buf); - } + for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) { + ARDOUR::MidiModel::TimeType time = (*i)->time(); + assert(time >= 0); + + ostringstream str; + str << hex; + for (uint32_t b = 0; b < (*i)->size(); ++b) { + str << int((*i)->buffer()[b]); + if (b != (*i)->size() -1) { + str << " "; } - break; - } else if (control->first.type() == MidiCCAutomation) { - //cerr << " found CC Automation of channel " << int(control->first.channel()) << " and id " << control->first.id() << endl; } - } + string text = str.str(); + + ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); + const double x = trackview.editor().frame_to_pixel(beats_to_frames(time)); + + double height = midi_stream_view()->contents_height(); + + boost::shared_ptr sysex = boost::shared_ptr( + new CanvasSysEx(*this, *group, text, height, x, 1.0)); + + // Show unless program change is beyond the region bounds + if (time - _region->start() >= _region->length() || time < _region->start()) { + sysex->hide(); + } else { + sysex->show(); + } + + _sys_exes.push_back(sysex); + } } @@ -697,15 +666,16 @@ MidiRegionView::~MidiRegionView () delete _delta_command; } - void MidiRegionView::region_resized (Change what_changed) { RegionView::region_resized(what_changed); if (what_changed & ARDOUR::PositionChanged) { - if (_enable_display) + set_duration(_region->length(), 0); + if (_enable_display) { redisplay_model(); + } } } @@ -715,14 +685,15 @@ MidiRegionView::reset_width_dependent_items (double pixel_width) RegionView::reset_width_dependent_items(pixel_width); assert(_pixel_width == pixel_width); - if (_enable_display) + if (_enable_display) { redisplay_model(); + } } void -MidiRegionView::set_height (gdouble height) +MidiRegionView::set_height (double height) { - static const double FUDGE = 2; + static const double FUDGE = 2.0; const double old_height = _height; RegionView::set_height(height); _height = height - FUDGE; @@ -731,8 +702,8 @@ MidiRegionView::set_height (gdouble height) midi_stream_view()->highest_note(), height != old_height + FUDGE); - if (name_text) { - name_text->raise_to_top(); + if (name_pixbuf) { + name_pixbuf->raise_to_top(); } } @@ -743,75 +714,70 @@ MidiRegionView::set_height (gdouble height) void MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force) { - if (_enable_display) { - if (!force && _current_range_min == min && _current_range_max == max) { - return; - } - - _current_range_min = min; - _current_range_max = max; - - for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) { - CanvasNoteEvent* event = *i; - Item* item = dynamic_cast(event); - assert(item); - if (event && event->note()) { - if (event->note()->note() < _current_range_min || event->note()->note() > _current_range_max) { - if (canvas_item_visible(item)) { - item->hide(); - } - } else { - if (!canvas_item_visible(item)) { - item->show(); - } - - event->hide_velocity(); - if (CanvasNote* note = dynamic_cast(event)) { - const double y1 = midi_stream_view()->note_to_y(event->note()->note()); - const double y2 = y1 + floor(midi_stream_view()->note_height()); - - note->property_y1() = y1; - note->property_y2() = y2; - } else if (CanvasHit* hit = dynamic_cast(event)) { - double x = trackview.editor.frame_to_pixel((nframes64_t) - event->note()->time() - _region->start()); - const double diamond_size = midi_stream_view()->note_height() / 2.0; - double y = midi_stream_view()->note_to_y(event->note()->note()) - + ((diamond_size-2.0) / 4.0); - - hit->set_height(diamond_size); - hit->move(x-hit->x1(), y-hit->y1()); - hit->show(); - } - if (event->selected()) { - event->show_velocity(); - } + if (!_enable_display) { + return; + } + + if (!force && _current_range_min == min && _current_range_max == max) { + return; + } + + _current_range_min = min; + _current_range_max = max; + + for (Events::const_iterator i = _events.begin(); i != _events.end(); ++i) { + CanvasNoteEvent* event = *i; + Item* item = dynamic_cast(event); + assert(item); + if (event && event->note()) { + if (event->note()->note() < _current_range_min + || event->note()->note() > _current_range_max) { + if (canvas_item_visible(item)) { + item->hide(); + } + } else { + if (!canvas_item_visible(item)) { + item->show(); + } + + if (CanvasNote* note = dynamic_cast(event)) { + const double y1 = midi_stream_view()->note_to_y(event->note()->note()); + const double y2 = y1 + floor(midi_stream_view()->note_height()); + + note->property_y1() = y1; + note->property_y2() = y2; + } else if (CanvasHit* hit = dynamic_cast(event)) { + double x = trackview.editor().frame_to_pixel( + beats_to_frames(event->note()->time()) - _region->start()); + const double diamond_size = midi_stream_view()->note_height() / 2.0; + double y = midi_stream_view()->note_to_y(event->note()->note()) + + ((diamond_size-2.0) / 4.0); + + hit->set_height(diamond_size); + hit->move(x-hit->x1(), y-hit->y1()); + hit->show(); } } } - } + } GhostRegion* MidiRegionView::add_ghost (TimeAxisView& tv) { - RouteTimeAxisView* rtv = dynamic_cast(&trackview); CanvasNote* note; - assert(rtv); double unit_position = _region->position () / samples_per_unit; MidiTimeAxisView* mtv = dynamic_cast(&tv); MidiGhostRegion* ghost; if (mtv && mtv->midi_view()) { - /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group. - this is because it's nice to have midi notes on top of the note lines and - audio waveforms under it. + /* 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 { + } else { ghost = new MidiGhostRegion (tv, trackview, unit_position); } @@ -852,6 +818,7 @@ MidiRegionView::end_write() delete[] _active_notes; _active_notes = NULL; _marked_for_selection.clear(); + _marked_for_velocity.clear(); } @@ -860,11 +827,13 @@ MidiRegionView::end_write() void MidiRegionView::resolve_note(uint8_t note, double end_time) { - if (midi_view()->note_mode() != Sustained) + if (midi_view()->note_mode() != Sustained) { return; + } if (_active_notes && _active_notes[note]) { - _active_notes[note]->property_x2() = trackview.editor.frame_to_pixel((nframes64_t)end_time); + const nframes64_t end_time_frames = beats_to_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 _active_notes[note] = NULL; } @@ -882,11 +851,53 @@ MidiRegionView::extend_active_notes() for (unsigned i=0; i < 128; ++i) { if (_active_notes[i]) { - _active_notes[i]->property_x2() = trackview.editor.frame_to_pixel(_region->length()); + _active_notes[i]->property_x2() = trackview.editor().frame_to_pixel(_region->length()); } } } +void +MidiRegionView::play_midi_note(boost::shared_ptr note) +{ + if (!trackview.editor().sound_notes()) { + return; + } + + RouteUI* route_ui = dynamic_cast (&trackview); + assert(route_ui); + + route_ui->midi_track()->write_immediate_event( + note->on_event().size(), note->on_event().buffer()); + + const double note_length_beats = (note->off_event().time() - note->on_event().time()); + nframes_t note_length_ms = beats_to_frames(note_length_beats) + * (1000 / (double)route_ui->session().nominal_frame_rate()); + Glib::signal_timeout().connect(bind(mem_fun(this, &MidiRegionView::play_midi_note_off), note), + note_length_ms, G_PRIORITY_DEFAULT); +} + +bool +MidiRegionView::play_midi_note_off(boost::shared_ptr note) +{ + RouteUI* route_ui = dynamic_cast (&trackview); + assert(route_ui); + + route_ui->midi_track()->write_immediate_event( + note->off_event().size(), note->off_event().buffer()); + + return false; +} + +bool +MidiRegionView::note_in_visible_range(const boost::shared_ptr note) const +{ + const nframes64_t note_start_frames = beats_to_frames(note->time()); + bool outside = (note_start_frames - _region->start() >= _region->length()) + || (note_start_frames < _region->start()) + || (note->note() < midi_stream_view()->lowest_note()) + || (note->note() > midi_stream_view()->highest_note()); + return !outside; +} /** Add a MIDI note to the view (with length). * @@ -895,53 +906,44 @@ MidiRegionView::extend_active_notes() * event arrives, to properly display the note. */ void -MidiRegionView::add_note(const boost::shared_ptr note) +MidiRegionView::add_note(const boost::shared_ptr note) { assert(note->time() >= 0); assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive); - // dont display notes beyond the region bounds - if ( note->time() - _region->start() >= _region->length() || - note->time() < _region->start() || - note->note() < midi_stream_view()->lowest_note() || - note->note() > midi_stream_view()->highest_note() ) { - return; - } - + const nframes64_t note_start_frames = beats_to_frames(note->time()); + const nframes64_t note_end_frames = beats_to_frames(note->end_time()); + ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); CanvasNoteEvent* event = 0; - const double x = trackview.editor.frame_to_pixel((nframes64_t)note->time() - _region->start()); + const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start()); if (midi_view()->note_mode() == Sustained) { - const double y1 = midi_stream_view()->note_to_y(note->note()); const double note_endpixel = - trackview.editor.frame_to_pixel((nframes64_t)note->end_time() - _region->start()); + trackview.editor().frame_to_pixel(note_end_frames - _region->start()); CanvasNote* ev_rect = new CanvasNote(*this, *group, note); ev_rect->property_x1() = x; ev_rect->property_y1() = y1; - if (note->length() > 0) + if (note->length() > 0) { ev_rect->property_x2() = note_endpixel; - else - ev_rect->property_x2() = trackview.editor.frame_to_pixel(_region->length()); + } else { + ev_rect->property_x2() = trackview.editor().frame_to_pixel(_region->length()); + } ev_rect->property_y2() = y1 + floor(midi_stream_view()->note_height()); if (note->length() == 0) { - if (_active_notes) { assert(note->note() < 128); // If this note is already active there's a stuck note, // finish the old note rectangle if (_active_notes[note->note()]) { CanvasNote* const old_rect = _active_notes[note->note()]; - boost::shared_ptr old_note = old_rect->note(); - cerr << "MidiModel: WARNING: Note has length 0: chan " << old_note->channel() - << "note " << (int)old_note->note() << " @ " << old_note->time() << endl; - /* FIXME: How large to make it? Make it a diamond? */ - old_rect->property_x2() = old_rect->property_x1() + 2.0; + boost::shared_ptr old_note = old_rect->note(); + old_rect->property_x2() = x; old_rect->property_outline_what() = (guint32) 0xF; } _active_notes[note->note()] = ev_rect; @@ -953,12 +955,9 @@ MidiRegionView::add_note(const boost::shared_ptr note) ev_rect->property_outline_what() = (guint32) 0xF; } - ev_rect->show(); - _events.push_back(ev_rect); event = ev_rect; MidiGhostRegion* gr; - for (std::vector::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { if ((gr = dynamic_cast(*g)) != 0) { gr->add_note(ev_rect); @@ -966,49 +965,58 @@ MidiRegionView::add_note(const boost::shared_ptr note) } } else if (midi_view()->note_mode() == Percussive) { - - //cerr << "MRV::add_note percussive " << note->note() << " @ " << note->time() - // << " .. " << note->end_time() << endl; - 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); CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note); ev_diamond->move(x, y); - ev_diamond->show(); - _events.push_back(ev_diamond); event = ev_diamond; } else { event = 0; } - if (event) { + 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); + if (note_in_visible_range(note)) { + event->show(); + } else { + event->hide(); + } } } void -MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext) +MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext) { assert(program.time >= 0); - // dont display program changes beyond the region bounds - if (program.time - _region->start() >= _region->length() || program.time < _region->start()) - return; - ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group(); - const double x = trackview.editor.frame_to_pixel((nframes64_t)program.time - _region->start()); + const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time)); double height = midi_stream_view()->contents_height(); boost::shared_ptr pgm_change = boost::shared_ptr( - new CanvasProgramChange(*this, *group, displaytext, height, x, 1.0)); - pgm_change->set_event_time(program.time); - pgm_change->set_program(program.value); - pgm_change->set_channel(program.channel); + new CanvasProgramChange(*this, *group, + displaytext, + height, + x, 1.0, + _model_name, + _custom_device_mode, + program.time, program.channel, program.value)); + + // Show unless program change is beyond the region bounds + if (program.time - _region->start() >= _region->length() || program.time < _region->start()) { + pgm_change->hide(); + } else { + pgm_change->show(); + } _pgm_changes.push_back(pgm_change); } @@ -1016,10 +1024,9 @@ MidiRegionView::add_pgm_change(ControlEvent& program, string displaytext) void MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) { - cerr << "getting patch key at " << time << " for channel " << channel << endl; Evoral::Parameter bank_select_msb(MidiCCAutomation, channel, MIDI_CTL_MSB_BANK); - boost::shared_ptr msb_control = _model->control(bank_select_msb); + boost::shared_ptr msb_control = _model->control(bank_select_msb); float msb = -1.0; if (msb_control != 0) { msb = int(msb_control->get_float(true, time)); @@ -1027,7 +1034,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch } Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK); - boost::shared_ptr lsb_control = _model->control(bank_select_lsb); + boost::shared_ptr lsb_control = _model->control(bank_select_lsb); float lsb = -1.0; if (lsb_control != 0) { lsb = lsb_control->get_float(true, time); @@ -1035,7 +1042,7 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch } Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0); - boost::shared_ptr program_control = _model->control(program_change); + boost::shared_ptr program_control = _model->control(program_change); float program_number = -1.0; if (program_control != 0) { program_number = program_control->get_float(true, time); @@ -1050,25 +1057,24 @@ MidiRegionView::get_patch_key_at(double time, uint8_t channel, MIDI::Name::Patch void -MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch) +MidiRegionView::alter_program_change(PCEvent& old_program, const MIDI::Name::PatchPrimaryKey& new_patch) { - // TODO: Get the real event here and alter them at the original times Evoral::Parameter bank_select_msb(MidiCCAutomation, old_program.channel, MIDI_CTL_MSB_BANK); - boost::shared_ptr msb_control = _model->control(bank_select_msb); + boost::shared_ptr msb_control = _model->control(bank_select_msb); if (msb_control != 0) { msb_control->set_float(float(new_patch.msb), true, old_program.time); } // TODO: Get the real event here and alter them at the original times Evoral::Parameter bank_select_lsb(MidiCCAutomation, old_program.channel, MIDI_CTL_LSB_BANK); - boost::shared_ptr lsb_control = _model->control(bank_select_lsb); + boost::shared_ptr lsb_control = _model->control(bank_select_lsb); if (lsb_control != 0) { lsb_control->set_float(float(new_patch.lsb), true, old_program.time); } Evoral::Parameter program_change(MidiPgmChangeAutomation, old_program.channel, 0); - boost::shared_ptr program_control = _model->control(program_change); + boost::shared_ptr program_control = _model->control(program_change); assert(program_control != 0); program_control->set_float(float(new_patch.program_number), true, old_program.time); @@ -1076,6 +1082,13 @@ MidiRegionView::alter_program_change(ControlEvent& old_program, const MIDI::Name redisplay_model(); } +void +MidiRegionView::program_selected(CanvasProgramChange& program, const MIDI::Name::PatchPrimaryKey& new_patch) +{ + PCEvent program_change_event(program.event_time(), program.program(), program.channel()); + alter_program_change(program_change_event, new_patch); +} + void MidiRegionView::previous_program(CanvasProgramChange& program) { @@ -1087,10 +1100,9 @@ MidiRegionView::previous_program(CanvasProgramChange& program) _model_name, _custom_device_mode, program.channel(), - key - ); + key); - ControlEvent program_change_event(program.event_time(), program.program(), program.channel()); + PCEvent program_change_event(program.event_time(), program.program(), program.channel()); if (patch) { alter_program_change(program_change_event, patch->patch_primary_key()); } @@ -1107,9 +1119,9 @@ MidiRegionView::next_program(CanvasProgramChange& program) _model_name, _custom_device_mode, program.channel(), - key - ); - ControlEvent program_change_event(program.event_time(), program.program(), program.channel()); + key); + + PCEvent program_change_event(program.event_time(), program.program(), program.channel()); if (patch) { alter_program_change(program_change_event, patch->patch_primary_key()); } @@ -1118,7 +1130,13 @@ MidiRegionView::next_program(CanvasProgramChange& program) void MidiRegionView::delete_selection() { - assert(_delta_command); + if (_selection.empty()) { + return; + } + + if (!_delta_command) { + _delta_command = _model->new_delta_command("delete selection"); + } for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->selected()) { @@ -1135,6 +1153,7 @@ MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev) for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { if ((*i)->selected() && (*i) != ev) { (*i)->selected(false); + (*i)->hide_velocity(); } } @@ -1144,31 +1163,32 @@ MidiRegionView::clear_selection_except(ArdourCanvas::CanvasNoteEvent* ev) void MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev) { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) { + + Selection::iterator tmp = i; + ++tmp; + if ((*i) != ev) { - (*i)->selected(false); - } - } + remove_from_selection (*i); + } - _selection.clear(); - _selection.insert(ev); + i = tmp; + } - if ( ! ev->selected()) { - ev->selected(true); + if (!ev->selected()) { + add_to_selection (ev); } } void MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add) { - if ( ! add) { + if (!add) { clear_selection_except(ev); } - _selection.insert(ev); - - if ( ! ev->selected()) { - ev->selected(true); + if (!ev->selected()) { + add_to_selection (ev); } } @@ -1176,15 +1196,11 @@ MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add) void MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev, bool add) { - if ( ! add) { + if (!add) { clear_selection_except(ev); } - _selection.erase(ev); - - if (ev->selected()) { - ev->selected(false); - } + remove_from_selection (ev); } @@ -1209,16 +1225,12 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2 assert((*i)->x1() >= last_x1); last_x1 = (*i)->x1(); #endif - // Inside rectangle if ((*i)->x1() >= x1 && (*i)->x1() <= x2 && (*i)->y1() >= last_y && (*i)->y1() <= y) { - if (!(*i)->selected()) { - (*i)->selected(true); - _selection.insert(*i); - } - // Not inside rectangle + // Inside rectangle + add_to_selection (*i); } else if ((*i)->selected()) { - (*i)->selected(false); - _selection.erase(*i); + // Not inside rectangle + remove_from_selection (*i); } } } else { @@ -1228,153 +1240,189 @@ MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2 assert((*i)->x1() >= last_x1); last_x1 = (*i)->x1(); #endif - // Inside rectangle if ((*i)->x2() <= x1 && (*i)->x2() >= x2 && (*i)->y1() >= last_y && (*i)->y1() <= y) { - if (!(*i)->selected()) { - (*i)->selected(true); - _selection.insert(*i); - } - // Not inside rectangle + // Inside rectangle + add_to_selection (*i); } else if ((*i)->selected()) { - (*i)->selected(false); - _selection.erase(*i); + // Not inside rectangle + remove_from_selection (*i); } } } } +void +MidiRegionView::remove_from_selection (CanvasNoteEvent* ev) +{ + Selection::iterator i = _selection.find (ev); + + if (i != _selection.end()) { + _selection.erase (i); + } + + ev->selected (false); + ev->hide_velocity (); + + if (_selection.empty()) { + PublicEditor& editor (trackview.editor()); + editor.get_selection().remove (this); + } +} + +void +MidiRegionView::add_to_selection (CanvasNoteEvent* ev) +{ + bool add_mrv_selection = false; + + if (_selection.empty()) { + add_mrv_selection = true; + } + + if (_selection.insert (ev).second) { + ev->selected (true); + play_midi_note ((ev)->note()); + } + + if (add_mrv_selection) { + PublicEditor& editor (trackview.editor()); + editor.get_selection().add (this); + } +} void MidiRegionView::move_selection(double dx, double dy) { - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { (*i)->move_event(dx, dy); + } } - void MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote) { // TODO: This would be faster/nicer with a MoveCommand that doesn't need to copy... - if (_selection.find(ev) != _selection.end()) { - uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note(); - uint8_t highest_note_in_selection = midi_stream_view()->highest_note(); - uint8_t highest_note_difference = 0; - - // find highest and lowest notes first - for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { - uint8_t pitch = (*i)->note()->note(); - lowest_note_in_selection = std::min(lowest_note_in_selection, pitch); - highest_note_in_selection = std::max(highest_note_in_selection, pitch); - } - - /* - 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 - if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) { - highest_note_difference = highest_note_in_selection - 127; - } - - start_delta_command(_("move notes")); - - for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) { - Selection::iterator next = i; - ++next; + if (_selection.find(ev) == _selection.end()) { + return; + } - const boost::shared_ptr copy(new Evoral::Note(*(*i)->note().get())); + uint8_t lowest_note_in_selection = midi_stream_view()->lowest_note(); + uint8_t highest_note_in_selection = midi_stream_view()->highest_note(); + uint8_t highest_note_difference = 0; - // we need to snap here again in nframes64_t in order to be sample accurate - double new_note_time = (*i)->note()->time(); - new_note_time += dt; + // find highest and lowest notes first + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + uint8_t pitch = (*i)->note()->note(); + lowest_note_in_selection = std::min(lowest_note_in_selection, pitch); + highest_note_in_selection = std::max(highest_note_in_selection, pitch); + } + + /* + 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 + if (dnote <= 127 && (highest_note_in_selection + dnote > 127)) { + highest_note_difference = highest_note_in_selection - 127; + } + + start_delta_command(_("move notes")); - // keep notes inside region if dragged beyond left region bound - if (new_note_time < _region->start()) { - new_note_time = _region->start(); - } - - // since note time is region-absolute but snap_to_frame expects position-relative - // time we have to coordinate transform back and forth here. - new_note_time = snap_to_frame(nframes64_t(new_note_time) - _region->start()) + _region->start(); - - copy->set_time(new_note_time); + for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) { + Selection::iterator next = i; + ++next; - uint8_t original_pitch = (*i)->note()->note(); - uint8_t new_pitch = original_pitch + dnote - highest_note_difference; - - // keep notes in standard midi range - clamp_0_to_127(new_pitch); - - //notes which are dragged beyond the standard midi range snap back to their original place - if ((original_pitch != 0 && new_pitch == 0) || (original_pitch != 127 && new_pitch == 127)) { - new_pitch = original_pitch; - } + const boost::shared_ptr copy(new NoteType(*(*i)->note().get())); - lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); - highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + nframes64_t start_frames = beats_to_frames((*i)->note()->time()); + if (dt >= 0) { + start_frames += snap_frame_to_frame(trackview.editor().pixel_to_frame(dt)); + } else { + start_frames -= snap_frame_to_frame(trackview.editor().pixel_to_frame(-dt)); + } - copy->set_note(new_pitch); - - command_remove_note(*i); - command_add_note(copy, true); + copy->set_time(frames_to_beats(start_frames)); - i = next; + uint8_t original_pitch = (*i)->note()->note(); + uint8_t new_pitch = original_pitch + dnote - highest_note_difference; + + // 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; } - apply_command(); + lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); + highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + + copy->set_note(new_pitch); - // care about notes being moved beyond the upper/lower bounds on the canvas - if (lowest_note_in_selection < midi_stream_view()->lowest_note() || - highest_note_in_selection > midi_stream_view()->highest_note()) { - midi_stream_view()->set_note_range(MidiStreamView::ContentsRange); - } + command_remove_note(*i); + command_add_note(copy, (*i)->selected()); + + i = next; + } + + apply_command(); + + // care about notes being moved beyond the upper/lower bounds on the canvas + if (lowest_note_in_selection < midi_stream_view()->lowest_note() || + highest_note_in_selection > midi_stream_view()->highest_note()) { + midi_stream_view()->set_note_range(MidiStreamView::ContentsRange); } } nframes64_t -MidiRegionView::snap_to_frame(double x) +MidiRegionView::snap_pixel_to_frame(double x) { - PublicEditor &editor = trackview.editor; - // x is region relative - // convert x to global frame + PublicEditor& editor = trackview.editor(); + // x is region relative, convert it to global absolute frames nframes64_t frame = editor.pixel_to_frame(x) + _region->position(); editor.snap_to(frame); - // convert event_frame back to local coordinates relative to position - frame -= _region->position(); - return frame; + return frame - _region->position(); // convert back to region relative } nframes64_t -MidiRegionView::snap_to_frame(nframes64_t x) +MidiRegionView::snap_frame_to_frame(nframes64_t x) { - PublicEditor &editor = trackview.editor; - // x is region relative - // convert x to global frame + PublicEditor& editor = trackview.editor(); + // x is region relative, convert it to global absolute frames nframes64_t frame = x + _region->position(); editor.snap_to(frame); - // convert event_frame back to local coordinates relative to position - frame -= _region->position(); - return frame; + return frame - _region->position(); // convert back to region relative } double MidiRegionView::snap_to_pixel(double x) { - return (double) trackview.editor.frame_to_pixel(snap_to_frame(x)); + return (double) trackview.editor().frame_to_pixel(snap_pixel_to_frame(x)); +} + +double +MidiRegionView::get_position_pixels() +{ + nframes64_t region_frame = get_position(); + return trackview.editor().frame_to_pixel(region_frame); +} + +nframes64_t +MidiRegionView::beats_to_frames(double beats) const +{ + return _time_converter.to(beats); } double -MidiRegionView::get_position_pixels(void) +MidiRegionView::frames_to_beats(nframes64_t frames) const { - nframes64_t region_frame = get_position(); - return trackview.editor.frame_to_pixel(region_frame); + return _time_converter.from(frames); } void @@ -1391,32 +1439,25 @@ MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end) resize_data->canvas_note = note; // create a new SimpleRect from the note which will be the resize preview - SimpleRect *resize_rect = - new SimpleRect( - *group, - note->x1(), - note->y1(), - note->x2(), - note->y2()); + SimpleRect *resize_rect = new SimpleRect( + *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_MidiNoteSelectedOutline.get(), - 128); + uint32_t fill_color = UINT_RGBA_CHANGE_A( + 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( - note_fill_color(note->note()->velocity()), + resize_rect->property_fill_color_rgba() = UINT_INTERPOLATE( + CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()), fill_color, 0.85); - resize_rect->property_outline_color_rgba() = - ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get(); + resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline( + ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get()); resize_data->resize_rect = resize_rect; @@ -1435,8 +1476,8 @@ void MidiRegionView::update_resizing(CanvasNote::NoteEnd note_end, double x, bool relative) { for (std::vector::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) { - SimpleRect *resize_rect = (*i)->resize_rect; - CanvasNote *canvas_note = (*i)->canvas_note; + SimpleRect* resize_rect = (*i)->resize_rect; + CanvasNote* canvas_note = (*i)->canvas_note; const double region_start = get_position_pixels(); @@ -1477,11 +1518,11 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo // because snapping works on world coordinates we have to transform current_x // to world coordinates before snapping and transform it back afterwards - nframes64_t current_frame = snap_to_frame(current_x); + nframes64_t current_frame = snap_pixel_to_frame(current_x); // transform to region start relative current_frame += _region->start(); - const boost::shared_ptr copy(new Evoral::Note(*(canvas_note->note().get()))); + const boost::shared_ptr copy(new NoteType(*(canvas_note->note().get()))); // resize beginning of note if (note_end == CanvasNote::NOTE_ON && current_frame < copy->end_time()) { @@ -1504,30 +1545,36 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo apply_command(); } +void +MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative) +{ + const boost::shared_ptr copy(new NoteType(*(event->note().get()))); + + if (relative) { + uint8_t new_velocity = copy->velocity() + velocity; + clamp_to_0_127(new_velocity); + copy->set_velocity(new_velocity); + } else { + copy->set_velocity(velocity); + } + + command_remove_note(event); + command_add_note(copy, event->selected(), true); +} void -MidiRegionView::change_velocity(uint8_t velocity, bool relative) +MidiRegionView::change_velocity(CanvasNoteEvent* ev, int8_t velocity, bool relative) { start_delta_command(_("change velocity")); + + change_note_velocity(ev, velocity, relative); + for (Selection::iterator i = _selection.begin(); i != _selection.end();) { Selection::iterator next = i; ++next; - - CanvasNoteEvent *event = *i; - const boost::shared_ptr copy(new Evoral::Note(*(event->note().get()))); - - if (relative) { - uint8_t new_velocity = copy->velocity() + velocity; - clamp_0_to_127(new_velocity); - - copy->set_velocity(new_velocity); - } else { // absolute - copy->set_velocity(velocity); + if ( !(*((*i)->note()) == *(ev->note())) ) { + change_note_velocity(*i, velocity, relative); } - - command_remove_note(event); - command_add_note(copy, true); - i = next; } @@ -1542,13 +1589,13 @@ MidiRegionView::change_channel(uint8_t channel) Selection::iterator next = i; ++next; - CanvasNoteEvent *event = *i; - const boost::shared_ptr copy(new Evoral::Note(*(event->note().get()))); + CanvasNoteEvent* event = *i; + const boost::shared_ptr copy(new NoteType(*(event->note().get()))); copy->set_channel(channel); command_remove_note(event); - command_add_note(copy, true); + command_add_note(copy, event->selected()); i = next; } @@ -1560,11 +1607,7 @@ MidiRegionView::change_channel(uint8_t channel) void MidiRegionView::note_entered(ArdourCanvas::CanvasNoteEvent* ev) { - if (ev->note() && _mouse_state == EraseTouchDragging) { - start_delta_command(_("note entered")); - ev->selected(true); - _delta_command->remove(ev->note()); - } else if (_mouse_state == SelectTouchDragging) { + if (_mouse_state == SelectTouchDragging) { note_selected(ev, true); } } @@ -1619,3 +1662,94 @@ MidiRegionView::midi_patch_settings_changed(std::string model, std::string custo redisplay_model(); } +void +MidiRegionView::cut_copy_clear (Editing::CutCopyOp op) +{ + if (_selection.empty()) { + return; + } + + PublicEditor& editor (trackview.editor()); + + switch (op) { + case Cut: + case Copy: + editor.get_cut_buffer().add (selection_as_cut_buffer()); + break; + default: + break; + } + + start_delta_command(); + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + switch (op) { + case Copy: + break; + case Cut: + command_remove_note (*i); + break; + case Clear: + break; + } + } + + apply_command(); +} + +MidiCutBuffer* +MidiRegionView::selection_as_cut_buffer () const +{ + Evoral::Sequence::Notes notes; + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + notes.push_back (boost::shared_ptr (new NoteType (*((*i)->note().get())))); + } + + /* sort them into time order */ + + sort (notes.begin(), notes.end(), Evoral::Sequence::note_time_comparator); + + MidiCutBuffer* cb = new MidiCutBuffer (trackview.session()); + cb->set (notes); + + return cb; +} + +void +MidiRegionView::paste (nframes64_t pos, float times, const MidiCutBuffer& mcb) +{ + if (mcb.empty()) { + return; + } + + start_delta_command (_("paste")); + + MidiModel::TimeType beat_delta; + MidiModel::TimeType paste_pos_beats; + MidiModel::TimeType duration; + + duration = mcb.notes().back()->end_time() - mcb.notes().front()->time(); + paste_pos_beats = frames_to_beats (pos); + beat_delta = mcb.notes().front()->time() - paste_pos_beats; + paste_pos_beats = 0; + + _selection.clear (); + + for (int n = 0; n < (int) times; ++n) { + + for (Evoral::Sequence::Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) { + + boost::shared_ptr copied_note (new NoteType (*((*i).get()))); + copied_note->set_time (paste_pos_beats + copied_note->time() - beat_delta); + + /* make all newly added notes selected */ + + command_add_note (copied_note, true); + } + + paste_pos_beats += duration; + } + + apply_command (); +}