mechanism to allow Track (or other Route-derived type) to add its own processors...
[ardour.git] / gtk2_ardour / midi_region_view.cc
index 5a4a5d6063a2f5cdc4a6e637f5cce053f7978c79..9d5a6f86e601e11d382c6640a030b0870f065efb 100644 (file)
@@ -956,7 +956,6 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, ui
 
        start_note_diff_command(_("add note"));
 
-       clear_editor_note_selection ();
        note_diff_add_note (new_note, true, false);
 
        apply_diff();
@@ -1052,7 +1051,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 +1060,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);
@@ -1095,6 +1093,7 @@ MidiRegionView::abort_command()
 {
        delete _note_diff_command;
        _note_diff_command = 0;
+       trackview.editor().abort_reversible_command();
        clear_editor_note_selection();
 }
 
@@ -1145,6 +1144,18 @@ MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
        return boost::shared_ptr<PatchChange>();
 }
 
+boost::shared_ptr<SysEx>
+MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
+{
+       SysExes::const_iterator f = _sys_exes.find (s);
+
+       if (f != _sys_exes.end()) {
+               return f->second;
+       }
+
+       return boost::shared_ptr<SysEx>();
+}
+
 void
 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
 {
@@ -1205,43 +1216,11 @@ MidiRegionView::redisplay_model()
                if (note_in_region_range (note, visible)) {
                        if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
                                cne->validate ();
-                               bool update = false;
-
-                               if (note_in_region_range (note, visible)) {
-                                       if (visible) {
-                                               update = true;
-                                               cne->show ();
-                                       } else {
-                                               cne->hide ();
-                                       }
+                               if (visible) {
+                                       cne->show ();
                                } else {
                                        cne->hide ();
                                }
-                               if ((sus = dynamic_cast<Note*>(cne))) {
-
-                                       if (update) {
-                                               update_sustained (sus);
-                                       }
-
-                                       for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
-                                               MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
-                                               if (gr && !gr->trackview.hidden()) {
-                                                       gr->update_note (sus, !update);
-                                               }
-                                       }
-                               } else if ((hit = dynamic_cast<Hit*>(cne))) {
-
-                                       if (update) {
-                                               update_hit (hit);
-                                       }
-
-                                       for (std::vector<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
-                                               MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*i);
-                                               if (gr && !gr->trackview.hidden()) {
-                                                       gr->update_hit (hit, !update);
-                                               }
-                                       }
-                               }
                        } else {
                                missing_notes.insert (note);
                        }
@@ -1268,6 +1247,21 @@ MidiRegionView::redisplay_model()
                                i = _events.erase (i);
 
                        } else {
+                               bool visible = cne->item()->visible();
+
+                               if ((sus = dynamic_cast<Note*>(cne))) {
+
+                                       if (visible) {
+                                               update_sustained (sus);
+                                       }
+
+                               } else if ((hit = dynamic_cast<Hit*>(cne))) {
+
+                                       if (visible) {
+                                               update_hit (hit);
+                                       }
+
+                               }
                                ++i;
                        }
                }
@@ -1295,7 +1289,12 @@ MidiRegionView::redisplay_model()
                }
        }
 
-       _sys_exes.clear();
+       for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
+               MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
+               if (gr && !gr->trackview.hidden()) {
+                       gr->redisplay_model ();
+               }
+       }
 
        display_sysexes();
        display_patch_changes ();
@@ -1340,7 +1339,7 @@ MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_c
                                const double x = trackview.editor().sample_to_pixel (region_frames);
                                const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
                                p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
-                               p->flag()->set_text (patch_name);
+                               p->set_text (patch_name);
 
                                p->show();
                        }
@@ -1388,7 +1387,8 @@ MidiRegionView::display_sysexes()
        }
 
        for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
-               Evoral::Beats time = (*i)->time();
+               MidiModel::SysExPtr sysex_ptr = *i;
+               Evoral::Beats time = sysex_ptr->time();
 
                if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
                        if (!display_periodic_messages) {
@@ -1412,9 +1412,16 @@ MidiRegionView::display_sysexes()
 
                // CAIROCANVAS: no longer passing *i (the sysex event) to the
                // SysEx canvas object!!!
+               boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
 
-               boost::shared_ptr<SysEx> sysex = boost::shared_ptr<SysEx>(
-                       new SysEx (*this, _note_group, text, height, x, 1.0));
+               if (!sysex) {
+                       sysex = boost::shared_ptr<SysEx>(
+                               new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
+                       _sys_exes.insert (make_pair (sysex_ptr, sysex));
+               } else {
+                       sysex->set_height (height);
+                       sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
+               }
 
                // Show unless message is beyond the region bounds
                if (time - _region->start() >= _region->length() || time < _region->start()) {
@@ -1422,8 +1429,6 @@ MidiRegionView::display_sysexes()
                } else {
                        sysex->show();
                }
-
-               _sys_exes.push_back(sysex);
        }
 }
 
@@ -1486,7 +1491,7 @@ MidiRegionView::reset_width_dependent_items (double pixel_width)
        bool hide_all = false;
        PatchChanges::iterator x = _patch_changes.begin();
        if (x != _patch_changes.end()) {
-               hide_all = x->second->flag()->width() >= _pixel_width;
+               hide_all = x->second->width() >= _pixel_width;
        }
 
        if (hide_all) {
@@ -2077,7 +2082,6 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const
 void
 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
 {
-       MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
        string name = _("add patch change");
 
        trackview.editor().begin_reversible_command (name);
@@ -2085,7 +2089,7 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beat
        c->add (MidiModel::PatchChangePtr (
                        new Evoral::PatchChange<Evoral::Beats> (
                                absolute_frames_to_source_beats (_region->position() + t),
-                               mtv->get_channel_for_add(), patch.program(), patch.bank()
+                               patch.channel(), patch.program(), patch.bank()
                                )
                        )
                );
@@ -2544,11 +2548,9 @@ MidiRegionView::add_to_selection (NoteBase* ev)
        }
 }
 
-void
-MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
+Evoral::Beats
+MidiRegionView::earliest_in_selection ()
 {
-       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
-       PossibleChord to_play;
        Evoral::Beats earliest = Evoral::MaxBeats;
 
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
@@ -2557,11 +2559,47 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
                }
        }
 
+       return earliest;
+}
+
+void
+MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
+{
+       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+       Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
+       TempoMap& tmap (editor->session()->tempo_map());
+       PossibleChord to_play;
+       Evoral::Beats earliest = earliest_in_selection();
+
        for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
-               if ((*i)->note()->time() == earliest) {
-                       to_play.push_back ((*i)->note());
+               NoteBase* n = *i;
+               if (n->note()->time() == earliest) {
+                       to_play.push_back (n->note());
                }
+               double const note_time_qn = session_relative_qn (n->note()->time().to_double());
+               double dx = 0.0;
+               if (midi_view()->note_mode() == Sustained) {
+                       dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                               - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
+               } else {
+                       /* Hit::x0() is offset by _position.x, unlike Note::x0() */
+                       Hit* hit = dynamic_cast<Hit*>(n);
+                       if (hit) {
+                               dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                                       - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
+                       }
+               }
+
                (*i)->move_event(dx, dy);
+
+               /* update length */
+               if (midi_view()->note_mode() == Sustained) {
+                       Note* sus = dynamic_cast<Note*> (*i);
+                       double const len_dx = editor->sample_to_pixel_unrounded (
+                               tmap.frame_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
+
+                       sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
+               }
        }
 
        if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
@@ -2587,68 +2625,210 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
        }
 }
 
+NoteBase*
+MidiRegionView::copy_selection (NoteBase* primary)
+{
+       _copy_drag_events.clear ();
+
+       if (_selection.empty()) {
+               return 0;
+       }
+
+       NoteBase* note;
+       NoteBase* ret = 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;
+               }
+
+               if ((*i) == primary) {
+                       ret = note;
+               }
+
+               _copy_drag_events.push_back (note);
+       }
+
+       return ret;
+}
+
+void
+MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
+{
+       typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+       Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
+       TempoMap& tmap (editor->session()->tempo_map());
+       PossibleChord to_play;
+       Evoral::Beats earliest = earliest_in_selection();
+
+       for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
+               NoteBase* n = *i;
+               if (n->note()->time() == earliest) {
+                       to_play.push_back (n->note());
+               }
+               double const note_time_qn = session_relative_qn (n->note()->time().to_double());
+               double dx = 0.0;
+               if (midi_view()->note_mode() == Sustained) {
+                       dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                               - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
+               } else {
+                       Hit* hit = dynamic_cast<Hit*>(n);
+                       if (hit) {
+                               dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+                                       - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
+                       }
+               }
+
+               (*i)->move_event(dx, dy);
+
+               if (midi_view()->note_mode() == Sustained) {
+                       Note* sus = dynamic_cast<Note*> (*i);
+                       double const len_dx = editor->sample_to_pixel_unrounded (
+                               tmap.frame_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
+
+                       sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
+               }
+       }
+
+       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 *, double d_qn, 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;
+               }
 
-       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) {
+
+                       Evoral::Beats new_time = Evoral::Beats ((*i)->note()->time().to_double() + d_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;
                }
 
-               note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
+               start_note_diff_command (_("copy notes"));
+
+               for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
+
+                       /* update time */
+                       Evoral::Beats new_time = Evoral::Beats ((*i)->note()->time().to_double() + d_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;
+                       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);
+                       (*i)->note()->set_note (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);
+                       // 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);
        }
 }
 
@@ -2661,7 +2841,7 @@ framepos_t
 MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
 {
        PublicEditor& editor (trackview.editor());
-       return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap);
+       return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap).frame;
 }
 
 /** @param x Pixel relative to the region position.
@@ -3313,21 +3493,20 @@ MidiRegionView::nudge_notes (bool forward, bool fine)
 
                /* use grid */
 
-               framepos_t next_pos = ref_point;
-
+               MusicFrame next_pos (ref_point, 0);
                if (forward) {
-                       if (max_framepos - 1 < next_pos) {
-                               next_pos += 1;
+                       if (max_framepos - 1 < next_pos.frame) {
+                               next_pos.frame += 1;
                        }
                } else {
-                       if (next_pos == 0) {
+                       if (next_pos.frame == 0) {
                                return;
                        }
-                       next_pos -= 1;
+                       next_pos.frame -= 1;
                }
 
                trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
-               const framecnt_t distance = ref_point - next_pos;
+               const framecnt_t distance = ref_point - next_pos.frame;
                delta = region_frames_to_region_beats (fabs ((double)distance));
        }
 
@@ -4241,3 +4420,9 @@ MidiRegionView::note_to_y(uint8_t note) const
 {
        return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
 }
+
+double
+MidiRegionView::session_relative_qn (double qn) const
+{
+       return qn + (region()->quarter_note() - midi_region()->start_beats());
+}