Implement "multi-paste" for notes, regions, and automation.
The idea here is that pasting several times to the same location doesn't make sense. Instead, the paste is appended past the last paste, snapped to the grid. This make it simple to replicate a given section a number of times, simply by copying once and pasting several times. This behaviour only appears when successive pastes are done to the same location (whatever the edit point is). When the paste point changes, the "multi-paste" state is reset. Boots 'n cats 'n boots 'n cats.
This commit is contained in:
parent
b01d1813f8
commit
31acd96384
|
@ -637,7 +637,7 @@ AutomationTimeAxisView::add_automation_event (GdkEvent* event, framepos_t when,
|
|||
* @param nth Index of the AutomationList within the selection to paste from.
|
||||
*/
|
||||
bool
|
||||
AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
|
||||
AutomationTimeAxisView::paste (framepos_t pos, unsigned paste_count, float times, Selection& selection, size_t nth)
|
||||
{
|
||||
boost::shared_ptr<AutomationLine> line;
|
||||
|
||||
|
@ -651,11 +651,11 @@ AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection
|
|||
return false;
|
||||
}
|
||||
|
||||
return paste_one (*line, pos, times, selection, nth);
|
||||
return paste_one (*line, pos, paste_count, times, selection, nth);
|
||||
}
|
||||
|
||||
bool
|
||||
AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
|
||||
AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, unsigned paste_count, float times, Selection& selection, size_t nth)
|
||||
{
|
||||
AutomationSelection::iterator p;
|
||||
boost::shared_ptr<AutomationList> alist(line.the_list());
|
||||
|
@ -671,6 +671,9 @@ AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float t
|
|||
return false;
|
||||
}
|
||||
|
||||
/* add multi-paste offset if applicable */
|
||||
pos += _editor.get_paste_offset(pos, paste_count, (*p)->length());
|
||||
|
||||
double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
|
||||
|
||||
XMLNode &before = alist->get_state();
|
||||
|
|
|
@ -93,7 +93,7 @@ class AutomationTimeAxisView : public TimeAxisView {
|
|||
/* editing operations */
|
||||
|
||||
void cut_copy_clear (Selection&, Editing::CutCopyOp);
|
||||
bool paste (ARDOUR::framepos_t, float times, Selection&, size_t nth);
|
||||
bool paste (ARDOUR::framepos_t, unsigned paste_count, float times, Selection&, size_t nth);
|
||||
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
|
@ -171,7 +171,7 @@ class AutomationTimeAxisView : public TimeAxisView {
|
|||
void build_display_menu ();
|
||||
|
||||
void cut_copy_clear_one (AutomationLine&, Selection&, Editing::CutCopyOp);
|
||||
bool paste_one (AutomationLine&, ARDOUR::framepos_t, float times, Selection&, size_t nth);
|
||||
bool paste_one (AutomationLine&, ARDOUR::framepos_t, unsigned, float times, Selection&, size_t nth);
|
||||
void route_going_away ();
|
||||
|
||||
void set_automation_state (ARDOUR::AutoState);
|
||||
|
|
|
@ -312,6 +312,8 @@ Editor::Editor ()
|
|||
clicked_control_point = 0;
|
||||
last_update_frame = 0;
|
||||
pre_press_cursor = 0;
|
||||
last_paste_pos = 0;
|
||||
paste_count = 0;
|
||||
_drags = new DragManager (this);
|
||||
lock_dialog = 0;
|
||||
ruler_dialog = 0;
|
||||
|
@ -3856,6 +3858,31 @@ Editor::playlist_selector () const
|
|||
return *_playlist_selector;
|
||||
}
|
||||
|
||||
framecnt_t
|
||||
Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t duration)
|
||||
{
|
||||
if (paste_count == 0) {
|
||||
/* don't bother calculating an offset that will be zero anyway */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* calculate basic unsnapped multi-paste offset */
|
||||
framecnt_t offset = paste_count * duration;
|
||||
|
||||
bool success = true;
|
||||
double snap_beats = get_grid_type_as_beats(success, pos);
|
||||
if (success) {
|
||||
/* we're snapped to something musical, round duration up */
|
||||
BeatsFramesConverter conv(_session->tempo_map(), pos);
|
||||
const Evoral::MusicalTime dur_beats = conv.from(duration);
|
||||
const framecnt_t snap_dur_beats = ceil(dur_beats / snap_beats) * snap_beats;
|
||||
|
||||
offset = paste_count * conv.to(snap_dur_beats);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
Evoral::MusicalTime
|
||||
Editor::get_grid_type_as_beats (bool& success, framepos_t position)
|
||||
{
|
||||
|
|
|
@ -313,6 +313,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
|
|||
/* nudge is initiated by transport controls owned by ARDOUR_UI */
|
||||
|
||||
framecnt_t get_nudge_distance (framepos_t pos, framecnt_t& next);
|
||||
framecnt_t get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t duration);
|
||||
Evoral::MusicalTime get_grid_type_as_beats (bool& success, framepos_t position);
|
||||
|
||||
void nudge_forward (bool next, bool force_playhead);
|
||||
|
@ -1109,6 +1110,11 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
|
|||
Gtkmm2ext::ActionMap editor_action_map;
|
||||
Gtkmm2ext::Bindings key_bindings;
|
||||
|
||||
/* CUT/COPY/PASTE */
|
||||
|
||||
framepos_t last_paste_pos;
|
||||
unsigned paste_count;
|
||||
|
||||
void cut_copy (Editing::CutCopyOp);
|
||||
bool can_cut_copy () const;
|
||||
void cut_copy_points (Editing::CutCopyOp);
|
||||
|
|
|
@ -4364,6 +4364,15 @@ Editor::paste_internal (framepos_t position, float times)
|
|||
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("preferred edit position is %1\n", position));
|
||||
}
|
||||
|
||||
if (position == last_paste_pos) {
|
||||
/* repeated paste in the same position */
|
||||
++paste_count;
|
||||
} else {
|
||||
/* paste in new location, reset repeated paste state */
|
||||
paste_count = 0;
|
||||
last_paste_pos = position;
|
||||
}
|
||||
|
||||
TrackViewList ts;
|
||||
TrackViewList::iterator i;
|
||||
size_t nth;
|
||||
|
@ -4401,7 +4410,7 @@ Editor::paste_internal (framepos_t position, float times)
|
|||
cb != cut_buffer->midi_notes.end() && r != rs.end(); ++r) {
|
||||
MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*r);
|
||||
if (mrv) {
|
||||
mrv->paste (position, times, **cb);
|
||||
mrv->paste (position, paste_count, times, **cb);
|
||||
++cb;
|
||||
}
|
||||
}
|
||||
|
@ -4413,7 +4422,7 @@ Editor::paste_internal (framepos_t position, float times)
|
|||
begin_reversible_command (Operations::paste);
|
||||
|
||||
for (nth = 0, i = ts.begin(); i != ts.end(); ++i, ++nth) {
|
||||
(*i)->paste (position, times, *cut_buffer, nth);
|
||||
(*i)->paste (position, paste_count, times, *cut_buffer, nth);
|
||||
}
|
||||
|
||||
commit_reversible_command ();
|
||||
|
|
|
@ -3321,25 +3321,37 @@ MidiRegionView::selection_as_cut_buffer () const
|
|||
|
||||
/** This method handles undo */
|
||||
void
|
||||
MidiRegionView::paste (framepos_t pos, float times, const MidiCutBuffer& mcb)
|
||||
MidiRegionView::paste (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
|
||||
{
|
||||
if (mcb.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PublicEditor& editor = trackview.editor ();
|
||||
|
||||
trackview.session()->begin_reversible_command (_("paste"));
|
||||
|
||||
start_note_diff_command (_("paste"));
|
||||
|
||||
const Evoral::MusicalTime pos_beats = absolute_frames_to_source_beats (pos);
|
||||
const Evoral::MusicalTime first_time = (*mcb.notes().begin())->time();
|
||||
const Evoral::MusicalTime last_time = (*mcb.notes().rbegin())->end_time();
|
||||
Evoral::MusicalTime end_point = 0;
|
||||
/* get snap duration, default to 1 beat if not snapped to anything musical */
|
||||
bool success = true;
|
||||
double snap_beats = editor.get_grid_type_as_beats(success, pos);
|
||||
if (!success) {
|
||||
snap_beats = 1.0;
|
||||
}
|
||||
|
||||
const Evoral::MusicalTime first_time = (*mcb.notes().begin())->time();
|
||||
const Evoral::MusicalTime last_time = (*mcb.notes().rbegin())->end_time();
|
||||
const Evoral::MusicalTime duration = last_time - first_time;
|
||||
const Evoral::MusicalTime snap_duration = ceil(duration / snap_beats) * snap_beats;
|
||||
const Evoral::MusicalTime paste_offset = paste_count * snap_duration;
|
||||
const Evoral::MusicalTime pos_beats = absolute_frames_to_source_beats(pos) + paste_offset;
|
||||
Evoral::MusicalTime end_point = 0;
|
||||
|
||||
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
|
||||
first_time,
|
||||
last_time,
|
||||
last_time - first_time, pos, _region->position(),
|
||||
duration, pos, _region->position(),
|
||||
pos_beats));
|
||||
|
||||
clear_selection ();
|
||||
|
|
|
@ -112,7 +112,7 @@ public:
|
|||
void resolve_note(uint8_t note_num, double end_time);
|
||||
|
||||
void cut_copy_clear (Editing::CutCopyOp);
|
||||
void paste (framepos_t pos, float times, const MidiCutBuffer&);
|
||||
void paste (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer&);
|
||||
|
||||
void add_canvas_patch_change (ARDOUR::MidiModel::PatchChangePtr patch, const std::string& displaytext, bool);
|
||||
|
||||
|
|
|
@ -299,6 +299,7 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible, publi
|
|||
virtual void foreach_time_axis_view (sigc::slot<void,TimeAxisView&>) = 0;
|
||||
virtual void add_to_idle_resize (TimeAxisView*, int32_t) = 0;
|
||||
virtual framecnt_t get_nudge_distance (framepos_t pos, framecnt_t& next) = 0;
|
||||
virtual framecnt_t get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t duration) = 0;
|
||||
virtual Evoral::MusicalTime get_grid_type_as_beats (bool& success, framepos_t position) = 0;
|
||||
virtual void edit_notes (TimeAxisViewItem&) = 0;
|
||||
|
||||
|
|
|
@ -1534,7 +1534,7 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
|
|||
}
|
||||
|
||||
bool
|
||||
RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
|
||||
RouteTimeAxisView::paste (framepos_t pos, unsigned paste_count, float times, Selection& selection, size_t nth)
|
||||
{
|
||||
if (!is_track()) {
|
||||
return false;
|
||||
|
@ -1556,6 +1556,11 @@ RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, siz
|
|||
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("modified paste to %1\n", pos));
|
||||
}
|
||||
|
||||
/* add multi-paste offset if applicable */
|
||||
std::pair<framepos_t, framepos_t> extent = (*p)->get_extent();
|
||||
const framecnt_t duration = extent.second - extent.first;
|
||||
pos += _editor.get_paste_offset(pos, paste_count, duration);
|
||||
|
||||
pl->clear_changes ();
|
||||
if (Config->get_edit_mode() == Ripple) {
|
||||
std::pair<framepos_t, framepos_t> extent = (*p)->get_extent_with_endspace();
|
||||
|
|
|
@ -99,7 +99,7 @@ public:
|
|||
|
||||
/* Editing operations */
|
||||
void cut_copy_clear (Selection&, Editing::CutCopyOp);
|
||||
bool paste (ARDOUR::framepos_t, float times, Selection&, size_t nth);
|
||||
bool paste (ARDOUR::framepos_t, unsigned paste_count, float times, Selection&, size_t nth);
|
||||
RegionView* combine_regions ();
|
||||
void uncombine_regions ();
|
||||
void uncombine_region (RegionView*);
|
||||
|
|
|
@ -165,7 +165,7 @@ class TimeAxisView : public virtual AxisView
|
|||
/* editing operations */
|
||||
|
||||
virtual void cut_copy_clear (Selection&, Editing::CutCopyOp) {}
|
||||
virtual bool paste (ARDOUR::framepos_t, float /*times*/, Selection&, size_t /*nth*/) { return false; }
|
||||
virtual bool paste (ARDOUR::framepos_t, unsigned /*paste_count*/, float /*times*/, Selection&, size_t /*nth*/) { return false; }
|
||||
|
||||
virtual void set_selected_regionviews (RegionSelection&) {}
|
||||
virtual void set_selected_points (PointSelection&) {}
|
||||
|
|
Loading…
Reference in New Issue