diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 91a1b52348..cdbdf7fce8 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -5209,7 +5209,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky ) TrackViewList tracks_to_add; TrackViewList tracks_to_remove; - for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) + for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) if ( !_editor->selection->tracks.contains ( *i ) ) tracks_to_add.push_back ( *i ); _editor->selection->add(tracks_to_add); @@ -5611,6 +5611,7 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i) , _cumulative_dx (0) , _cumulative_dy (0) , _was_selected (false) + , _copy (false) { DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n"); @@ -5624,6 +5625,13 @@ void NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *) { Drag::start_grab (event); + + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) { + _copy = true; + } else { + _copy = false; + } + setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ())); if (!(_was_selected = _primary->selected())) { @@ -5720,8 +5728,13 @@ NoteDrag::total_dy () const } void -NoteDrag::motion (GdkEvent * event, bool) +NoteDrag::motion (GdkEvent * event, bool first_move) { + if (_copy && first_move) { + /* make copies of all the selected notes */ + _primary = _region->copy_selection (); + } + /* Total change in x and y since the start of the drag */ frameoffset_t const dx = total_dx (event->button.state); int8_t const dy = total_dy (); @@ -5737,7 +5750,11 @@ NoteDrag::motion (GdkEvent * event, bool) int8_t note_delta = total_dy(); if (tdx || tdy) { - _region->move_selection (tdx, tdy, note_delta); + if (_copy) { + _region->move_copies (tdx, tdy, note_delta); + } else { + _region->move_selection (tdx, tdy, note_delta); + } /* the new note value may be the same as the old one, but we * don't know what that means because the selection may have @@ -5797,7 +5814,7 @@ NoteDrag::finished (GdkEvent* ev, bool moved) } } } else { - _region->note_dropped (_primary, total_dx (ev->button.state), total_dy()); + _region->note_dropped (_primary, total_dx (ev->button.state), total_dy(), _copy); } } diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index f235881d03..91fb37b82e 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -567,6 +567,7 @@ class NoteDrag : public Drag double _cumulative_dy; bool _was_selected; double _note_height; + bool _copy; }; class NoteCreateDrag : public Drag diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 5a4a5d6063..b0250f2986 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -1052,7 +1052,7 @@ MidiRegionView::note_diff_add_change (NoteBase* ev, } void -MidiRegionView::apply_diff (bool as_subcommand) +MidiRegionView::apply_diff (bool as_subcommand, bool was_copy) { bool add_or_remove; bool commit = false; @@ -1061,15 +1061,14 @@ MidiRegionView::apply_diff (bool as_subcommand) return; } - if ((add_or_remove = _note_diff_command->adds_or_removes())) { + if (!was_copy && (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()); } } - midi_view()->midi_track()->midi_playlist()->region_edited( - _region, _note_diff_command); + midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command); if (as_subcommand) { _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command); @@ -2587,68 +2586,190 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy) } } +NoteBase* +MidiRegionView::copy_selection () +{ + NoteBase* note; + _copy_drag_events.clear (); + + if (_selection.empty()) { + return 0; + } + + for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { + boost::shared_ptr g (new NoteType (*((*i)->note()))); + if (midi_view()->note_mode() == Sustained) { + Note* n = new Note (*this, _note_group, g); + update_sustained (n, false); + note = n; + } else { + Hit* h = new Hit (*this, _note_group, 10, g); + update_hit (h, false); + note = h; + } + + _copy_drag_events.push_back (note); + } + + return _copy_drag_events.front (); +} + void -MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) +MidiRegionView::move_copies (double dx, double dy, double cumulative_dy) +{ + typedef vector > PossibleChord; + PossibleChord to_play; + Evoral::Beats earliest = Evoral::MaxBeats; + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) { + if ((*i)->note()->time() < earliest) { + earliest = (*i)->note()->time(); + } + } + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) { + if ((*i)->note()->time() == earliest) { + to_play.push_back ((*i)->note()); + } + (*i)->move_event(dx, dy); + } + + if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) { + + if (to_play.size() > 1) { + + 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); + } + + start_playing_midi_chord (shifted); + + } else if (!to_play.empty()) { + + 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 +MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote, bool copy) { 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 + if (!copy) { + // 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; - } - TempoMap& map (trackview.session()->tempo_map()); - - start_note_diff_command (_("move notes")); - - for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { - - double const start_qn = _region->quarter_note() - midi_region()->start_beats(); - framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt; - Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn); - if (new_time < 0) { - continue; + 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); } - note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time); + /* + 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; + */ - uint8_t original_pitch = (*i)->note()->note(); - uint8_t new_pitch = original_pitch + dnote - highest_note_difference; + // 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; + } + TempoMap& map (trackview.session()->tempo_map()); - // keep notes in standard midi range - clamp_to_0_127(new_pitch); + start_note_diff_command (_("move notes")); - lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); - highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) { - note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch); + double const start_qn = _region->quarter_note() - midi_region()->start_beats(); + framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt; + Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn); + if (new_time < 0) { + continue; + } + + note_diff_add_change (*i, MidiModel::NoteDiffCommand::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); + + lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); + highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + + note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch); + } + } else { + + clear_editor_note_selection (); + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.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); + } + + // 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; + } + + TempoMap& map (trackview.session()->tempo_map()); + start_note_diff_command (_("copy notes")); + + for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) { + + /* update time */ + + double const start_qn = _region->quarter_note() - midi_region()->start_beats(); + framepos_t new_frames = map.frame_at_quarter_note (start_qn + (*i)->note()->time().to_double()) + dt; + Evoral::Beats new_time = Evoral::Beats (map.quarter_note_at_frame (new_frames) - start_qn); + + if (new_time < 0) { + continue; + } + + (*i)->note()->set_time (new_time); + + /* update pitch */ + + 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); + + lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); + highest_note_in_selection = std::max(highest_note_in_selection, new_pitch); + + note_diff_add_note ((*i)->note(), true); + + delete *i; + } + + _copy_drag_events.clear (); } - apply_diff(); + apply_diff (false, copy); // 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); + midi_stream_view()->set_note_range (MidiStreamView::ContentsRange); } } diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index e1af49abce..fd62312693 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -180,7 +180,7 @@ public: void note_diff_add_note (const boost::shared_ptr note, bool selected, bool show_velocity = false); void note_diff_remove_note (NoteBase* ev); - void apply_diff (bool as_subcommand = false); + void apply_diff (bool as_subcommand = false, bool was_copy = false); void abort_command(); void note_entered(NoteBase* ev); @@ -201,7 +201,9 @@ public: void invert_selection (); void move_selection(double dx, double dy, double cumulative_dy); - void note_dropped (NoteBase* ev, ARDOUR::frameoffset_t, int8_t d_note); + void note_dropped (NoteBase* ev, ARDOUR::frameoffset_t, int8_t d_note, bool copy); + NoteBase* copy_selection (); + void move_copies(double dx, double dy, double cumulative_dy); void select_notes (std::list); void select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend); @@ -412,6 +414,7 @@ private: typedef boost::unordered_map, NoteBase*> Events; typedef boost::unordered_map > PatchChanges; typedef std::vector< boost::shared_ptr > SysExes; + typedef std::vector CopyDragEvents; ARDOUR::BeatsFramesConverter _region_relative_time_converter; ARDOUR::BeatsFramesConverter _source_relative_time_converter; @@ -419,6 +422,7 @@ private: boost::shared_ptr _model; Events _events; + CopyDragEvents _copy_drag_events; PatchChanges _patch_changes; SysExes _sys_exes; Note** _active_notes;