implement copy-drag for MIDI notes.
authorPaul Davis <paul@linuxaudiosystems.com>
Mon, 23 Jan 2017 20:57:38 +0000 (21:57 +0100)
committerPaul Davis <paul@linuxaudiosystems.com>
Mon, 23 Jan 2017 20:58:02 +0000 (21:58 +0100)
Probably some corner cases to be fixed, but pretty functional and largely modelled
on existing code (paste, drag, step add note etc.)

gtk2_ardour/editor_drag.cc
gtk2_ardour/editor_drag.h
gtk2_ardour/midi_region_view.cc
gtk2_ardour/midi_region_view.h

index 91a1b523489d20c65fe2f582da143d9b63bf0202..cdbdf7fce8287b8e8c6ab8d889616bcd6f90d5b5 100644 (file)
@@ -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);
        }
 }
 
index f235881d035287376d7bcabd1ce77d828f3b8b6a..91fb37b82eee155217ea5e93a915db59b165ba5e 100644 (file)
@@ -567,6 +567,7 @@ class NoteDrag : public Drag
        double _cumulative_dy;
        bool   _was_selected;
        double _note_height;
+       bool   _copy;
 };
 
 class NoteCreateDrag : public Drag
index 5a4a5d6063a2f5cdc4a6e637f5cce053f7978c79..b0250f298602c4e6335f88bf5e4360e7796380ab 100644 (file)
@@ -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<NoteType> 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::move_copies (double dx, double dy, double cumulative_dy)
+{
+       typedef vector<boost::shared_ptr<NoteType> > 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<NoteType> 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<NoteType> 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)
+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;
-       */
+               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);
+               }
 
-       // 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());
+               /*
+                 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;
+               */
 
-       start_note_diff_command (_("move notes"));
+               // 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());
 
-       for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
+               start_note_diff_command (_("move notes"));
 
-               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) {
+
+                       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"));
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
+               for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
 
-               uint8_t original_pitch = (*i)->note()->note();
-               uint8_t new_pitch      = original_pitch + dnote - highest_note_difference;
+                       /* update time */
 
-               // keep notes in standard midi range
-               clamp_to_0_127(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);
 
-               lowest_note_in_selection  = std::min(lowest_note_in_selection,  new_pitch);
-               highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+                       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);
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, 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);
        }
 }
 
index e1af49abceb7421d67048fa29da1a792ef7433fc..fd62312693c1b789f43c6d714352deaa4e8effd6 100644 (file)
@@ -180,7 +180,7 @@ public:
        void note_diff_add_note (const boost::shared_ptr<NoteType> 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<Evoral::event_id_t>);
        void select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend);
@@ -412,6 +414,7 @@ private:
        typedef boost::unordered_map<boost::shared_ptr<NoteType>, NoteBase*>                 Events;
        typedef boost::unordered_map<ARDOUR::MidiModel::PatchChangePtr, boost::shared_ptr<PatchChange> > PatchChanges;
        typedef std::vector< boost::shared_ptr<SysEx> >                                      SysExes;
+       typedef std::vector<NoteBase*> CopyDragEvents;
 
        ARDOUR::BeatsFramesConverter _region_relative_time_converter;
        ARDOUR::BeatsFramesConverter _source_relative_time_converter;
@@ -419,6 +422,7 @@ private:
 
        boost::shared_ptr<ARDOUR::MidiModel> _model;
        Events                               _events;
+       CopyDragEvents                       _copy_drag_events;
        PatchChanges                         _patch_changes;
        SysExes                              _sys_exes;
        Note**                               _active_notes;