13
0

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

@ -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);
}
}

View File

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

View 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::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 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);
}
}

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_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;