From 0026399358e04ccac5e49dd79f4c832730060370 Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Mon, 10 Feb 2014 19:41:11 +0000 Subject: [PATCH 01/58] Splice mode: exclude newly-added regions from being shuffled When adding regions in splice mode, exclude the region being added from possibly being shuffled. I don't know whether this might have some other adverse effects, but it fixes an obvious defect in 'Splice' mode where newly-recorded regions 'jump' to the playhead position when recording stops, and since splice mode is pretty much broken anyway, I don't think it can make matters any worse. --- libs/ardour/playlist.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index 47462a3575..d939ba61b0 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -706,7 +706,7 @@ Playlist::flush_notifications (bool from_undo) } } - possibly_splice_unlocked (position, (pos + length) - position, boost::shared_ptr()); + possibly_splice_unlocked (position, (pos + length) - position, region); } void From ce8e374cf8ff1c8f9611cba89ba4e9509e262878 Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Sat, 8 Mar 2014 17:11:37 +0000 Subject: [PATCH 02/58] Splice mode: fix comment typo --- gtk2_ardour/editor_drag.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index a3c07b8672..191d111b0d 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -1410,7 +1410,7 @@ RegionSpliceDrag::motion (GdkEvent* event, bool) if (!tv || !tv->is_track()) { /* To make sure we hide the verbose canvas cursor when the mouse is - not held over and audiotrack. + not held over an audio track. */ _editor->verbose_cursor()->hide (); return; From d75c7151d4cf306f1961e7a967ea129c6fd8d153 Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Wed, 23 Oct 2013 21:50:01 +0100 Subject: [PATCH 03/58] Ripple mode: basic implementation Add a value for Ripple to EditMode enum. Add Ripple edit mode to edit mode dropdown, by adding it to the Editor::build_edit_mode_menu() helper function, and remove the old code that added items to the (now unused) Editor::edit_mode_strings. Add the regions that should be affected by the drag to RegionDrag::_views so that the drag carries them along automatically. Use a copy of the RegionList in Playlist::core_ripple(), since bad things happen when iterating over regions and they get moved around in the list. Handle rippling in removal of regions from playlist. When dragging in ripple mode, exclude all regions that lie before the original start position of the selected regions being dragged from rippling: this is what Mixbus does. Make editor dragging respect snap-to settings, by using the existing compute_x_delta() function, which did almost the right thing. Move setting of _last_frame_position out of that function so all ripple-dragged regions can move. Ripple when dragging from region list: even though Mixbus doesn't do this, it seems like a good idea. Prevent multi-track selection being dragged across tracks, by making RegionMotionDrag::y_movement_allowed() virtual, and overriding it in RegionRippleDrag to forbid dragging of selections containing regions on more than one track to dofferent tracks in ripple mode. Remember which TimeAxisView a ripple-mode drag that's allowed cross-track drags started from, so that the effect of rippling regions after any region that's dragged off that track can be undone. --- gtk2_ardour/editor.cc | 10 +- gtk2_ardour/editor.h | 2 +- gtk2_ardour/editor_actions.cc | 1 + gtk2_ardour/editor_audio_import.cc | 3 + gtk2_ardour/editor_drag.cc | 269 ++++++++++++++++++++++++++++- gtk2_ardour/editor_drag.h | 43 ++++- gtk2_ardour/editor_mouse.cc | 16 +- gtk2_ardour/editor_ops.cc | 20 ++- gtk2_ardour/route_time_axis.cc | 5 + libs/ardour/ardour/playlist.h | 14 ++ libs/ardour/ardour/types.h | 1 + libs/ardour/enums.cc | 1 + libs/ardour/playlist.cc | 71 +++++++- libs/ardour/utils.cc | 5 + 14 files changed, 431 insertions(+), 30 deletions(-) diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index f111aa18b1..87a70102a8 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -2904,12 +2904,6 @@ Editor::setup_toolbar () mouse_mode_box->pack_start (*mouse_mode_align, false, false); - edit_mode_strings.push_back (edit_mode_to_string (Slide)); - if (!Profile->get_sae()) { - edit_mode_strings.push_back (edit_mode_to_string (Splice)); - } - edit_mode_strings.push_back (edit_mode_to_string (Lock)); - edit_mode_selector.set_name ("mouse mode button"); edit_mode_selector.set_size_request (65, -1); edit_mode_selector.add_elements (ArdourButton::Inset); @@ -3126,6 +3120,7 @@ Editor::build_edit_mode_menu () edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Slide), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Slide))); edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Splice), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Splice))); + edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Ripple), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Ripple))); edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Lock), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Lock))); } @@ -3437,10 +3432,11 @@ Editor::cycle_edit_mode () if (Profile->get_sae()) { Config->set_edit_mode (Lock); } else { - Config->set_edit_mode (Splice); + Config->set_edit_mode (Ripple); } break; case Splice: + case Ripple: Config->set_edit_mode (Lock); break; case Lock: diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 1398936979..e0c642b4fc 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -1613,7 +1613,6 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void edit_mode_selection_done ( ARDOUR::EditMode m ); void build_edit_mode_menu (); Gtk::VBox edit_mode_box; - std::vector edit_mode_strings; void set_edit_mode (ARDOUR::EditMode); void cycle_edit_mode (); @@ -2092,6 +2091,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD friend class RegionDrag; friend class RegionMoveDrag; friend class RegionSpliceDrag; + friend class RegionRippleDrag; friend class TrimDrag; friend class MeterMarkerDrag; friend class TempoMarkerDrag; diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index d8889a9c81..1072b497c2 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -476,6 +476,7 @@ Editor::register_actions () ActionManager::register_action (editor_actions, "cycle-edit-point-with-marker", _("Change Edit Point Including Marker"), sigc::bind (sigc::mem_fun (*this, &Editor::cycle_edit_point), true)); if (!Profile->get_sae()) { ActionManager::register_action (editor_actions, "set-edit-splice", _("Splice"), sigc::bind (sigc::mem_fun (*this, &Editor::set_edit_mode), Splice)); + ActionManager::register_action (editor_actions, "set-edit-ripple", _("Ripple"), bind (mem_fun (*this, &Editor::set_edit_mode), Ripple)); } ActionManager::register_action (editor_actions, "set-edit-slide", _("Slide"), sigc::bind (sigc::mem_fun (*this, &Editor::set_edit_mode), Slide)); ActionManager::register_action (editor_actions, "set-edit-lock", _("Lock"), sigc::bind (sigc::mem_fun (*this, &Editor::set_edit_mode), Lock)); diff --git a/gtk2_ardour/editor_audio_import.cc b/gtk2_ardour/editor_audio_import.cc index da70df5c15..7a5f3982bf 100644 --- a/gtk2_ardour/editor_audio_import.cc +++ b/gtk2_ardour/editor_audio_import.cc @@ -888,6 +888,9 @@ Editor::finish_bringing_in_material (boost::shared_ptr region, uint32_t boost::shared_ptr copy (RegionFactory::create (region, region->properties())); playlist->clear_changes (); playlist->add_region (copy, pos); + if (Config->get_edit_mode() == Ripple) + playlist->ripple (pos, copy->length(), copy); + _session->add_command (new StatefulDiffCommand (playlist)); break; } diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 191d111b0d..420fc52d31 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -599,7 +599,6 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r } } - _last_frame_position = *pending_region_position; } return dx; @@ -667,6 +666,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) /* Work out the change in x */ framepos_t pending_region_position; double const x_delta = compute_x_delta (event, &pending_region_position); + _last_frame_position = pending_region_position; /* Work out the change in y */ @@ -1197,7 +1197,7 @@ RegionMoveDrag::remove_region_from_playlist ( playlist->clear_changes (); } - playlist->remove_region (region); + playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag } @@ -1257,11 +1257,15 @@ RegionMoveDrag::collect_new_region_view (RegionView* rv) void RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists) { + std::cerr << "RegionMoveDrag::add_stateful_diff_commands_for_playlists ()" << std::endl; for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) { + std::cerr << "playlist: " << (*i)->name() << std::endl; StatefulDiffCommand* c = new StatefulDiffCommand (*i); if (!c->empty()) { + std::cerr << "added StatefulDiffCommand!" << std::endl; _editor->session()->add_command (c); } else { + std::cerr << "no StatefulDiffcommand to add..." << std::endl; delete c; } } @@ -1368,6 +1372,12 @@ RegionInsertDrag::finished (GdkEvent *, bool) _editor->begin_reversible_command (Operations::insert_region); playlist->clear_changes (); playlist->add_region (_primary->region (), _last_frame_position); + + // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably + if (Config->get_edit_mode() == Ripple) { + playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region()); + } + _editor->session()->add_command (new StatefulDiffCommand (playlist)); _editor->commit_reversible_command (); @@ -1424,10 +1434,11 @@ RegionSpliceDrag::motion (GdkEvent* event, bool) dir = -1; } - RegionSelection copy (_editor->selection->regions); - - RegionSelectionByPosition cmp; - copy.sort (cmp); + // RegionSelection copy (_editor->selection->regions); + // RegionSelectionByPosition cmp; + // copy.sort (cmp); + RegionSelection copy; + _editor->selection->regions.by_position(copy); framepos_t const pf = adjusted_current_frame (event); @@ -1476,6 +1487,252 @@ RegionSpliceDrag::aborted (bool) /* XXX: TODO */ } +/*** + * ripple mode... + */ + +void +RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress) +{ + RegionSelection to_ripple; + TrackViewList tracks; + tracks.push_back (tav); // rv.get_time_axis_view ()); + + _editor->get_regions_after (to_ripple, where, tracks); + + for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) { + if (!exclude.contains (*i)) { + // the selection has already been added to _views + + if (drag_in_progress) { + (*i)->drag_start(); + ArdourCanvas::Group* rvg = (*i)->get_canvas_group(); + Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0)); + rvg->reparent (_editor->_region_motion_group); + (*i)->fake_set_opaque (true); + rvg->set_position (rv_canvas_offset); + } + _views.push_back (DraggingView (*i, this)); + } + } +} + +void +RegionRippleDrag::remove_unselected_from_views(framecnt_t amount) +{ + + std::cerr << "_views contains " << _views.size() << " views, including those on " << prev_tav->name() << std::endl; + + for (std::list::iterator i = _views.begin(); i != _views.end(); ) { + // we added all the regions after the selection + std::cerr << "iterating _views..." << std::endl; + std::cerr << "found " << i->view->region()->name() << " in _views..." << std::endl; + + std::list::iterator to_erase = i++; + if (!_editor->selection->regions.contains (to_erase->view)) { + std::cerr << "removing " << to_erase->view->region()->name() << " from _views..." << std::endl; + // restore the non-selected regions to their original playlist & positions, + // and then ripple them back by the length of the regions that were dragged away + // do the same things as RegionMotionDrag::aborted + + if (_item) { + _item->ungrab (); + } + + RegionView *rv = to_erase->view; + +#if 0 + // this is how RegionMotionDrag::aborted() does it... + TimeAxisView* tv = &(rv->get_time_axis_view ()); + RouteTimeAxisView* rtv = dynamic_cast (tv); + assert (rtv); + assert (rtv == prev_tav); + rv->get_canvas_group()->reparent (*rtv->view()->canvas_item()); +#else + // this should be equivalent... + rv->get_canvas_group()->reparent(prev_tav->view()->canvas_item()); +#endif + rv->get_canvas_group()->set_y_position (0); + rv->drag_end (); + rv->fake_set_opaque (false); + rv->move(-amount, 0); // XXX second parameter is y delta - do we need to do something? + // rv->set_height (rtv->view()->child_height ()); + + _views.erase (to_erase); + if (i == _views.end()) { + std::cerr << "reached end of _views iterator in loop!" << std::endl; + // break; + } + } + } +} + +RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list const & v) + : RegionMoveDrag (e, i, p, v, false, false) +{ + DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n"); + // compute length of selection + RegionSelection selected_regions = _editor->selection->regions; + selection_length = selected_regions.end_frame() - selected_regions.start(); + + // we'll only allow dragging to another track in ripple mode if all the regions + // being dragged start off on the same track + allow_moves_across_tracks = (selected_regions.playlists().size() == 1); + prev_tav = NULL; + prev_amount = 0; + exclude = new RegionList; + for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) { + exclude->push_back((*i)->region()); + } + + // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple + RegionSelection copy; + selected_regions.by_position(copy); // get selected regions sorted by position into copy + + std::set > playlists = copy.playlists(); + std::set >::const_iterator pi; + + for (pi = playlists.begin(); pi != playlists.end(); ++pi) { + // find ripple start point on each applicable playlist + RegionView *first_selected_on_this_track = NULL; + for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) { + if ((*i)->region()->playlist() == (*pi)) { + // region is on this playlist - it's the first, because they're sorted + first_selected_on_this_track = *i; + break; + } + } + assert (first_selected_on_this_track); // we should always find the region in one of the playlists... + add_all_after_to_views ( + &first_selected_on_this_track->get_time_axis_view(), + first_selected_on_this_track->region()->position() + first_selected_on_this_track->region()->length(), + selected_regions, false); + } + + if (allow_moves_across_tracks) { + orig_tav = &(*selected_regions.begin())->get_time_axis_view(); + } else { + orig_tav = NULL; + } + +} + +void +RegionRippleDrag::motion (GdkEvent* event, bool first_move) +{ + /* Which trackview is this ? */ + + pair const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ()); + RouteTimeAxisView* tv = dynamic_cast (tvp.first); + + /* The region motion is only processed if the pointer is over + an audio track. + */ + + if (!tv || !tv->is_track()) { + /* To make sure we hide the verbose canvas cursor when the mouse is + not held over an audiotrack. + */ + _editor->verbose_cursor()->hide (); + return; + } + + framepos_t where = adjusted_current_frame (event); + assert (where >= 0); + framepos_t after; + double delta = compute_x_delta (event, &after); + + framecnt_t amount = _editor->pixel_to_sample (delta); + + if (allow_moves_across_tracks) { + // all the originally selected regions were on the same track + + framecnt_t adjust = 0; + if (prev_tav && tv != prev_tav) { + // dragged onto a different track + // remove the unselected regions from _views, restore them to their original positions + // and add the regions after the drop point on the new playlist to _views instead. + // undo the effect of rippling the previous playlist, and include the effect of removing + // the dragged region(s) from this track + + remove_unselected_from_views (prev_amount); + // ripple previous playlist according to the regions that have been removed onto the new playlist + prev_tav->playlist()->ripple(prev_position, -selection_length, exclude); + prev_amount = 0; + + // move just the selected regions + std::cerr << "calling RegionMoveDrag::motion() for single-track selection dragged across tracks, _views.size() now " << _views.size() << std::endl; + RegionMoveDrag::motion(event, first_move); + std::cerr << "RegionRippleDrag::motion() done!" << std::endl; + + // ensure that the ripple operation on the new playlist inserts selection_length time + adjust = selection_length; + // ripple the new current playlist + tv->playlist()->ripple (where, amount+adjust, exclude); + + // add regions after point where drag entered this track to subsequent ripples + add_all_after_to_views (tv, where, _editor->selection->regions, true); + std::cerr << "added regions on new track " << tv->name() << ", _views now contains " << _views.size() << " views" << std::endl; + + } else { + // motion on same track + // std::cerr << "calling RegionMoveDrag::motion() for single-track selection dragged within track..." << std::endl; + RegionMoveDrag::motion(event, first_move); + // std::cerr << "RegionRippleDrag::motion() done!" << std::endl; + + } + prev_tav = tv; + + // remember what we've done to this playlist so we can undo it if the selection is dragged to another track + prev_position = where; + if (!_x_constrained) { + prev_amount += amount; + } + } else { + // selection encompasses multiple tracks - just drag + // cross-track drags are forbidden + std::cerr << "calling RegionMoveDrag::motion() for multiple-track selection..." << std::endl; + RegionMoveDrag::motion(event, first_move); + std::cerr << "RegionRippleDrag::motion() done!" << std::endl; + + } + + _last_frame_position = after; +} + +void +RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred) +{ + if (!movement_occurred) { + return; + } + + _editor->begin_reversible_command(_("Ripple drag")); + + // if regions were dragged across tracks, we've rippled any later + // regions on the track the regions were dragged off, so we need + // to add the original track to the undo record + if (orig_tav) { + vector cmds; + orig_tav->playlist()->rdiff (cmds); + _editor->session()->add_commands (cmds); + } + + // other modified playlists are added to undo by RegionMoveDrag::finished() + RegionMoveDrag::finished (event, movement_occurred); + _editor->commit_reversible_command(); + +} + +void +RegionRippleDrag::aborted (bool movement_occurred) +{ + /* XXX: TODO */ + RegionMoveDrag::aborted (movement_occurred); + _views.clear (); +} + + RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v) : Drag (e, i), _view (dynamic_cast (v)) diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index 0bcfed9979..4b7114c67c 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -313,7 +313,7 @@ public: protected: double compute_x_delta (GdkEvent const *, ARDOUR::framepos_t *); - bool y_movement_allowed (int, double) const; + virtual bool y_movement_allowed (int, double) const; bool _brushing; ARDOUR::framepos_t _last_frame_position; ///< last position of the thing being dragged @@ -346,9 +346,11 @@ public: void setup_pointer_frame_offset (); -private: +protected: typedef std::set > PlaylistSet; + void add_stateful_diff_commands_for_playlists (PlaylistSet const &); +private: void finished_no_copy ( bool const, bool const, @@ -375,7 +377,6 @@ private: PlaylistSet& modified_playlists ); - void add_stateful_diff_commands_for_playlists (PlaylistSet const &); void collect_new_region_view (RegionView *); @@ -408,6 +409,42 @@ public: void aborted (bool); }; +/** Region drag in ripple mode */ + +class RegionRippleDrag : public RegionMoveDrag +{ +public: + RegionRippleDrag (Editor *, ArdourCanvas::Item *, RegionView *, std::list const &); + ~RegionRippleDrag () { delete exclude; } + + void motion (GdkEvent *, bool); + void finished (GdkEvent *, bool); + void aborted (bool); +protected: + bool y_movement_allowed (int delta_track, double delta_layer) const { + std::cerr << "RegionRippleDrag::y_movement_allowed (" << delta_track << ", " << delta_layer << ")..." << std::endl; + if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer)) { + if (delta_track) { + return allow_moves_across_tracks; + } else { + return true; + } + } + return false; + } +private: + TimeAxisView *prev_tav; // where regions were most recently dragged from + TimeAxisView *orig_tav; // where drag started + framecnt_t prev_amount; + framepos_t prev_position; + framecnt_t selection_length; + bool allow_moves_across_tracks; // only if all selected regions are on one track + ARDOUR::RegionList *exclude; + void add_all_after_to_views (TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress); + void remove_unselected_from_views (framecnt_t amount); + +}; + /** Drags to create regions */ class RegionCreateDrag : public Drag { diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index 928801d0b2..8e5a357ac4 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -2687,10 +2687,16 @@ Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region _region_motion_group->raise_to_top (); - if (Config->get_edit_mode() == Splice) { - _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer())); - } else { - _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false)); + switch (Config->get_edit_mode()) { + case Splice: + _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer())); + break; + case Ripple: + _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer())); + break; + default: + _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false)); + break; } } @@ -2717,7 +2723,7 @@ Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* return; } - if (Config->get_edit_mode() == Splice) { + if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) { return; } diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 08d6297faa..30ca540535 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -2119,6 +2119,9 @@ Editor::insert_region_list_drag (boost::shared_ptr region, int x, int y) begin_reversible_command (_("insert dragged region")); playlist->clear_changes (); playlist->add_region (RegionFactory::create (region, true), where, 1.0); + if (Config->get_edit_mode() == Ripple) + playlist->ripple (where, region->length(), boost::shared_ptr()); + _session->add_command(new StatefulDiffCommand (playlist)); commit_reversible_command (); } @@ -2192,6 +2195,9 @@ Editor::insert_region_list_selection (float times) begin_reversible_command (_("insert region")); playlist->clear_changes (); playlist->add_region ((RegionFactory::create (region, true)), get_preferred_edit_position(), times); + if (Config->get_edit_mode() == Ripple) + playlist->ripple (get_preferred_edit_position(), region->length() * times, boost::shared_ptr()); + _session->add_command(new StatefulDiffCommand (playlist)); commit_reversible_command (); } @@ -4054,10 +4060,11 @@ Editor::remove_clicked_region () boost::shared_ptr playlist = clicked_routeview->playlist(); - begin_reversible_command (_("remove region")); playlist->clear_changes (); playlist->clear_owned_changes (); playlist->remove_region (clicked_regionview->region()); + if (Config->get_edit_mode() == Ripple) + playlist->ripple (clicked_regionview->region()->position(), -clicked_regionview->region()->length(), boost::shared_ptr()); /* We might have removed regions, which alters other regions' layering_index, so we need to do a recursive diff here. @@ -4120,6 +4127,9 @@ Editor::remove_selected_regions () playlist->clear_owned_changes (); playlist->freeze (); playlist->remove_region (*rl); + if (Config->get_edit_mode() == Ripple) + playlist->ripple ((*rl)->position(), -(*rl)->length(), boost::shared_ptr()); + } vector >::iterator pl; @@ -4249,12 +4259,16 @@ Editor::cut_copy_regions (CutCopyOp op, RegionSelection& rs) switch (op) { case Delete: pl->remove_region (r); + if (Config->get_edit_mode() == Ripple) + pl->ripple (r->position(), -r->length(), boost::shared_ptr()); break; case Cut: _xx = RegionFactory::create (r); npl->add_region (_xx, r->position() - first_position); pl->remove_region (r); + if (Config->get_edit_mode() == Ripple) + pl->ripple (r->position(), -r->length(), boost::shared_ptr()); break; case Copy: @@ -4263,7 +4277,9 @@ Editor::cut_copy_regions (CutCopyOp op, RegionSelection& rs) break; case Clear: - pl->remove_region (r); + pl->remove_region (r); + if (Config->get_edit_mode() == Ripple) + pl->ripple (r->position(), -r->length(), boost::shared_ptr()); break; } diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc index 8eb4f58532..fd8f7f5995 100644 --- a/gtk2_ardour/route_time_axis.cc +++ b/gtk2_ardour/route_time_axis.cc @@ -1424,6 +1424,11 @@ RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, siz } pl->clear_changes (); + if (Config->get_edit_mode() == Ripple) { + std::pair extent = (*p)->get_extent(); + framecnt_t amount = extent.second - extent.first; + pl->ripple(pos, amount * times, boost::shared_ptr()); + } pl->paste (*p, pos, times); _session->add_command (new StatefulDiffCommand (pl)); diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h index 5629a04629..ababa60063 100644 --- a/libs/ardour/ardour/playlist.h +++ b/libs/ardour/ardour/playlist.h @@ -144,6 +144,14 @@ public: void uncombine (boost::shared_ptr); void shuffle (boost::shared_ptr, int dir); + void ripple (framepos_t at, framecnt_t distance, RegionList *exclude); + void ripple (framepos_t at, framecnt_t distance, boost::shared_ptr exclude) { + RegionList el; + if (exclude) + el.push_back (exclude); + ripple (at, distance, &el); + } + void update_after_tempo_map_change (); boost::shared_ptr cut (std::list&, bool result_is_hidden = true); @@ -283,6 +291,7 @@ public: bool first_set_state; bool _hidden; bool _splicing; + bool _rippling; bool _shuffling; bool _nudging; uint32_t _refcnt; @@ -337,6 +346,11 @@ public: void splice_locked (framepos_t at, framecnt_t distance, boost::shared_ptr exclude); void splice_unlocked (framepos_t at, framecnt_t distance, boost::shared_ptr exclude); + void core_ripple (framepos_t at, framecnt_t distance, RegionList *exclude); + void ripple_locked (framepos_t at, framecnt_t distance, RegionList *exclude); + void ripple_unlocked (framepos_t at, framecnt_t distance, RegionList *exclude); + + virtual void remove_dependents (boost::shared_ptr /*region*/) {} virtual XMLNode& state (bool); diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index 1b9c3326c0..92a8c0da5b 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -341,6 +341,7 @@ namespace ARDOUR { enum EditMode { Slide, Splice, + Ripple, Lock }; diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index 948025cc2b..3f5ce75eb3 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -237,6 +237,7 @@ setup_enum_writer () REGISTER_ENUM (Slide); REGISTER_ENUM (Splice); + REGISTER_ENUM (Ripple); // XXX do the old enum values have to stay in order? REGISTER_ENUM (Lock); REGISTER (_EditMode); diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index d939ba61b0..11ca20972e 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -172,6 +172,7 @@ Playlist::Playlist (boost::shared_ptr other, string namestr, boo in_set_state--; _splicing = other->_splicing; + _rippling = other->_rippling; _nudging = other->_nudging; _edit_mode = other->_edit_mode; @@ -302,6 +303,7 @@ Playlist::init (bool hide) _refcnt = 0; _hidden = hide; _splicing = false; + _rippling = false; _shuffling = false; _nudging = false; in_set_state = 0; @@ -1399,7 +1401,7 @@ Playlist::flush_notifications (bool from_undo) if (_edit_mode == Splice) { splice_locked (at, distance, exclude); - } + } } void @@ -1456,12 +1458,63 @@ Playlist::flush_notifications (bool from_undo) _splicing = false; notify_contents_changed (); - } +} - void - Playlist::region_bounds_changed (const PropertyChange& what_changed, boost::shared_ptr region) - { - if (in_set_state || _splicing || _nudging || _shuffling) { +void +Playlist::ripple_locked (framepos_t at, framecnt_t distance, RegionList *exclude) +{ + { + RegionWriteLock rl (this); + core_ripple (at, distance, exclude); + } +} + +void +Playlist::ripple_unlocked (framepos_t at, framecnt_t distance, RegionList *exclude) +{ + core_ripple (at, distance, exclude); +} + +void +Playlist::core_ripple (framepos_t at, framecnt_t distance, RegionList *exclude) +{ + if (distance == 0) { + return; + } + + _rippling = true; + RegionListProperty copy = regions; + for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) { + assert (i != copy.end()); + + if (exclude) { + if (std::find(exclude->begin(), exclude->end(), (*i)) != exclude->end()) { + continue; + } + } + + if ((*i)->position() >= at) { + framepos_t new_pos = (*i)->position() + distance; + framepos_t limit = max_framepos - (*i)->length(); + if (new_pos < 0) { + new_pos = 0; + } else if (new_pos >= limit ) { + new_pos = limit; + } + + (*i)->set_position (new_pos); + } + } + + _rippling = false; + notify_contents_changed (); +} + + +void +Playlist::region_bounds_changed (const PropertyChange& what_changed, boost::shared_ptr region) +{ + if (in_set_state || _splicing || _rippling || _nudging || _shuffling) { return; } @@ -2694,6 +2747,12 @@ Playlist::region_is_shuffle_constrained (boost::shared_ptr) return false; } +void +Playlist::ripple (framepos_t at, framecnt_t distance, RegionList *exclude) +{ + ripple_locked (at, distance, exclude); +} + void Playlist::update_after_tempo_map_change () { diff --git a/libs/ardour/utils.cc b/libs/ardour/utils.cc index d1d2372977..715c0d67dc 100644 --- a/libs/ardour/utils.cc +++ b/libs/ardour/utils.cc @@ -396,6 +396,8 @@ string_to_edit_mode (string str) return Splice; } else if (str == _("Slide")) { return Slide; + } else if (str == _("Ripple")) { + return Ripple; } else if (str == _("Lock")) { return Lock; } @@ -414,6 +416,9 @@ edit_mode_to_string (EditMode mode) case Lock: return _("Lock"); + case Ripple: + return _("Ripple"); + default: case Splice: return _("Splice"); From 933da08c72047828772c6ca97041335d94a2b8b4 Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Thu, 1 May 2014 13:23:43 +0100 Subject: [PATCH 04/58] Ripple mode: tidy up Remove a load of debug output and dead code. Move implementation of RegionRippleDrag::y_movement_allowed() out of header into .cc file. --- gtk2_ardour/editor_drag.cc | 51 +++++++++++--------------------------- gtk2_ardour/editor_drag.h | 13 ++-------- 2 files changed, 16 insertions(+), 48 deletions(-) diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 420fc52d31..d31f6c951e 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -1257,15 +1257,11 @@ RegionMoveDrag::collect_new_region_view (RegionView* rv) void RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists) { - std::cerr << "RegionMoveDrag::add_stateful_diff_commands_for_playlists ()" << std::endl; for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) { - std::cerr << "playlist: " << (*i)->name() << std::endl; StatefulDiffCommand* c = new StatefulDiffCommand (*i); if (!c->empty()) { - std::cerr << "added StatefulDiffCommand!" << std::endl; _editor->session()->add_command (c); } else { - std::cerr << "no StatefulDiffcommand to add..." << std::endl; delete c; } } @@ -1434,9 +1430,6 @@ RegionSpliceDrag::motion (GdkEvent* event, bool) dir = -1; } - // RegionSelection copy (_editor->selection->regions); - // RegionSelectionByPosition cmp; - // copy.sort (cmp); RegionSelection copy; _editor->selection->regions.by_position(copy); @@ -1521,16 +1514,11 @@ void RegionRippleDrag::remove_unselected_from_views(framecnt_t amount) { - std::cerr << "_views contains " << _views.size() << " views, including those on " << prev_tav->name() << std::endl; - for (std::list::iterator i = _views.begin(); i != _views.end(); ) { // we added all the regions after the selection - std::cerr << "iterating _views..." << std::endl; - std::cerr << "found " << i->view->region()->name() << " in _views..." << std::endl; std::list::iterator to_erase = i++; if (!_editor->selection->regions.contains (to_erase->view)) { - std::cerr << "removing " << to_erase->view->region()->name() << " from _views..." << std::endl; // restore the non-selected regions to their original playlist & positions, // and then ripple them back by the length of the regions that were dragged away // do the same things as RegionMotionDrag::aborted @@ -1541,32 +1529,31 @@ RegionRippleDrag::remove_unselected_from_views(framecnt_t amount) RegionView *rv = to_erase->view; -#if 0 - // this is how RegionMotionDrag::aborted() does it... - TimeAxisView* tv = &(rv->get_time_axis_view ()); - RouteTimeAxisView* rtv = dynamic_cast (tv); - assert (rtv); - assert (rtv == prev_tav); - rv->get_canvas_group()->reparent (*rtv->view()->canvas_item()); -#else - // this should be equivalent... rv->get_canvas_group()->reparent(prev_tav->view()->canvas_item()); -#endif rv->get_canvas_group()->set_y_position (0); rv->drag_end (); rv->fake_set_opaque (false); - rv->move(-amount, 0); // XXX second parameter is y delta - do we need to do something? + rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK // rv->set_height (rtv->view()->child_height ()); _views.erase (to_erase); - if (i == _views.end()) { - std::cerr << "reached end of _views iterator in loop!" << std::endl; - // break; - } } } } +bool +RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer) const +{ + if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer)) { + if (delta_track) { + return allow_moves_across_tracks; + } else { + return true; + } + } + return false; +} + RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list const & v) : RegionMoveDrag (e, i, p, v, false, false) { @@ -1661,9 +1648,7 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) prev_amount = 0; // move just the selected regions - std::cerr << "calling RegionMoveDrag::motion() for single-track selection dragged across tracks, _views.size() now " << _views.size() << std::endl; RegionMoveDrag::motion(event, first_move); - std::cerr << "RegionRippleDrag::motion() done!" << std::endl; // ensure that the ripple operation on the new playlist inserts selection_length time adjust = selection_length; @@ -1672,14 +1657,10 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) // add regions after point where drag entered this track to subsequent ripples add_all_after_to_views (tv, where, _editor->selection->regions, true); - std::cerr << "added regions on new track " << tv->name() << ", _views now contains " << _views.size() << " views" << std::endl; } else { // motion on same track - // std::cerr << "calling RegionMoveDrag::motion() for single-track selection dragged within track..." << std::endl; RegionMoveDrag::motion(event, first_move); - // std::cerr << "RegionRippleDrag::motion() done!" << std::endl; - } prev_tav = tv; @@ -1691,10 +1672,7 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) } else { // selection encompasses multiple tracks - just drag // cross-track drags are forbidden - std::cerr << "calling RegionMoveDrag::motion() for multiple-track selection..." << std::endl; RegionMoveDrag::motion(event, first_move); - std::cerr << "RegionRippleDrag::motion() done!" << std::endl; - } _last_frame_position = after; @@ -1727,7 +1705,6 @@ RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred) void RegionRippleDrag::aborted (bool movement_occurred) { - /* XXX: TODO */ RegionMoveDrag::aborted (movement_occurred); _views.clear (); } diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index 4b7114c67c..6c382cc1bd 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -421,17 +421,8 @@ public: void finished (GdkEvent *, bool); void aborted (bool); protected: - bool y_movement_allowed (int delta_track, double delta_layer) const { - std::cerr << "RegionRippleDrag::y_movement_allowed (" << delta_track << ", " << delta_layer << ")..." << std::endl; - if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer)) { - if (delta_track) { - return allow_moves_across_tracks; - } else { - return true; - } - } - return false; - } + bool y_movement_allowed (int delta_track, double delta_layer) const; + private: TimeAxisView *prev_tav; // where regions were most recently dragged from TimeAxisView *orig_tav; // where drag started From 99c0ef21d797bb4b3de5b5f0b2a9d204fc3b1132 Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Fri, 16 May 2014 00:51:54 +0100 Subject: [PATCH 05/58] Ripple mode: ripple when deleting ranges. Make delete, cut and clear of a range ripple the appropriate playlists. --- gtk2_ardour/route_time_axis.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc index fd8f7f5995..9cb26d1622 100644 --- a/gtk2_ardour/route_time_axis.cc +++ b/gtk2_ardour/route_time_axis.cc @@ -1363,6 +1363,10 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) switch (op) { case Delete: if (playlist->cut (time) != 0) { + if (Config->get_edit_mode() == Ripple) + playlist->ripple(time.start() + time.length(), -time.length(), NULL); + // no need to exclude any regions from rippling here + vector cmds; playlist->rdiff (cmds); _session->add_commands (cmds); @@ -1374,6 +1378,10 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) case Cut: if ((what_we_got = playlist->cut (time)) != 0) { _editor.get_cut_buffer().add (what_we_got); + if (Config->get_edit_mode() == Ripple) + playlist->ripple(time.start() + time.length(), -time.length(), NULL); + // no need to exclude any regions from rippling here + vector cmds; playlist->rdiff (cmds); _session->add_commands (cmds); @@ -1389,6 +1397,9 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) case Clear: if ((what_we_got = playlist->cut (time)) != 0) { + if (Config->get_edit_mode() == Ripple) + playlist->ripple(time.start() + time.length(), -time.length(), NULL); + // no need to exclude any regions from rippling here vector cmds; playlist->rdiff (cmds); From 46fa5c939720a8493975a8d35d04282b68c05842 Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Fri, 16 May 2014 00:49:09 +0100 Subject: [PATCH 06/58] Don't clear the clipboard when deleting things Deleting regions (or ranges, or anything) shouldn't affect the contents of the clipboard - only CutCopyOp::Copy, CutCopyOp::Cut and CutCopyOp::Clear should do that. --- gtk2_ardour/editor_ops.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 30ca540535..e15128f56e 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -3784,7 +3784,7 @@ Editor::can_cut_copy () const /** Cut, copy or clear selected regions, automation points or a time range. - * @param op Operation (Cut, Copy or Clear) + * @param op Operation (Delete, Cut, Copy or Clear) */ void Editor::cut_copy (CutCopyOp op) @@ -3820,7 +3820,7 @@ Editor::cut_copy (CutCopyOp op) } } - if ( op != Clear ) //"Delete" doesn't change copy/paste buf + if ( op != Delete ) //"Delete" doesn't change copy/paste buf cut_buffer->clear (); if (entered_marker) { From 00af9a967bbcf1c80e9db4494de577568991043c Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Fri, 16 May 2014 20:04:57 +0100 Subject: [PATCH 07/58] Ripple mode: ripple all after start of deleted range When deleting a range in ripple mode, ripple everything after the start point of that range backwards by the length of the range, so that newly-created regions immediately at the range end move back to the range start. --- gtk2_ardour/route_time_axis.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc index 9cb26d1622..ed46278aa6 100644 --- a/gtk2_ardour/route_time_axis.cc +++ b/gtk2_ardour/route_time_axis.cc @@ -1364,7 +1364,7 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) case Delete: if (playlist->cut (time) != 0) { if (Config->get_edit_mode() == Ripple) - playlist->ripple(time.start() + time.length(), -time.length(), NULL); + playlist->ripple(time.start(), -time.length(), NULL); // no need to exclude any regions from rippling here vector cmds; @@ -1379,7 +1379,7 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) if ((what_we_got = playlist->cut (time)) != 0) { _editor.get_cut_buffer().add (what_we_got); if (Config->get_edit_mode() == Ripple) - playlist->ripple(time.start() + time.length(), -time.length(), NULL); + playlist->ripple(time.start(), -time.length(), NULL); // no need to exclude any regions from rippling here vector cmds; @@ -1398,7 +1398,7 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) case Clear: if ((what_we_got = playlist->cut (time)) != 0) { if (Config->get_edit_mode() == Ripple) - playlist->ripple(time.start() + time.length(), -time.length(), NULL); + playlist->ripple(time.start(), -time.length(), NULL); // no need to exclude any regions from rippling here vector cmds; From 74bc0c84686c4a85941b98d17179d3209bf9a2a8 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 2 Jun 2014 11:20:37 -0400 Subject: [PATCH 08/58] substantive changes to the logic and safety for naming of (audio/MIDI) sources, especially when created via import --- gtk2_ardour/editor.h | 2 +- gtk2_ardour/editor_audio_import.cc | 46 ++++--- libs/ardour/ardour/file_source.h | 4 + libs/ardour/ardour/session.h | 10 +- libs/ardour/ardour/smf_source.h | 9 -- libs/ardour/audiofilesource.cc | 1 + libs/ardour/diskstream.cc | 1 - libs/ardour/file_source.cc | 27 +++- libs/ardour/filter.cc | 5 +- libs/ardour/import.cc | 96 ++++---------- libs/ardour/midi_diskstream.cc | 4 +- libs/ardour/session.cc | 196 +++++++++++++---------------- libs/ardour/session_state.cc | 37 +----- libs/ardour/smf_source.cc | 31 ----- 14 files changed, 178 insertions(+), 291 deletions(-) diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 3a74fa22ed..4da4a52525 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -1262,7 +1262,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD int target_regions, int target_tracks, boost::shared_ptr&, bool add_channel_suffix); int finish_bringing_in_material (boost::shared_ptr region, uint32_t, uint32_t, framepos_t& pos, Editing::ImportMode mode, - boost::shared_ptr& existing_track); + boost::shared_ptr& existing_track, const std::string& new_track_name); boost::shared_ptr get_nth_selected_audio_track (int nth) const; boost::shared_ptr get_nth_selected_midi_track (int nth) const; diff --git a/gtk2_ardour/editor_audio_import.cc b/gtk2_ardour/editor_audio_import.cc index 9364437b78..fb1e101f24 100644 --- a/gtk2_ardour/editor_audio_import.cc +++ b/gtk2_ardour/editor_audio_import.cc @@ -657,7 +657,8 @@ Editor::add_sources (vector paths, SourceList& sources, framepos_t& pos, uint32_t input_chan = 0; uint32_t output_chan = 0; bool use_timestamp; - + vector track_names; + use_timestamp = (pos == -1); // kludge (for MIDI we're abusing "channel" for "track" here) @@ -694,6 +695,11 @@ Editor::add_sources (vector paths, SourceList& sources, framepos_t& pos, regions.push_back (r); + /* if we're creating a new track, name it after the cleaned-up + * and "merged" region name. + */ + + track_names.push_back (region_name); } else if (target_regions == -1 || target_regions > 1) { @@ -724,29 +730,26 @@ Editor::add_sources (vector paths, SourceList& sources, framepos_t& pos, region_name = (*x)->name(); } - switch (sources.size()) { - /* zero and one channel handled - by previous if() condition - */ - case 2: + if (sources.size() == 2) { if (n == 0) { region_name += "-L"; } else { region_name += "-R"; } - break; - default: - region_name += (char) '-'; - region_name += (char) ('1' + n); - break; + } else if (sources.size() > 2) { + region_name += string_compose ("-%1", n+1); } + track_names.push_back (region_name); + } else { if (fs) { region_name = region_name_from_path (fs->path(), false, false, sources.size(), n); - } else{ + } else { region_name = (*x)->name(); } + + track_names.push_back (PBD::basename_nosuffix (paths[n])); } PropertyList plist; @@ -798,6 +801,12 @@ Editor::add_sources (vector paths, SourceList& sources, framepos_t& pos, framepos_t rlen = 0; begin_reversible_command (Operations::insert_file); + + /* we only use tracks names when importing to new tracks, but we + * require that one is defined for every region, just to keep + * the API simpler. + */ + assert (regions.size() == track_names.size()); for (vector >::iterator r = regions.begin(); r != regions.end(); ++r, ++n) { boost::shared_ptr ar = boost::dynamic_pointer_cast (*r); @@ -830,9 +839,8 @@ Editor::add_sources (vector paths, SourceList& sources, framepos_t& pos, pos = get_preferred_edit_position (); } } - - - finish_bringing_in_material (*r, input_chan, output_chan, pos, mode, track); + + finish_bringing_in_material (*r, input_chan, output_chan, pos, mode, track, track_names[n]); rlen = (*r)->length(); @@ -859,7 +867,7 @@ Editor::add_sources (vector paths, SourceList& sources, framepos_t& pos, int Editor::finish_bringing_in_material (boost::shared_ptr region, uint32_t in_chans, uint32_t out_chans, framepos_t& pos, - ImportMode mode, boost::shared_ptr& existing_track) + ImportMode mode, boost::shared_ptr& existing_track, const string& new_track_name) { boost::shared_ptr ar = boost::dynamic_pointer_cast(region); boost::shared_ptr mr = boost::dynamic_pointer_cast(region); @@ -916,7 +924,11 @@ Editor::finish_bringing_in_material (boost::shared_ptr region, uint32_t existing_track = mt.front(); } - existing_track->set_name (region->name()); + if (!new_track_name.empty()) { + existing_track->set_name (new_track_name); + } else { + existing_track->set_name (region->name()); + } } boost::shared_ptr playlist = existing_track->playlist(); diff --git a/libs/ardour/ardour/file_source.h b/libs/ardour/ardour/file_source.h index 531cdf4d4c..8eebfeac4e 100644 --- a/libs/ardour/ardour/file_source.h +++ b/libs/ardour/ardour/file_source.h @@ -85,6 +85,10 @@ public: void existence_check (); virtual void prevent_deletion (); + /** Rename the file on disk referenced by this source to \param newname + */ + int rename (const std::string& name); + protected: FileSource (Session& session, DataType type, const std::string& path, diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 168137ffc3..b93f932cc0 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -195,10 +195,10 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi std::string peak_path (std::string) const; std::string peak_path_from_audio_path (std::string) const; - std::string new_audio_source_name (const std::string&, uint32_t nchans, uint32_t chan, bool destructive); - std::string new_midi_source_name (const std::string&); - std::string new_source_path_from_name (DataType type, const std::string&); + std::string new_audio_source_path (const std::string&, uint32_t nchans, uint32_t chan, bool destructive, bool take_required); + std::string new_midi_source_path (const std::string&); RouteList new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name); + std::vector get_paths_for_new_sources (bool allow_replacing, const std::string& import_file_path, uint32_t channels); void process (pframes_t nframes); @@ -526,8 +526,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi boost::shared_ptr find_whole_file_parent (boost::shared_ptr) const; - std::string path_from_region_name (DataType type, std::string name, std::string identifier); - boost::shared_ptr XMLRegionFactory (const XMLNode&, bool full); boost::shared_ptr XMLAudioRegionFactory (const XMLNode&, bool full); boost::shared_ptr XMLMidiRegionFactory (const XMLNode&, bool full); @@ -1433,7 +1431,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi bool no_questions_about_missing_files; - std::string get_best_session_directory_for_new_source (); + std::string get_best_session_directory_for_new_audio (); mutable gint _playback_load; mutable gint _capture_load; diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h index a068a3e385..f359100451 100644 --- a/libs/ardour/ardour/smf_source.h +++ b/libs/ardour/ardour/smf_source.h @@ -47,15 +47,6 @@ public: virtual ~SMFSource (); - /** Rename the file on disk referenced by this source to \param newname - * - * This method exists only for MIDI file sources, not for audio, which - * can never be renamed. It exists for MIDI so that we can get - * consistent and sane region/source numbering when regions are added - * manually (which never happens with audio). - */ - int rename (const std::string& name); - bool safe_file_extension (const std::string& path) const { return safe_midi_file_extension(path); } diff --git a/libs/ardour/audiofilesource.cc b/libs/ardour/audiofilesource.cc index 13b03f8f48..37bf502177 100644 --- a/libs/ardour/audiofilesource.cc +++ b/libs/ardour/audiofilesource.cc @@ -36,6 +36,7 @@ #include "pbd/stl_delete.h" #include "pbd/strsplit.h" #include "pbd/shortpath.h" +#include "pbd/stacktrace.h" #include "pbd/enumwriter.h" #include diff --git a/libs/ardour/diskstream.cc b/libs/ardour/diskstream.cc index a359f228e8..94b68478d1 100644 --- a/libs/ardour/diskstream.cc +++ b/libs/ardour/diskstream.cc @@ -739,4 +739,3 @@ Diskstream::disengage_record_enable () { g_atomic_int_set (&_record_enabled, 0); } - diff --git a/libs/ardour/file_source.cc b/libs/ardour/file_source.cc index de2783a1ac..d579d11965 100644 --- a/libs/ardour/file_source.cc +++ b/libs/ardour/file_source.cc @@ -214,7 +214,7 @@ FileSource::move_to_trash (const string& trash_dir_name) if (move_dependents_to_trash() != 0) { /* try to back out */ - rename (newpath.c_str(), _path.c_str()); + ::rename (newpath.c_str(), _path.c_str()); return -1; } @@ -581,3 +581,28 @@ FileSource::is_stub () const return true; } +int +FileSource::rename (const string& newpath) +{ + Glib::Threads::Mutex::Lock lm (_lock); + string oldpath = _path; + + // Test whether newpath exists, if yes notify the user but continue. + if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) { + error << string_compose (_("Programming error! %1 tried to rename a file over another file! It's safe to continue working, but please report this to the developers."), PROGRAM_NAME) << endmsg; + return -1; + } + + if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { + /* rename only needed if file exists on disk */ + if (::rename (oldpath.c_str(), newpath.c_str()) != 0) { + error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg; + return -1; + } + } + + _name = Glib::path_get_basename (newpath); + _path = newpath; + + return 0; +} diff --git a/libs/ardour/filter.cc b/libs/ardour/filter.cc index b085ec946b..b723de1e56 100644 --- a/libs/ardour/filter.cc +++ b/libs/ardour/filter.cc @@ -59,10 +59,9 @@ Filter::make_new_sources (boost::shared_ptr region, SourceList& nsrcs, s } } - string path = session.path_from_region_name (region->data_type(), - PBD::basename_nosuffix (names[i]), string ("")); + string path = session.new_audio_source_path (name, region->n_channels(), i, false, false); - if (path.length() == 0) { + if (path.empty()) { error << string_compose (_("filter: error creating name for new file based on %1"), region->name()) << endmsg; return -1; diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc index e63143e695..6f7b3d0616 100644 --- a/libs/ardour/import.cc +++ b/libs/ardour/import.cc @@ -116,78 +116,31 @@ open_importable_source (const string& path, framecnt_t samplerate, ARDOUR::SrcQu } } -static std::string -get_non_existent_filename (HeaderFormat hf, DataType type, const bool allow_replacing, const std::string& destdir, const std::string& basename, uint channel, uint channels) -{ - char buf[PATH_MAX+1]; - bool goodfile = false; - string base = basename; - string ext = native_header_format_extension (hf, type); - uint32_t cnt = 1; - - do { - - if (type == DataType::AUDIO && channels == 2) { - if (channel == 0) { - if (cnt == 1) { - snprintf (buf, sizeof(buf), "%s-L%s", base.c_str(), ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%d-L%s", base.c_str(), cnt, ext.c_str()); - } - } else { - if (cnt == 1) { - snprintf (buf, sizeof(buf), "%s-R%s", base.c_str(), ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%d-R%s", base.c_str(), cnt, ext.c_str()); - } - } - } else if (channels > 1) { - if (cnt == 1) { - snprintf (buf, sizeof(buf), "%s-c%d%s", base.c_str(), channel, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%d-c%d%s", base.c_str(), cnt, channel, ext.c_str()); - } - } else { - if (cnt == 1) { - snprintf (buf, sizeof(buf), "%s%s", base.c_str(), ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%d%s", base.c_str(), cnt, ext.c_str()); - } - } - - string tempname = destdir + "/" + buf; - - if (!allow_replacing && Glib::file_test (tempname, Glib::FILE_TEST_EXISTS)) { - - cnt++; - - } else { - - goodfile = true; - } - - } while (!goodfile); - - return buf; -} - -static vector -get_paths_for_new_sources (HeaderFormat hf, const bool allow_replacing, const string& import_file_path, const string& session_dir, uint channels) +vector +Session::get_paths_for_new_sources (bool /*allow_replacing*/, const string& import_file_path, uint32_t channels) { vector new_paths; const string basename = basename_nosuffix (import_file_path); - SessionDirectory sdir(session_dir); - for (uint n = 0; n < channels; ++n) { const DataType type = SMFSource::safe_midi_file_extension (import_file_path) ? DataType::MIDI : DataType::AUDIO; + string filepath; - std::string filepath = (type == DataType::MIDI) - ? sdir.midi_path() : sdir.sound_path(); + switch (type) { + case DataType::MIDI: + filepath = new_midi_source_path (basename); + break; + case DataType::AUDIO: + filepath = new_audio_source_path (basename, channels, n, false, false); + break; + } + + if (filepath.empty()) { + error << string_compose (_("Cannot find new filename for imported file %1"), import_file_path) << endmsg; + return vector(); + } - filepath = Glib::build_filename (filepath, - get_non_existent_filename (hf, type, allow_replacing, filepath, basename, n, channels)); new_paths.push_back (filepath); } @@ -274,12 +227,12 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status, { const framecnt_t nframes = ResampledImportableSource::blocksize; boost::shared_ptr afs; - uint channels = source->channels(); + uint32_t channels = source->channels(); boost::scoped_array data(new float[nframes * channels]); vector > channel_data; - for (uint n = 0; n < channels; ++n) { + for (uint32_t n = 0; n < channels; ++n) { channel_data.push_back(boost::shared_array(new Sample[nframes])); } @@ -323,14 +276,14 @@ write_audio_data_to_new_files (ImportableSource* source, ImportStatus& status, progress_multiplier = 0.5; progress_base = 0.5; } - - uint read_count = 0; + + framecnt_t read_count = 0; while (!status.cancel) { framecnt_t nread, nfread; - uint x; - uint chn; + uint32_t x; + uint32_t chn; if ((nread = source->read (data.get(), nframes)) == 0) { break; @@ -513,10 +466,7 @@ Session::import_files (ImportStatus& status) } } - vector new_paths = get_paths_for_new_sources (config.get_native_file_header_format(), - status.replace_existing_source, *p, - get_best_session_directory_for_new_source (), - channels); + vector new_paths = get_paths_for_new_sources (status.replace_existing_source, *p, channels); Sources newfiles; framepos_t natural_position = source ? source->natural_position() : 0; diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index c38c08d72e..54b060299f 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -1234,9 +1234,9 @@ MidiDiskstream::steal_write_source_name () */ try { - string new_name = _session.new_midi_source_name (name()); + string new_path = _session.new_midi_source_path (name()); - if (_write_source->rename (new_name)) { + if (_write_source->rename (new_path)) { return string(); } } catch (...) { diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 1006c0f90b..0e4a10f76b 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -36,6 +36,7 @@ #include +#include "pbd/convert.h" #include "pbd/error.h" #include "pbd/boost_debug.h" #include "pbd/pathscanner.h" @@ -3366,30 +3367,6 @@ Session::count_sources_by_origin (const string& path) return cnt; } -/** Return the full path (in some session directory) for a new within-session source. - * \a name must be a session-unique name that does not contain slashes - * (e.g. as returned by new_*_source_name) - */ -string -Session::new_source_path_from_name (DataType type, const string& name) -{ - assert(name.find("/") == string::npos); - - SessionDirectory sdir(get_best_session_directory_for_new_source()); - - std::string p; - if (type == DataType::AUDIO) { - p = sdir.sound_path(); - } else if (type == DataType::MIDI) { - p = sdir.midi_path(); - } else { - error << "Unknown source type, unable to create file path" << endmsg; - return ""; - } - - return Glib::build_filename (p, name); -} - string Session::peak_path (string base) const { @@ -3398,18 +3375,20 @@ Session::peak_path (string base) const /** Return a unique name based on \a base for a new internal audio source */ string -Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t chan, bool destructive) +Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required) { uint32_t cnt; - char buf[PATH_MAX+1]; - const uint32_t limit = 10000; + string possible_name; + const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name string legalized; string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); + bool some_related_source_name_exists = false; - buf[0] = '\0'; + possible_name[0] = '\0'; legalized = legalize_for_path (base); // Find a "version" of the base name that doesn't exist in any of the possible directories. + for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) { vector::iterator i; @@ -3417,47 +3396,37 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { + ostringstream sstr; + if (destructive) { - - if (nchan < 2) { - snprintf (buf, sizeof(buf), "T%04d-%s%s", - cnt, legalized.c_str(), ext.c_str()); - } else if (nchan == 2) { - if (chan == 0) { - snprintf (buf, sizeof(buf), "T%04d-%s%%L%s", - cnt, legalized.c_str(), ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "T%04d-%s%%R%s", - cnt, legalized.c_str(), ext.c_str()); - } - } else if (nchan < 26) { - snprintf (buf, sizeof(buf), "T%04d-%s%%%c%s", - cnt, legalized.c_str(), 'a' + chan, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "T%04d-%s%s", - cnt, legalized.c_str(), ext.c_str()); - } - + sstr << 'T'; + sstr << setfill ('0') << setw (4) << cnt; + sstr << legalized; } else { - - if (nchan < 2) { - snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str()); - } else if (nchan == 2) { - if (chan == 0) { - snprintf (buf, sizeof(buf), "%s-%u%%L%s", legalized.c_str(), cnt, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%u%%R%s", legalized.c_str(), cnt, ext.c_str()); - } - } else if (nchan < 26) { - snprintf (buf, sizeof(buf), "%s-%u%%%c%s", legalized.c_str(), cnt, 'a' + chan, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%u%s", legalized.c_str(), cnt, ext.c_str()); + sstr << legalized; + + if (take_required || some_related_source_name_exists) { + sstr << '-'; + sstr << cnt; } } + + if (nchan == 2) { + if (chan == 0) { + sstr << "%L"; + } else { + sstr << "%R"; + } + } else if (nchan > 2 && nchan < 26) { + sstr << '%'; + sstr << 'a' + chan; + } + sstr << ext; + + possible_name = sstr.str(); SessionDirectory sdir((*i).path); - - string spath = sdir.sound_path(); + const string spath = sdir.sound_path(); /* note that we search *without* the extension so that we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf" @@ -3465,7 +3434,7 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha a file format change. */ - if (matching_unsuffixed_filename_exists_in (spath, buf)) { + if (matching_unsuffixed_filename_exists_in (spath, possible_name)) { existing++; break; } @@ -3479,7 +3448,7 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha * notions of their removability. */ - string possible_path = Glib::build_filename (spath, buf); + string possible_path = Glib::build_filename (spath, possible_name); if (audio_source_by_path_and_channel (possible_path, chan)) { existing++; @@ -3491,6 +3460,8 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha break; } + some_related_source_name_exists = true; + if (cnt > limit) { error << string_compose( _("There are already %1 recordings for %2, which I consider too many."), @@ -3500,32 +3471,31 @@ Session::new_audio_source_name (const string& base, uint32_t nchan, uint32_t cha } } - return Glib::path_get_basename (buf); -} + /* We've established that the new name does not exist in any session + * directory, so now find out which one we should use for this new + * audio source. + */ -/** Create a new within-session audio source */ -boost::shared_ptr -Session::create_audio_source_for_session (size_t n_chans, string const & n, uint32_t chan, bool destructive) -{ - const string name = new_audio_source_name (n, n_chans, chan, destructive); - const string path = new_source_path_from_name(DataType::AUDIO, name); + SessionDirectory sdir (get_best_session_directory_for_new_audio()); - return boost::dynamic_pointer_cast ( - SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); + std::string s = Glib::build_filename (sdir.sound_path(), possible_name); + + return s; } /** Return a unique name based on \a owner_name for a new internal MIDI source */ string -Session::new_midi_source_name (const string& owner_name) +Session::new_midi_source_path (const string& base) { uint32_t cnt; char buf[PATH_MAX+1]; const uint32_t limit = 10000; string legalized; + string possible_path; string possible_name; buf[0] = '\0'; - legalized = legalize_for_path (owner_name); + legalized = legalize_for_path (base); // Find a "version" of the file name that doesn't exist in any of the possible directories. @@ -3533,7 +3503,7 @@ Session::new_midi_source_name (const string& owner_name) vector::iterator i; uint32_t existing = 0; - + for (i = session_dirs.begin(); i != session_dirs.end(); ++i) { SessionDirectory sdir((*i).path); @@ -3541,7 +3511,7 @@ Session::new_midi_source_name (const string& owner_name) snprintf (buf, sizeof(buf), "%s-%u.mid", legalized.c_str(), cnt); possible_name = buf; - std::string possible_path = Glib::build_filename (sdir.midi_path(), possible_name); + possible_path = Glib::build_filename (sdir.midi_path(), possible_name); if (Glib::file_test (possible_path, Glib::FILE_TEST_EXISTS)) { existing++; @@ -3559,31 +3529,47 @@ Session::new_midi_source_name (const string& owner_name) if (cnt > limit) { error << string_compose( _("There are already %1 recordings for %2, which I consider too many."), - limit, owner_name) << endmsg; + limit, base) << endmsg; destroy (); - throw failed_constructor(); + return 0; } } - return possible_name; + /* No need to "find best location" for software/app-based RAID, because + MIDI is so small that we always put it in the same place. + */ + + return possible_path; } +/** Create a new within-session audio source */ +boost::shared_ptr +Session::create_audio_source_for_session (size_t n_chans, string const & base, uint32_t chan, bool destructive) +{ + const string path = new_audio_source_path (base, n_chans, chan, destructive, true); + + if (!path.empty()) { + return boost::dynamic_pointer_cast ( + SourceFactory::createWritable (DataType::AUDIO, *this, path, destructive, frame_rate())); + } else { + throw failed_constructor (); + } +} + /** Create a new within-session MIDI source */ boost::shared_ptr Session::create_midi_source_for_session (string const & basic_name) { - std::string name; - - if (name.empty()) { - name = new_midi_source_name (basic_name); + const string path = new_midi_source_path (basic_name); + + if (!path.empty()) { + return boost::dynamic_pointer_cast ( + SourceFactory::createWritable ( + DataType::MIDI, *this, path, false, frame_rate())); + } else { + throw failed_constructor (); } - - const string path = new_source_path_from_name (DataType::MIDI, name); - - return boost::dynamic_pointer_cast ( - SourceFactory::createWritable ( - DataType::MIDI, *this, path, false, frame_rate())); } /** Create a new within-session MIDI source */ @@ -3617,7 +3603,7 @@ Session::create_midi_source_by_stealing_name (boost::shared_ptr track) return boost::shared_ptr(); } - const string path = new_source_path_from_name (DataType::MIDI, name); + const string path = new_midi_source_path (name); return boost::dynamic_pointer_cast ( SourceFactory::createWritable ( @@ -4123,18 +4109,13 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, boost::shared_ptr result; boost::shared_ptr playlist; boost::shared_ptr fsource; - uint32_t x; - char buf[PATH_MAX+1]; ChanCount diskstream_channels (track.n_channels()); framepos_t position; framecnt_t this_chunk; framepos_t to_do; BufferSet buffers; - SessionDirectory sdir(get_best_session_directory_for_new_source ()); - const string sound_dir = sdir.sound_path(); framepos_t len = end - start; bool need_block_size_reset = false; - string ext; ChanCount const max_proc = track.max_processor_streams (); if (end <= start) { @@ -4155,29 +4136,22 @@ Session::write_one_track (AudioTrack& track, framepos_t start, framepos_t end, goto out; } - ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO); - for (uint32_t chan_n = 0; chan_n < diskstream_channels.n_audio(); ++chan_n) { - for (x = 0; x < 99999; ++x) { - snprintf (buf, sizeof(buf), "%s/%s-%d-bounce-%" PRIu32 "%s", sound_dir.c_str(), playlist->name().c_str(), chan_n, x+1, ext.c_str()); - if (!Glib::file_test (buf, Glib::FILE_TEST_EXISTS)) { - break; - } - } - - if (x == 99999) { - error << string_compose (_("too many bounced versions of playlist \"%1\""), playlist->name()) << endmsg; + string base_name = string_compose ("%1-%2-bounce", playlist->name(), chan_n); + string path = new_audio_source_path (base_name, diskstream_channels.n_audio(), chan_n, false, true); + + if (path.empty()) { goto out; } try { fsource = boost::dynamic_pointer_cast ( - SourceFactory::createWritable (DataType::AUDIO, *this, buf, false, frame_rate())); + SourceFactory::createWritable (DataType::AUDIO, *this, path, false, frame_rate())); } catch (failed_constructor& err) { - error << string_compose (_("cannot create new audio file \"%1\" for %2"), buf, track.name()) << endmsg; + error << string_compose (_("cannot create new audio file \"%1\" for %2"), path, track.name()) << endmsg; goto out; } diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index ffbe55afbf..c985f8810a 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -1830,41 +1830,6 @@ Session::get_sources_as_xml () return *node; } -string -Session::path_from_region_name (DataType type, string name, string identifier) -{ - char buf[PATH_MAX+1]; - uint32_t n; - SessionDirectory sdir(get_best_session_directory_for_new_source()); - std::string source_dir = ((type == DataType::AUDIO) - ? sdir.sound_path() : sdir.midi_path()); - - string ext = native_header_format_extension (config.get_native_file_header_format(), type); - - for (n = 0; n < 999999; ++n) { - if (identifier.length()) { - snprintf (buf, sizeof(buf), "%s%s%" PRIu32 "%s", name.c_str(), - identifier.c_str(), n, ext.c_str()); - } else { - snprintf (buf, sizeof(buf), "%s-%" PRIu32 "%s", name.c_str(), - n, ext.c_str()); - } - - std::string source_path = Glib::build_filename (source_dir, buf); - - if (!Glib::file_test (source_path, Glib::FILE_TEST_EXISTS)) { - return source_path; - } - } - - error << string_compose (_("cannot create new file from region name \"%1\" with ident = \"%2\": too many existing files with similar names"), - name, identifier) - << endmsg; - - return ""; -} - - int Session::load_sources (const XMLNode& node) { @@ -2053,7 +2018,7 @@ Session::refresh_disk_space () } string -Session::get_best_session_directory_for_new_source () +Session::get_best_session_directory_for_new_audio () { vector::iterator i; string result = _session_dir->root_path(); diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc index c3bc78c83d..1cd456ee58 100644 --- a/libs/ardour/smf_source.cc +++ b/libs/ardour/smf_source.cc @@ -718,34 +718,3 @@ SMFSource::prevent_deletion () _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy)); } -int -SMFSource::rename (const string& newname) -{ - Glib::Threads::Mutex::Lock lm (_lock); - string oldpath = _path; - string newpath = _session.new_source_path_from_name (DataType::MIDI, newname); - - if (newpath.empty()) { - error << string_compose (_("programming error: %1"), "cannot generate a changed file path") << endmsg; - return -1; - } - - // Test whether newpath exists, if yes notify the user but continue. - if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) { - error << string_compose (_("Programming error! %1 tried to rename a file over another file! It's safe to continue working, but please report this to the developers."), PROGRAM_NAME) << endmsg; - return -1; - } - - if (Glib::file_test (oldpath.c_str(), Glib::FILE_TEST_EXISTS)) { - /* rename only needed if file exists on disk */ - if (::rename (oldpath.c_str(), newpath.c_str()) != 0) { - error << string_compose (_("cannot rename file %1 to %2 (%3)"), oldpath, newpath, strerror(errno)) << endmsg; - return -1; - } - } - - _name = Glib::path_get_basename (newpath); - _path = newpath; - - return 0; -} From b660bc8ae92d19aedf0165815432b77a0c6170c4 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 10 Jun 2014 10:07:04 -0400 Subject: [PATCH 09/58] fix crash recovery: add new constructors to SndFileSource, AudioFileSource, add a new SourceFactory method and finally tweak AudioDiskstream::use_pending_capture_data() to create both the required whole-file and the in-playlist regions --- libs/ardour/ardour/audiofilesource.h | 6 +++++ libs/ardour/ardour/sndfilesource.h | 11 +++++++- libs/ardour/ardour/source_factory.h | 3 +++ libs/ardour/audio_diskstream.cc | 38 +++++++++++++++++++--------- libs/ardour/audiofilesource.cc | 16 ++++++++++++ libs/ardour/file_source.cc | 2 +- libs/ardour/sndfilesource.cc | 36 ++++++++++++++++++++++++++ libs/ardour/source_factory.cc | 33 ++++++++++++++++++++++++ 8 files changed, 131 insertions(+), 14 deletions(-) diff --git a/libs/ardour/ardour/audiofilesource.h b/libs/ardour/ardour/audiofilesource.h index 9be8d6ce45..7f4b18e404 100644 --- a/libs/ardour/ardour/audiofilesource.h +++ b/libs/ardour/ardour/audiofilesource.h @@ -93,6 +93,12 @@ protected: /** Constructor to be called for existing in-session files */ AudioFileSource (Session&, const XMLNode&, bool must_exist = true); + /** Constructor to be called for crash recovery. Final argument is not + * used but exists to differentiate from the external-to-session + * constructor above. + */ + AudioFileSource (Session&, const std::string& path, Source::Flag flags, bool); + int init (const std::string& idstr, bool must_exist); virtual void set_header_timeline_position () = 0; diff --git a/libs/ardour/ardour/sndfilesource.h b/libs/ardour/ardour/sndfilesource.h index 3f63f1c598..9604d3f232 100644 --- a/libs/ardour/ardour/sndfilesource.h +++ b/libs/ardour/ardour/sndfilesource.h @@ -38,7 +38,16 @@ class SndFileSource : public AudioFileSource { SampleFormat samp_format, HeaderFormat hdr_format, framecnt_t rate, Flag flags = SndFileSource::default_writable_flags); - /** Constructor to be called for existing in-session files */ + /* Constructor to be called for recovering files being used for + * capture. They are in-session, they already exist, they should not + * be writable. They are an odd hybrid (from a constructor point of + * view) of the previous two constructors. + */ + SndFileSource (Session&, const std::string& path, int chn); + + /** Constructor to be called for existing in-session files during + * session loading + */ SndFileSource (Session&, const XMLNode&); ~SndFileSource (); diff --git a/libs/ardour/ardour/source_factory.h b/libs/ardour/ardour/source_factory.h index c94f783b44..ce0f86bb6b 100644 --- a/libs/ardour/ardour/source_factory.h +++ b/libs/ardour/ardour/source_factory.h @@ -57,6 +57,9 @@ class SourceFactory { bool destructive, framecnt_t rate, bool announce = true, bool async = false); + static boost::shared_ptr createForRecovery + (DataType type, Session&, const std::string& path, int chn); + static boost::shared_ptr createFromPlaylist (DataType type, Session& s, boost::shared_ptr p, const PBD::ID& orig, const std::string& name, uint32_t chn, frameoffset_t start, framecnt_t len, bool copy, bool defer_peaks); diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 010e1da21f..7785284dac 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -2181,11 +2181,16 @@ AudioDiskstream::use_pending_capture_data (XMLNode& node) continue; } + /* XXX as of June 2014, we always record to mono + files. Since this Source is being created as part of + crash recovery, we know that we need the first + channel (the final argument to the SourceFactory + call below). If we ever support non-mono files for + capture, this will need rethinking. + */ + try { - fs = boost::dynamic_pointer_cast ( - SourceFactory::createWritable ( - DataType::AUDIO, _session, - prop->value(), false, _session.frame_rate())); + fs = boost::dynamic_pointer_cast (SourceFactory::createForRecovery (DataType::AUDIO, _session, prop->value(), 0)); } catch (failed_constructor& err) { @@ -2216,21 +2221,31 @@ AudioDiskstream::use_pending_capture_data (XMLNode& node) return -1; } - boost::shared_ptr region; - try { - PropertyList plist; + boost::shared_ptr wf_region; + boost::shared_ptr region; + + /* First create the whole file region */ + PropertyList plist; + plist.add (Properties::start, 0); plist.add (Properties::length, first_fs->length (first_fs->timeline_position())); plist.add (Properties::name, region_name_from_path (first_fs->name(), true)); - region = boost::dynamic_pointer_cast (RegionFactory::create (pending_sources, plist)); + wf_region = boost::dynamic_pointer_cast (RegionFactory::create (pending_sources, plist)); - region->set_automatic (true); - region->set_whole_file (true); - region->special_set_position (0); + wf_region->set_automatic (true); + wf_region->set_whole_file (true); + wf_region->special_set_position (position); + + /* Now create a region that isn't the whole file for adding to + * the playlist */ + + region = boost::dynamic_pointer_cast (RegionFactory::create (pending_sources, plist)); + + _playlist->add_region (region, position); } catch (failed_constructor& err) { @@ -2241,7 +2256,6 @@ AudioDiskstream::use_pending_capture_data (XMLNode& node) return -1; } - _playlist->add_region (region, position); return 0; } diff --git a/libs/ardour/audiofilesource.cc b/libs/ardour/audiofilesource.cc index 37bf502177..8c3bf00176 100644 --- a/libs/ardour/audiofilesource.cc +++ b/libs/ardour/audiofilesource.cc @@ -115,6 +115,22 @@ AudioFileSource::AudioFileSource (Session& s, const string& path, const string& } } +/** Constructor used for existing internal-to-session files during crash + * recovery. File must exist + */ +AudioFileSource::AudioFileSource (Session& s, const string& path, Source::Flag flags, bool /* ignored-exists-for-prototype differentiation */) + : Source (s, DataType::AUDIO, path, flags) + , AudioSource (s, path) + , FileSource (s, DataType::AUDIO, path, string(), flags) +{ + /* note that origin remains empty */ + + if (init (_path, true)) { + throw failed_constructor (); + } +} + + /** Constructor used for existing internal-to-session files via XML. File must exist. */ AudioFileSource::AudioFileSource (Session& s, const XMLNode& node, bool must_exist) : Source (s, node) diff --git a/libs/ardour/file_source.cc b/libs/ardour/file_source.cc index d579d11965..30ae2178fe 100644 --- a/libs/ardour/file_source.cc +++ b/libs/ardour/file_source.cc @@ -56,7 +56,7 @@ PBD::Signal3 > FileSource:: FileSource::FileSource (Session& session, DataType type, const string& path, const string& origin, Source::Flag flag) : Source(session, type, path, flag) , _path (path) - , _file_is_new (!origin.empty()) // origin empty => new file VS. origin !empty => new file + , _file_is_new (!origin.empty()) // if origin is left unspecified (empty string) then file must exist , _channel (0) , _origin (origin) , _open (false) diff --git a/libs/ardour/sndfilesource.cc b/libs/ardour/sndfilesource.cc index 6b019f6fd0..5465c5e4a4 100644 --- a/libs/ardour/sndfilesource.cc +++ b/libs/ardour/sndfilesource.cc @@ -183,6 +183,34 @@ SndFileSource::SndFileSource (Session& s, const string& path, const string& orig } } +/** Constructor to be called for recovering files being used for + * capture. They are in-session, they already exist, they should not + * be writable. They are an odd hybrid (from a constructor point of + * view) of the previous two constructors. + */ +SndFileSource::SndFileSource (Session& s, const string& path, int chn) + : Source (s, DataType::AUDIO, path, Flag (0)) + /* the final boolean argument is not used, its value is irrelevant. see audiofilesource.h for explanation */ + , AudioFileSource (s, path, Flag (0)) + , _descriptor (0) + , _broadcast_info (0) + , _capture_start (false) + , _capture_end (false) + , file_pos (0) + , xfade_buf (0) +{ + _channel = chn; + + init_sndfile (); + + assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS)); + existence_check (); + + if (open()) { + throw failed_constructor (); + } +} + void SndFileSource::init_sndfile () { @@ -256,6 +284,14 @@ SndFileSource::open () delete _broadcast_info; _broadcast_info = 0; _flags = Flag (_flags & ~Broadcast); + } + + /* Set the broadcast flag if the BWF info is already there. We need + * this when recovering or using existing files. + */ + + if (bwf_info_exists) { + _flags = Flag (_flags | Broadcast); } if (writable()) { diff --git a/libs/ardour/source_factory.cc b/libs/ardour/source_factory.cc index 391b205a94..6d2bb80b30 100644 --- a/libs/ardour/source_factory.cc +++ b/libs/ardour/source_factory.cc @@ -341,6 +341,39 @@ SourceFactory::createWritable (DataType type, Session& s, const std::string& pat return boost::shared_ptr (); } +boost::shared_ptr +SourceFactory::createForRecovery (DataType type, Session& s, const std::string& path, int chn) +{ + /* this might throw failed_constructor(), which is OK */ + + if (type == DataType::AUDIO) { + Source* src = new SndFileSource (s, path, chn); + +#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS + // boost_debug_shared_ptr_mark_interesting (src, "Source"); +#endif + boost::shared_ptr ret (src); + + if (setup_peakfile (ret, false)) { + return boost::shared_ptr(); + } + + // no analysis data - this is still basically a new file (we + // crashed while recording. + + // always announce these files + + SourceCreated (ret); + + return ret; + + } else if (type == DataType::MIDI) { + error << _("Recovery attempted on a MIDI file - not implemented") << endmsg; + } + + return boost::shared_ptr (); +} + boost::shared_ptr SourceFactory::createFromPlaylist (DataType type, Session& s, boost::shared_ptr p, const PBD::ID& orig, const std::string& name, uint32_t chn, frameoffset_t start, framecnt_t len, bool copy, bool defer_peaks) From 038d292c3fb53718f1a580a0f0ff54c7a0aa8e23 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 30 Jun 2014 03:31:58 +0200 Subject: [PATCH 10/58] fix issue with track-deletion when the monitoring section is used. --- libs/ardour/session.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 121df3130c..eba11a457b 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -2603,6 +2603,13 @@ Session::remove_route (boost::shared_ptr route) } } + /* if the monitoring section had a pointer to this route, remove it */ + if (_monitor_out && !route->is_master() && !route->is_monitor()) { + Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ()); + PBD::Unwinder uw (ignore_route_processor_changes, true); + route->remove_aux_or_listen (_monitor_out); + } + boost::shared_ptr mt = boost::dynamic_pointer_cast (route); if (mt && mt->step_editing()) { if (_step_editors > 0) { From 8b216111da5a2c59b78d9b797dcb0019fd0034f1 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 30 Jun 2014 04:04:35 +0200 Subject: [PATCH 11/58] clean up after monitoring-section on session close --- libs/ardour/session.cc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index eba11a457b..36c43f0c06 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -500,6 +500,14 @@ Session::destroy () clear_clicks (); + /* need to remove auditioner before monitoring section + * otherwise it is re-connected */ + auditioner.reset (); + + /* drop references to routes held by the monitoring section + * specifically _monitor_out aux/listen references */ + remove_monitor_section(); + /* clear out any pending dead wood from RCU managed objects */ routes.flush (); @@ -519,7 +527,6 @@ Session::destroy () /* reset these three references to special routes before we do the usual route delete thing */ - auditioner.reset (); _master_out.reset (); _monitor_out.reset (); @@ -3757,6 +3764,9 @@ Session::audition_region (boost::shared_ptr r) void Session::cancel_audition () { + if (!auditioner) { + return; + } if (auditioner->auditioning()) { auditioner->cancel_audition (); AuditionActive (false); /* EMIT SIGNAL */ From cf075743e445ac38c2d0226c8a12bd9178990484 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 30 Jun 2014 08:31:20 -0400 Subject: [PATCH 12/58] additional DEBUG_TRACE message for canvas current item selection --- libs/canvas/canvas.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/canvas/canvas.cc b/libs/canvas/canvas.cc index 242f8ecbfd..86d7e7d6fc 100644 --- a/libs/canvas/canvas.cc +++ b/libs/canvas/canvas.cc @@ -416,6 +416,8 @@ GtkCanvas::pick_current_item (Duple const & point, int state) within_items.push_front (possible_item); } + DEBUG_TRACE (PBD::DEBUG::CanvasEnterLeave, string_compose ("after filtering insensitive + containers, we have %1 items\n", within_items.size())); + if (within_items.empty()) { /* no items at point, just send leave event below */ From 33339090c39ecd5f096bb6055af2dc8df65ae378 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 30 Jun 2014 08:32:18 -0400 Subject: [PATCH 13/58] invisible items/containers should not add their children to "items-at-point" --- libs/canvas/item.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/canvas/item.cc b/libs/canvas/item.cc index 2d4f03a41f..f734ecf740 100644 --- a/libs/canvas/item.cc +++ b/libs/canvas/item.cc @@ -959,11 +959,13 @@ Item::add_items_at_point (Duple const point, vector& items) const return; } - /* recurse and add any items within our group that contain point */ + /* recurse and add any items within our group that contain point. + Our children are only considered visible if we are + */ vector our_items; - if (!_items.empty()) { + if (!_items.empty() && visible()) { ensure_lut (); our_items = _lut->items_at_point (point); } From a9b336108198d7f18c0a532c5dac714a0a1515da Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 30 Jun 2014 09:17:03 -0400 Subject: [PATCH 14/58] use Editor::effective_mouse_mode() inside Editor::button_selection(); clean up special-case of fade in/out handles and mouse mode --- gtk2_ardour/editor_mouse.cc | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index d0287328c5..25525336f4 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -490,7 +490,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp to cut notes or regions. */ - MouseMode eff_mouse_mode = mouse_mode; + MouseMode eff_mouse_mode = effective_mouse_mode (); if (get_smart_mode() && eff_mouse_mode == MouseRange && event->button.button == 3 && item_type == RegionItem) { /* context clicks are always about object properties, even if @@ -499,6 +499,20 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp eff_mouse_mode = MouseObject; } + /* special case: allow drag of region fade in/out in object mode with join object/range enabled */ + if (get_smart_mode()) { + switch (item_type) { + case FadeInHandleItem: + case FadeInTrimHandleItem: + case FadeOutHandleItem: + case FadeOutTrimHandleItem: + eff_mouse_mode = MouseObject; + break; + default: + break; + } + } + if (((mouse_mode != MouseObject) && (mouse_mode != MouseAudition || item_type != RegionItem) && (mouse_mode != MouseTimeFX || item_type != RegionItem) && @@ -567,7 +581,6 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp case StartCrossFadeItem: case EndCrossFadeItem: if (eff_mouse_mode != MouseRange) { - cerr << "Should be setting selected regionview\n"; set_selected_regionview_from_click (press, op); } else if (event->type == GDK_BUTTON_PRESS) { set_selected_track_as_side_effect (op); @@ -717,8 +730,17 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT Editing::MouseMode eff = effective_mouse_mode (); /* special case: allow drag of region fade in/out in object mode with join object/range enabled */ - if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) { - eff = MouseObject; + if (get_smart_mode()) { + switch (item_type) { + case FadeInHandleItem: + case FadeInTrimHandleItem: + case FadeOutHandleItem: + case FadeOutTrimHandleItem: + eff = MouseObject; + break; + default: + break; + } } /* there is no Range mode when in internal edit mode */ From 4df566782c377b3161218daf279effff11010521 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 30 Jun 2014 09:20:26 -0400 Subject: [PATCH 15/58] remove canvas cursor debug output --- gtk2_ardour/editor_canvas.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gtk2_ardour/editor_canvas.cc b/gtk2_ardour/editor_canvas.cc index cae0199673..9739f65dd1 100644 --- a/gtk2_ardour/editor_canvas.cc +++ b/gtk2_ardour/editor_canvas.cc @@ -1182,11 +1182,9 @@ Editor::which_track_cursor () const case JOIN_OBJECT_RANGE_NONE: case JOIN_OBJECT_RANGE_OBJECT: cursor = which_grabber_cursor (); - cerr << "region use grabber\n"; break; case JOIN_OBJECT_RANGE_RANGE: cursor = _cursors->selector; - cerr << "region use selector\n"; break; } } @@ -1216,8 +1214,6 @@ Editor::choose_canvas_cursor_on_entry (GdkEventCrossing* /*event*/, ItemType typ { Gdk::Cursor* cursor = 0; - cerr << "entered new item type " << enum_2_string (type) << endl; - if (_drags->active()) { return; } From a0e399f7dd15a69c25660f4a4bb58b12f27297a6 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 30 Jun 2014 10:14:06 -0400 Subject: [PATCH 16/58] make inactive group tab color be themeable and update it dynamically --- gtk2_ardour/ardour3_ui_default.conf.in | 1 + gtk2_ardour/canvas_vars.h | 1 + gtk2_ardour/editor_group_tabs.cc | 5 ++--- gtk2_ardour/group_tabs.cc | 2 ++ gtk2_ardour/mixer_group_tabs.cc | 5 ++--- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gtk2_ardour/ardour3_ui_default.conf.in b/gtk2_ardour/ardour3_ui_default.conf.in index 6d851a376c..2f76f369c8 100644 --- a/gtk2_ardour/ardour3_ui_default.conf.in +++ b/gtk2_ardour/ardour3_ui_default.conf.in @@ -45,6 +45,7 @@