implement copy-drag for MIDI notes.

Probably some corner cases to be fixed, but pretty functional and largely modelled
on existing code (paste, drag, step add note etc.)
This commit is contained in:
Paul Davis 2017-01-23 21:57:38 +01:00
parent 4a03572cd9
commit 8dedea5ffa
4 changed files with 196 additions and 53 deletions

View file

@ -5611,6 +5611,7 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
, _cumulative_dx (0) , _cumulative_dx (0)
, _cumulative_dy (0) , _cumulative_dy (0)
, _was_selected (false) , _was_selected (false)
, _copy (false)
{ {
DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n"); DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
@ -5624,6 +5625,13 @@ void
NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *) NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{ {
Drag::start_grab (event); 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 ())); setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
if (!(_was_selected = _primary->selected())) { if (!(_was_selected = _primary->selected())) {
@ -5720,8 +5728,13 @@ NoteDrag::total_dy () const
} }
void 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 */ /* Total change in x and y since the start of the drag */
frameoffset_t const dx = total_dx (event->button.state); frameoffset_t const dx = total_dx (event->button.state);
int8_t const dy = total_dy (); int8_t const dy = total_dy ();
@ -5737,7 +5750,11 @@ NoteDrag::motion (GdkEvent * event, bool)
int8_t note_delta = total_dy(); int8_t note_delta = total_dy();
if (tdx || tdy) { 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 /* 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 * don't know what that means because the selection may have
@ -5797,7 +5814,7 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
} }
} }
} else { } else {
_region->note_dropped (_primary, total_dx (ev->button.state), total_dy()); _region->note_dropped (_primary, total_dx (ev->button.state), total_dy(), _copy);
} }
} }

View file

@ -567,6 +567,7 @@ class NoteDrag : public Drag
double _cumulative_dy; double _cumulative_dy;
bool _was_selected; bool _was_selected;
double _note_height; double _note_height;
bool _copy;
}; };
class NoteCreateDrag : public Drag class NoteCreateDrag : public Drag

View file

@ -1052,7 +1052,7 @@ MidiRegionView::note_diff_add_change (NoteBase* ev,
} }
void void
MidiRegionView::apply_diff (bool as_subcommand) MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
{ {
bool add_or_remove; bool add_or_remove;
bool commit = false; bool commit = false;
@ -1061,15 +1061,14 @@ MidiRegionView::apply_diff (bool as_subcommand)
return; 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 // Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) { for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note()); _marked_for_selection.insert((*i)->note());
} }
} }
midi_view()->midi_track()->midi_playlist()->region_edited( midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command);
_region, _note_diff_command);
if (as_subcommand) { if (as_subcommand) {
_model->apply_command_as_subcommand (*trackview.session(), _note_diff_command); _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 void
MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) 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, bool copy)
{ {
uint8_t lowest_note_in_selection = 127; uint8_t lowest_note_in_selection = 127;
uint8_t highest_note_in_selection = 0; uint8_t highest_note_in_selection = 0;
uint8_t highest_note_difference = 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) { for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
uint8_t pitch = (*i)->note()->note(); uint8_t pitch = (*i)->note()->note();
lowest_note_in_selection = std::min(lowest_note_in_selection, pitch); lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
highest_note_in_selection = std::max(highest_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;
} }
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(); // Make sure the note pitch does not exceed the MIDI standard range
uint8_t new_pitch = original_pitch + dnote - highest_note_difference; 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 start_note_diff_command (_("move notes"));
clamp_to_0_127(new_pitch);
lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch); for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
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 // care about notes being moved beyond the upper/lower bounds on the canvas
if (lowest_note_in_selection < midi_stream_view()->lowest_note() || if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
highest_note_in_selection > midi_stream_view()->highest_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);
} }
} }

View 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_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity = false);
void note_diff_remove_note (NoteBase* ev); 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 abort_command();
void note_entered(NoteBase* ev); void note_entered(NoteBase* ev);
@ -201,7 +201,9 @@ public:
void invert_selection (); void invert_selection ();
void move_selection(double dx, double dy, double cumulative_dy); 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_notes (std::list<Evoral::event_id_t>);
void select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend); 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<boost::shared_ptr<NoteType>, NoteBase*> Events;
typedef boost::unordered_map<ARDOUR::MidiModel::PatchChangePtr, boost::shared_ptr<PatchChange> > PatchChanges; typedef boost::unordered_map<ARDOUR::MidiModel::PatchChangePtr, boost::shared_ptr<PatchChange> > PatchChanges;
typedef std::vector< boost::shared_ptr<SysEx> > SysExes; typedef std::vector< boost::shared_ptr<SysEx> > SysExes;
typedef std::vector<NoteBase*> CopyDragEvents;
ARDOUR::BeatsFramesConverter _region_relative_time_converter; ARDOUR::BeatsFramesConverter _region_relative_time_converter;
ARDOUR::BeatsFramesConverter _source_relative_time_converter; ARDOUR::BeatsFramesConverter _source_relative_time_converter;
@ -419,6 +422,7 @@ private:
boost::shared_ptr<ARDOUR::MidiModel> _model; boost::shared_ptr<ARDOUR::MidiModel> _model;
Events _events; Events _events;
CopyDragEvents _copy_drag_events;
PatchChanges _patch_changes; PatchChanges _patch_changes;
SysExes _sys_exes; SysExes _sys_exes;
Note** _active_notes; Note** _active_notes;