mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-17 04:06:26 +01:00
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:
parent
4a03572cd9
commit
8dedea5ffa
4 changed files with 196 additions and 53 deletions
|
|
@ -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 )
|
//( 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_add;
|
||||||
TrackViewList tracks_to_remove;
|
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 ) )
|
if ( !_editor->selection->tracks.contains ( *i ) )
|
||||||
tracks_to_add.push_back ( *i );
|
tracks_to_add.push_back ( *i );
|
||||||
_editor->selection->add(tracks_to_add);
|
_editor->selection->add(tracks_to_add);
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue