+ if (!trackview.editor().sound_notes()) {
+ return;
+ }
+
+ RouteUI* route_ui = dynamic_cast<RouteUI*> (&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<NoteType> note)
+{
+ RouteUI* route_ui = dynamic_cast<RouteUI*> (&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_region_range(const boost::shared_ptr<NoteType> note, bool& visible) 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());
+
+ visible = (note->note() >= midi_stream_view()->lowest_note()) &&
+ (note->note() <= midi_stream_view()->highest_note());
+
+ return !outside;
+}
+
+void
+MidiRegionView::update_note (CanvasNote* ev)
+{
+ boost::shared_ptr<NoteType> note = ev->note();
+
+ const nframes64_t note_start_frames = beats_to_frames(note->time());
+ const nframes64_t note_end_frames = beats_to_frames(note->end_time());
+
+ const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
+ 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;
+ if (note->length() > 0) {
+ ev->property_x2() = note_endpixel;
+ } else {
+ ev->property_x2() = trackview.editor().frame_to_pixel(_region->length());
+ }
+ ev->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<NoteType> old_note = old_rect->note();
+ old_rect->property_x2() = x;
+ old_rect->property_outline_what() = (guint32) 0xF;
+ }
+ _active_notes[note->note()] = ev;
+ }
+ /* outline all but right edge */
+ ev->property_outline_what() = (guint32) (0x1 & 0x4 & 0x8);
+ } else {
+ /* outline all edges */
+ ev->property_outline_what() = (guint32) 0xF;
+ }
+}
+
+void
+MidiRegionView::update_hit (CanvasHit* ev)
+{
+ boost::shared_ptr<NoteType> note = ev->note();
+
+ const nframes64_t note_start_frames = beats_to_frames(note->time());
+ const double x = trackview.editor().frame_to_pixel(note_start_frames - _region->start());
+ 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);
+
+ ev->move_to (x, y);
+}
+
+/** Add a MIDI note to the view (with length).
+ *
+ * If in sustained mode, notes with length 0 will be considered active
+ * notes, and resolve_note should be called when the corresponding note off
+ * event arrives, to properly display the note.
+ */
+void
+MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
+{
+ CanvasNoteEvent* event = 0;
+
+ assert(note->time() >= 0);
+ assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
+
+ ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
+
+ if (midi_view()->note_mode() == Sustained) {
+
+ CanvasNote* ev_rect = new CanvasNote(*this, *group, note);
+
+ update_note (ev_rect);
+
+ event = ev_rect;
+
+ MidiGhostRegion* gr;
+
+ for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
+ if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
+ gr->add_note(ev_rect);
+ }
+ }
+
+ } else if (midi_view()->note_mode() == Percussive) {
+
+ const double diamond_size = midi_stream_view()->note_height() / 2.0;
+
+ CanvasHit* ev_diamond = new CanvasHit(*this, *group, diamond_size, note);
+
+ update_hit (ev_diamond);
+
+ event = ev_diamond;
+
+ } else {
+ event = 0;
+ }
+
+ 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 (visible) {
+ event->show();
+ } else {
+ event->hide ();
+ }
+ }
+}
+
+void
+MidiRegionView::add_note (uint8_t channel, uint8_t number, uint8_t velocity,
+ Evoral::MusicalTime pos, Evoral::MusicalTime len)
+{
+ boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
+
+ start_delta_command (_("step add"));
+ delta_add_note (new_note, true, false);
+ apply_delta();
+
+ /* potentially extend region to hold new note */
+
+ nframes64_t end_frame = _region->position() + beats_to_frames (new_note->end_time());
+ nframes64_t region_end = _region->position() + _region->length() - 1;
+
+ if (end_frame > region_end) {
+ _region->set_length (end_frame, this);
+ } else {
+ redisplay_model ();
+ }
+}
+
+void
+MidiRegionView::add_pgm_change(PCEvent& program, const string& displaytext)
+{
+ assert(program.time >= 0);
+
+ ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
+ const double x = trackview.editor().frame_to_pixel(beats_to_frames(program.time));
+
+ double height = midi_stream_view()->contents_height();
+
+ boost::shared_ptr<CanvasProgramChange> pgm_change = boost::shared_ptr<CanvasProgramChange>(
+ 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);
+}
+
+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<Evoral::Control> msb_control = _model->control(bank_select_msb);
+ float msb = -1.0;
+ if (msb_control != 0) {
+ msb = int(msb_control->get_float(true, time));
+ cerr << "got msb " << msb;
+ }
+
+ Evoral::Parameter bank_select_lsb(MidiCCAutomation, channel, MIDI_CTL_LSB_BANK);
+ boost::shared_ptr<Evoral::Control> lsb_control = _model->control(bank_select_lsb);
+ float lsb = -1.0;
+ if (lsb_control != 0) {
+ lsb = lsb_control->get_float(true, time);
+ cerr << " got lsb " << lsb;
+ }
+
+ Evoral::Parameter program_change(MidiPgmChangeAutomation, channel, 0);
+ boost::shared_ptr<Evoral::Control> program_control = _model->control(program_change);
+ float program_number = -1.0;
+ if (program_control != 0) {
+ program_number = program_control->get_float(true, time);
+ cerr << " got program " << program_number << endl;
+ }
+
+ key.msb = (int) floor(msb + 0.5);
+ key.lsb = (int) floor(lsb + 0.5);
+ key.program_number = (int) floor(program_number + 0.5);
+ assert(key.is_sane());
+}
+
+
+void
+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<Evoral::Control> 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<Evoral::Control> 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<Evoral::Control> program_control = _model->control(program_change);
+
+ assert(program_control != 0);
+ program_control->set_float(float(new_patch.program_number), true, old_program.time);
+
+ 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)
+{
+ MIDI::Name::PatchPrimaryKey key;
+ get_patch_key_at(program.event_time(), program.channel(), key);
+
+ boost::shared_ptr<MIDI::Name::Patch> patch =
+ MIDI::Name::MidiPatchManager::instance().previous_patch(
+ _model_name,
+ _custom_device_mode,
+ 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());
+ }
+}
+
+void
+MidiRegionView::next_program(CanvasProgramChange& program)
+{
+ MIDI::Name::PatchPrimaryKey key;
+ get_patch_key_at(program.event_time(), program.channel(), key);
+
+ boost::shared_ptr<MIDI::Name::Patch> patch =
+ MIDI::Name::MidiPatchManager::instance().next_patch(
+ _model_name,
+ _custom_device_mode,
+ 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());
+ }
+}
+
+void
+MidiRegionView::delete_selection()
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ start_delta_command (_("delete selection"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->selected()) {
+ _delta_command->remove((*i)->note());
+ }
+ }
+
+ _selection.clear();
+
+ apply_delta ();
+}
+
+void
+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();
+ }
+ }
+
+ _selection.clear();
+}
+
+void
+MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
+{
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
+ if ((*i) != ev) {
+
+ Selection::iterator tmp = i;
+ ++tmp;
+
+ (*i)->selected (false);
+ _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.
+ */
+
+ if (!ev->selected()) {
+ add_to_selection (ev);
+ }
+}
+
+void
+MidiRegionView::note_selected(ArdourCanvas::CanvasNoteEvent* ev, bool add, bool extend)
+{
+ if (!add) {
+ clear_selection_except(ev);
+ }
+
+ if (!extend) {
+
+ if (!ev->selected()) {
+ add_to_selection (ev);
+ }
+
+ } else {
+ /* find end of latest note selected, select all between that and the start of "ev" */
+
+ Evoral::MusicalTime earliest = DBL_MAX;
+ Evoral::MusicalTime latest = 0;
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->note()->end_time() > latest) {
+ latest = (*i)->note()->end_time();
+ }
+ if ((*i)->note()->time() < earliest) {
+ earliest = (*i)->note()->time();
+ }
+ }
+
+ if (ev->note()->end_time() > latest) {
+ latest = ev->note()->end_time();
+ }
+
+ if (ev->note()->time() < earliest) {
+ earliest = ev->note()->time();
+ }
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+
+ /* find notes entirely within OR spanning the earliest..latest range */
+
+ if (((*i)->note()->time() >= earliest && (*i)->note()->end_time() <= latest) ||
+ ((*i)->note()->time() <= earliest && (*i)->note()->end_time() >= latest)) {
+ 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
+ }
+ }
+}
+
+void
+MidiRegionView::note_deselected(ArdourCanvas::CanvasNoteEvent* ev)
+{
+ remove_from_selection (ev);
+}
+
+void
+MidiRegionView::update_drag_selection(double x1, double x2, double y1, double y2)
+{
+ if (x1 > x2) {
+ swap (x1, x2);
+ }
+
+ 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.
+ */
+
+ const double ix1 = (*i)->x1();
+ const double ix2 = (*i)->x2();
+ const double iy1 = (*i)->y1();
+ const double iy2 = (*i)->y2();
+
+ if ((ix1 >= x1 && ix1 <= x2 && iy1 >= y1 && iy1 <= y2) ||
+ (ix1 >= x1 && ix1 <= x2 && iy2 >= y1 && iy2 <= y2) ||
+ (ix2 >= x1 && ix2 <= x2 && iy1 >= y1 && iy1 <= y2) ||
+ (ix2 >= x1 && ix2 <= x2 && iy2 >= y1 && iy2 <= y2)) {
+
+ // Inside rectangle
+ if (!(*i)->selected()) {
+ add_to_selection (*i);
+ }
+ } else if ((*i)->selected()) {
+ // 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) {
+ (*i)->move_event(dx, dy);
+ }
+}
+
+void
+MidiRegionView::note_dropped(CanvasNoteEvent *, double dt, int8_t dnote)
+{
+ assert (!_selection.empty());
+
+ uint8_t lowest_note_in_selection = 127;
+ uint8_t highest_note_in_selection = 0;
+ 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 (highest_note_in_selection + dnote > 127) {
+ highest_note_difference = highest_note_in_selection - 127;
+ }
+
+ start_diff_command(_("move notes"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
+
+ 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));
+ }
+
+ Evoral::MusicalTime new_time = frames_to_beats(start_frames);
+
+ if (new_time < 0) {
+ continue;
+ }
+
+ diff_add_change (*i, MidiModel::DiffCommand::StartTime, new_time);
+
+ 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;
+ }
+
+ lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+ diff_add_change (*i, MidiModel::DiffCommand::NoteNumber, new_pitch);
+ }
+
+ apply_diff();
+
+ // 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_pixel_to_frame(double x)
+{
+ 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);
+ return frame - _region->position(); // convert back to region relative
+}
+
+nframes64_t
+MidiRegionView::snap_frame_to_frame(nframes64_t x)
+{
+ PublicEditor& editor = trackview.editor();
+ // x is region relative, convert it to global absolute frames
+ nframes64_t frame = x + _region->position();
+ editor.snap_to(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_pixel_to_frame(x));
+}
+
+double
+MidiRegionView::get_position_pixels()
+{
+ nframes64_t region_frame = get_position();
+ return trackview.editor().frame_to_pixel(region_frame);
+}
+
+double
+MidiRegionView::get_end_position_pixels()
+{
+ nframes64_t frame = get_position() + get_duration ();
+ return trackview.editor().frame_to_pixel(frame);
+}
+
+nframes64_t
+MidiRegionView::beats_to_frames(double beats) const
+{
+ return _time_converter.to(beats);
+}
+
+double
+MidiRegionView::frames_to_beats(nframes64_t frames) const
+{
+ return _time_converter.from(frames);
+}
+
+void
+MidiRegionView::begin_resizing (bool /*at_front*/)
+{
+ _resize_data.clear();
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ CanvasNote *note = dynamic_cast<CanvasNote *> (*i);
+
+ // only insert CanvasNotes into the map
+ if (note) {
+ NoteResizeData *resize_data = new NoteResizeData();
+ 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());
+
+ // calculate the colors: get the color settings
+ 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(
+ CanvasNoteEvent::meter_style_fill_color(note->note()->velocity()),
+ fill_color,
+ 0.85);
+
+ resize_rect->property_outline_color_rgba() = CanvasNoteEvent::calculate_outline(
+ ARDOUR_UI::config()->canvasvar_MidiNoteSelected.get());
+
+ resize_data->resize_rect = resize_rect;
+ _resize_data.push_back(resize_data);
+ }
+ }
+}
+
+void
+MidiRegionView::update_resizing (bool at_front, double delta_x, bool relative)
+{
+ for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
+ SimpleRect* resize_rect = (*i)->resize_rect;
+ CanvasNote* canvas_note = (*i)->canvas_note;
+ double current_x;
+
+ if (at_front) {
+ if (relative) {
+ current_x = canvas_note->x1() + delta_x;
+ } else {
+ // x is in track relative, transform it to region relative
+ current_x = delta_x - get_position_pixels();
+ }
+ } else {
+ if (relative) {
+ current_x = canvas_note->x2() + delta_x;
+ } else {
+ // x is in track relative, transform it to region relative
+ current_x = delta_x - get_end_position_pixels ();
+ }
+ }
+
+ if (at_front) {
+ resize_rect->property_x1() = snap_to_pixel(current_x);
+ resize_rect->property_x2() = canvas_note->x2();
+ } else {
+ resize_rect->property_x2() = snap_to_pixel(current_x);
+ resize_rect->property_x1() = canvas_note->x1();
+ }
+ }
+}
+
+void
+MidiRegionView::commit_resizing (bool at_front, double delta_x, bool relative)
+{
+ start_diff_command(_("resize notes"));
+
+ for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
+ CanvasNote* canvas_note = (*i)->canvas_note;
+ SimpleRect* resize_rect = (*i)->resize_rect;
+ const double region_start = get_position_pixels();
+ double current_x;
+
+ if (at_front) {
+ if (relative) {
+ current_x = canvas_note->x1() + delta_x;
+ } else {
+ // x is in track relative, transform it to region relative
+ current_x = region_start + delta_x;
+ }
+ } else {
+ if (relative) {
+ current_x = canvas_note->x2() + delta_x;
+ } else {
+ // x is in track relative, transform it to region relative
+ current_x = region_start + delta_x;
+ }
+ }
+
+ current_x = snap_pixel_to_frame (current_x);
+ current_x = frames_to_beats (current_x);
+
+ if (at_front && current_x < canvas_note->note()->end_time()) {
+ diff_add_change (canvas_note, MidiModel::DiffCommand::StartTime, current_x);
+
+ double len = canvas_note->note()->time() - current_x;
+ len += canvas_note->note()->length();
+
+ if (len > 0) {
+ /* XXX convert to beats */
+ diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
+ }
+ }
+
+ if (!at_front) {
+ double len = current_x - canvas_note->note()->time();
+
+ if (len > 0) {
+ /* XXX convert to beats */
+ diff_add_change (canvas_note, MidiModel::DiffCommand::Length, len);
+ }
+ }
+
+ delete resize_rect;
+ delete (*i);
+ }
+
+ _resize_data.clear();
+ apply_diff();
+}
+
+void
+MidiRegionView::change_note_velocity(CanvasNoteEvent* event, int8_t velocity, bool relative)
+{
+ uint8_t new_velocity;
+
+ if (relative) {
+ new_velocity = event->note()->velocity() + velocity;
+ clamp_to_0_127(new_velocity);
+ } else {
+ new_velocity = velocity;
+ }
+
+ diff_add_change (event, MidiModel::DiffCommand::Velocity, new_velocity);
+}
+
+void
+MidiRegionView::change_note_note (CanvasNoteEvent* event, int8_t note, bool relative)
+{
+ uint8_t new_note;
+
+ if (relative) {
+ new_note = event->note()->note() + note;
+ } else {
+ new_note = note;
+ }
+
+ clamp_to_0_127 (new_note);
+ diff_add_change (event, MidiModel::DiffCommand::NoteNumber, new_note);
+}
+
+void
+MidiRegionView::trim_note (CanvasNoteEvent* event, Evoral::MusicalTime front_delta, Evoral::MusicalTime end_delta)
+{
+ bool change_start = false;
+ bool change_length = false;
+ Evoral::MusicalTime new_start;
+ Evoral::MusicalTime new_length;
+
+ /* 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)
+
+ 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 (front_delta) {
+ if (front_delta < 0) {
+
+ if (event->note()->time() < -front_delta) {
+ new_start = 0;
+ } else {
+ new_start = event->note()->time() + front_delta; // moves earlier
+ }
+
+ /* start moved toward zero, so move the end point out to where it used to be.
+ Note that front_delta is negative, so this increases the length.
+ */
+
+ new_length = event->note()->length() - front_delta;
+ change_start = true;
+ change_length = true;
+
+ } else {
+
+ Evoral::MusicalTime new_pos = event->note()->time() + front_delta;
+
+ if (new_pos < event->note()->end_time()) {
+ new_start = event->note()->time() + front_delta;
+ /* start moved toward the end, so move the end point back to where it used to be */
+ new_length = event->note()->length() - front_delta;
+ change_start = true;
+ change_length = true;
+ }
+ }
+
+ }
+
+ if (end_delta) {
+ bool can_change = true;
+ if (end_delta < 0) {
+ if (event->note()->length() < -end_delta) {
+ can_change = false;
+ }
+ }
+
+ if (can_change) {
+ new_length = event->note()->length() + end_delta;
+ change_length = true;
+ }
+ }
+
+ if (change_start) {
+ diff_add_change (event, MidiModel::DiffCommand::StartTime, new_start);
+ }
+
+ if (change_length) {
+ diff_add_change (event, MidiModel::DiffCommand::Length, new_length);
+ }
+}
+
+void
+MidiRegionView::change_note_time (CanvasNoteEvent* event, Evoral::MusicalTime delta, bool relative)
+{
+ Evoral::MusicalTime new_time;
+
+ if (relative) {
+ if (delta < 0.0) {
+ if (event->note()->time() < -delta) {
+ new_time = 0;
+ } else {
+ new_time = event->note()->time() + delta;
+ }
+ } else {
+ new_time = event->note()->time() + delta;