From 6304261b980419eaafde84bc32ff5e8f1c7aa236 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Tue, 27 Dec 2011 01:42:49 +0000 Subject: Re-work layering in possibly debatable ways. Sketchy docs in doc/layering. git-svn-id: svn://localhost/ardour2/branches/3.0@11088 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/playlist.cc | 701 +++++++++++++++++++++++++++--------------------- 1 file changed, 392 insertions(+), 309 deletions(-) (limited to 'libs/ardour/playlist.cc') diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index 641aeff012..f2084fda0c 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -30,6 +30,7 @@ #include "pbd/convert.h" #include "pbd/failed_constructor.h" +#include "pbd/stacktrace.h" #include "pbd/stateful_diff_command.h" #include "pbd/xml++.h" @@ -320,8 +321,8 @@ Playlist::init (bool hide) _frozen = false; layer_op_counter = 0; freeze_length = 0; - _explicit_relayering = false; _combine_ops = 0; + _relayer_suspended = false; _session.history().BeginUndoRedo.connect_same_thread (*this, boost::bind (&Playlist::begin_undo, this)); _session.history().EndUndoRedo.connect_same_thread (*this, boost::bind (&Playlist::end_undo, this)); @@ -487,6 +488,8 @@ Playlist::notify_region_moved (boost::shared_ptr r) { Evoral::RangeMove const move (r->last_position (), r->length (), r->position ()); + /* We could timestamp the region's layer op here, but we're doing it in region_bounds_changed */ + if (holding_state ()) { pending_range_moves.push_back (move); @@ -503,6 +506,8 @@ Playlist::notify_region_moved (boost::shared_ptr r) void Playlist::notify_region_start_trimmed (boost::shared_ptr r) { + timestamp_layer_op (LayerOpBoundsChange, r); + if (r->position() >= r->last_position()) { /* trimmed shorter */ return; @@ -526,6 +531,8 @@ Playlist::notify_region_start_trimmed (boost::shared_ptr r) void Playlist::notify_region_end_trimmed (boost::shared_ptr r) { + timestamp_layer_op (LayerOpBoundsChange, r); + if (r->length() < r->last_length()) { /* trimmed shorter */ } @@ -552,6 +559,8 @@ Playlist::notify_region_added (boost::shared_ptr r) as though it could be. */ + timestamp_layer_op (LayerOpAdd, r); + if (holding_state()) { pending_adds.insert (r); pending_contents_change = true; @@ -560,6 +569,7 @@ Playlist::notify_region_added (boost::shared_ptr r) pending_contents_change = false; RegionAdded (boost::weak_ptr (r)); /* EMIT SIGNAL */ ContentsChanged (); /* EMIT SIGNAL */ + relayer (r); } } @@ -577,34 +587,35 @@ Playlist::flush_notifications (bool from_undo) in_flush = true; + /* We have: + + pending_bounds: regions whose bounds position and/or length changes + pending_removes: regions which were removed + pending_adds: regions which were added + pending_length: true if the playlist length might have changed + pending_contents_change: true if there was almost any change in the playlist + pending_range_moves: details of periods of time that have been moved about (when regions have been moved) + + */ + if (!pending_bounds.empty() || !pending_removes.empty() || !pending_adds.empty()) { regions_changed = true; } - /* we have no idea what order the regions ended up in pending - bounds (it could be based on selection order, for example). - so, to preserve layering in the "most recently moved is higher" - model, sort them by existing layer, then timestamp them. - */ - - // RegionSortByLayer cmp; - // pending_bounds.sort (cmp); + /* Make a list of regions that need relayering */ + RegionList regions_to_relayer; for (RegionList::iterator r = pending_bounds.begin(); r != pending_bounds.end(); ++r) { - if (_session.config.get_layer_model() == MoveAddHigher) { - timestamp_layer_op (*r); - } + regions_to_relayer.push_back (*r); dependent_checks_needed.insert (*r); } for (s = pending_removes.begin(); s != pending_removes.end(); ++s) { remove_dependents (*s); - // cerr << _name << " sends RegionRemoved\n"; RegionRemoved (boost::weak_ptr (*s)); /* EMIT SIGNAL */ } for (s = pending_adds.begin(); s != pending_adds.end(); ++s) { - // cerr << _name << " sends RegionAdded\n"; /* don't emit RegionAdded signal until relayering is done, so that the region is fully setup by the time anyone hear's that its been added @@ -613,18 +624,14 @@ Playlist::flush_notifications (bool from_undo) } if (regions_changed || pending_contents_change) { - if (!in_set_state) { - relayer (); - } pending_contents_change = false; - // cerr << _name << " sends 5 contents change @ " << get_microseconds() << endl; ContentsChanged (); /* EMIT SIGNAL */ - // cerr << _name << "done contents change @ " << get_microseconds() << endl; } for (s = pending_adds.begin(); s != pending_adds.end(); ++s) { (*s)->clear_changes (); RegionAdded (boost::weak_ptr (*s)); /* EMIT SIGNAL */ + regions_to_relayer.push_back (*s); } for (s = dependent_checks_needed.begin(); s != dependent_checks_needed.end(); ++s) { @@ -639,6 +646,10 @@ Playlist::flush_notifications (bool from_undo) RegionsExtended (pending_region_extensions); } + if (!regions_to_relayer.empty ()) { + relayer (regions_to_relayer); + } + clear_pending (); in_flush = false; @@ -741,18 +752,11 @@ Playlist::flush_notifications (bool from_undo) region->set_position (position); - timestamp_layer_op (region); - regions.insert (upper_bound (regions.begin(), regions.end(), region, cmp), region); all_regions.insert (region); possibly_splice_unlocked (position, region->length(), region); - if (!holding_state ()) { - /* layers get assigned from XML state, and are not reset during undo/redo */ - relayer (); - } - /* we need to notify the existence of new region before checking dependents. Ick. */ notify_region_added (region); @@ -812,7 +816,6 @@ Playlist::flush_notifications (bool from_undo) possibly_splice_unlocked (pos, -distance); if (!holding_state ()) { - relayer (); remove_dependents (region); } @@ -865,11 +868,20 @@ Playlist::flush_notifications (bool from_undo) } } +/** Go through each region on the playlist and cut them at start and end, removing the section between + * start and end if cutting == true. Regions that lie entirely within start and end are always + * removed. + */ void Playlist::partition_internal (framepos_t start, framepos_t end, bool cutting, RegionList& thawlist) { RegionList new_regions; + /* Don't relayer regions that are created during this operation; leave them + on the same region as the original. + */ + suspend_relayer (); + { RegionLock rlock (this); @@ -948,7 +960,7 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start() + (pos2 - pos1)); plist.add (Properties::length, pos3 - pos2); plist.add (Properties::name, new_name); - plist.add (Properties::layer, regions.size()); + plist.add (Properties::layer, current->layer()); plist.add (Properties::automatic, true); plist.add (Properties::left_of_split, true); plist.add (Properties::right_of_split, true); @@ -967,7 +979,7 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start() + (pos3 - pos1)); plist.add (Properties::length, pos4 - pos3); plist.add (Properties::name, new_name); - plist.add (Properties::layer, regions.size()); + plist.add (Properties::layer, current->layer()); plist.add (Properties::automatic, true); plist.add (Properties::right_of_split, true); @@ -1005,7 +1017,7 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start() + (pos2 - pos1)); plist.add (Properties::length, pos4 - pos2); plist.add (Properties::name, new_name); - plist.add (Properties::layer, regions.size()); + plist.add (Properties::layer, current->layer()); plist.add (Properties::automatic, true); plist.add (Properties::left_of_split, true); @@ -1048,7 +1060,7 @@ Playlist::flush_notifications (bool from_undo) plist.add (Properties::start, current->start()); plist.add (Properties::length, pos3 - pos1); plist.add (Properties::name, new_name); - plist.add (Properties::layer, regions.size()); + plist.add (Properties::layer, current->layer()); plist.add (Properties::automatic, true); plist.add (Properties::right_of_split, true); @@ -1095,6 +1107,8 @@ Playlist::flush_notifications (bool from_undo) for (RegionList::iterator i = new_regions.begin(); i != new_regions.end(); ++i) { check_dependents (*i, false); } + + resume_relayer (); } boost::shared_ptr @@ -1370,18 +1384,6 @@ Playlist::flush_notifications (bool from_undo) add_region_internal (left, region->position()); add_region_internal (right, region->position() + before); - uint64_t orig_layer_op = region->last_layer_op(); - for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - if ((*i)->last_layer_op() > orig_layer_op) { - (*i)->set_last_layer_op( (*i)->last_layer_op() + 1 ); - } - } - - left->set_last_layer_op ( orig_layer_op ); - right->set_last_layer_op ( orig_layer_op + 1); - - layer_op_counter++; - finalize_split_region (region, left, right); remove_region_internal (region); @@ -1467,6 +1469,8 @@ Playlist::flush_notifications (bool from_undo) if (what_changed.contains (Properties::position)) { + timestamp_layer_op (LayerOpBoundsChange, region); + /* remove it from the list then add it back in the right place again. */ @@ -1506,13 +1510,8 @@ Playlist::flush_notifications (bool from_undo) if (holding_state ()) { pending_bounds.push_back (region); } else { - if (_session.config.get_layer_model() == MoveAddHigher) { - /* it moved or changed length, so change the timestamp */ - timestamp_layer_op (region); - } - notify_contents_changed (); - relayer (); + relayer (region); check_dependents (region, false); } } @@ -1572,10 +1571,6 @@ Playlist::flush_notifications (bool from_undo) notify_region_start_trimmed (region); } - /* don't notify about layer changes, since we are the only object that can initiate - them, and we notify in ::relayer() - */ - if (what_changed.contains (our_interests)) { save = true; } @@ -2134,6 +2129,7 @@ Playlist::flush_notifications (bool from_undo) return -1; } + suspend_relayer (); freeze (); plist = node.properties(); @@ -2195,9 +2191,7 @@ Playlist::flush_notifications (bool from_undo) } add_region (region, region->position(), 1.0); - - // So that layer_op ordering doesn't get screwed up - region->set_last_layer_op( region->layer()); + region->resume_property_changes (); } @@ -2218,6 +2212,7 @@ Playlist::flush_notifications (bool from_undo) thaw (); notify_contents_changed (); + resume_relayer (); in_set_state--; first_set_state = false; @@ -2345,289 +2340,253 @@ Playlist::set_edit_mode (EditMode mode) _edit_mode = mode; } -/******************** - * Region Layering - ********************/ - +/** Relayer a region. See the other relayer() methods for commentary. */ void -Playlist::relayer () +Playlist::relayer (boost::shared_ptr region) { - /* never compute layers when changing state for undo/redo or setting from XML */ - - if (in_update || in_set_state) { + if (_relayer_suspended) { return; } + + RegionList r; + r.push_back (region); + relayer (r); +} - bool changed = false; - - /* Build up a new list of regions on each layer, stored in a set of lists - each of which represent some period of time on some layer. The idea - is to avoid having to search the entire region list to establish whether - each region overlaps another */ - - /* how many pieces to divide this playlist's time up into */ - int const divisions = 512; - - /* find the start and end positions of the regions on this playlist */ - framepos_t start = INT64_MAX; - framepos_t end = 0; - for (RegionList::const_iterator i = regions.begin(); i != regions.end(); ++i) { - start = min (start, (*i)->position()); - end = max (end, (*i)->position() + (*i)->length()); - } - - /* hence the size of each time division */ - double const division_size = (end - start) / double (divisions); - - vector > layers; - layers.push_back (vector (divisions)); - - /* we want to go through regions from desired lowest to desired highest layer, - which depends on the layer model - */ - - RegionList copy = regions.rlist(); - - /* sort according to the model and the layering mode that we're in */ - - if (_explicit_relayering) { +Playlist::TemporaryLayers +Playlist::compute_temporary_layers (RegionList const & relayer_regions) +{ + TemporaryLayers temporary_layers; + OverlapCache cache (this); - copy.sort (RegionSortByLayerWithPending ()); + for (RegionList::const_iterator i = relayer_regions.begin(); i != relayer_regions.end(); ++i) { - } else if (_session.config.get_layer_model() == MoveAddHigher || _session.config.get_layer_model() == AddHigher) { + /* current_overlaps: regions that overlap *i now */ + RegionList current_overlaps = cache.get ((*i)->bounds ()); + current_overlaps.remove (*i); + + /* overlaps_to_preserve: regions that overlap *i now, but which aren't being + worked on during this relayer: these will have their relationship with + *i preserved. + */ + RegionList overlaps_to_preserve; - copy.sort (RegionSortByLastLayerOp ()); + /* overlaps_to_check: regions that overlap *i now, and must be checked to + see if *i is still on the correct layer with respect to them (according + to current layering rules). + */ + RegionList overlaps_to_check; - } + if (_session.config.get_relayer_on_all_edits () || (*i)->last_layer_op (LayerOpAdd) > (*i)->last_layer_op (LayerOpBoundsChange)) { + /* We're configured to relayer on all edits, or this region has had + no edit since it was added to the playlist, so we're relayering + the whole lot; in this case there are no `overlaps_to_preserve'. + */ + overlaps_to_check = current_overlaps; + } else { + /* We're only relayering new overlaps; find them */ + RegionList last_overlaps = cache.get ((*i)->last_relayer_bounds ()); + last_overlaps.remove (*i); + for (RegionList::const_iterator j = current_overlaps.begin(); j != current_overlaps.end(); ++j) { + if (find (last_overlaps.begin(), last_overlaps.end(), *j) == last_overlaps.end()) { + /* This is a new overlap, which must be checked */ + overlaps_to_check.push_back (*j); + } else { + /* This is an existing overlap, which must be preserved */ + overlaps_to_preserve.push_back (*j); + } + } + } + if (overlaps_to_check.empty ()) { + /* There are no overlaps to check, so just leave everything as it is */ + continue; + } - for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) { + /* Put *i on our overlaps_to_check_list */ + overlaps_to_check.push_back (*i); - /* reset the pending explicit relayer flag for every region, now that we're relayering */ - (*i)->set_pending_explicit_relayer (false); + /* And sort it according to the current layer model */ + switch (_session.config.get_layer_model()) { + case LaterHigher: + overlaps_to_check.sort (RegionSortByPosition ()); + break; + case AddOrBoundsChangeHigher: + overlaps_to_check.sort (RegionSortByAddOrBounds ()); + break; + case AddHigher: + overlaps_to_check.sort (RegionSortByAdd ()); + break; + } - /* find the time divisions that this region covers; if there are no regions on the list, - division_size will equal 0 and in this case we'll just say that - start_division = end_division = 0. + /* Now find *i in our overlaps_to_check list; within this list it will be in the + right place wrt the current layering rules, so we can work out the layers of the + nearest regions below and above. */ - int start_division = 0; - int end_division = 0; - - if (division_size > 0) { - start_division = floor ( ((*i)->position() - start) / division_size); - end_division = floor ( ((*i)->position() + (*i)->length() - start) / division_size ); - if (end_division == divisions) { - end_division--; - } + double previous_layer = -DBL_MAX; + double next_layer = DBL_MAX; + RegionList::const_iterator j = overlaps_to_check.begin(); + while (*j != *i) { + previous_layer = temporary_layers.get (*j); + ++j; } - assert (divisions == 0 || end_division < divisions); + /* we must have found *i */ + assert (j != overlaps_to_check.end ()); - /* find the lowest layer that this region can go on */ - size_t j = layers.size(); - while (j > 0) { - /* try layer j - 1; it can go on if it overlaps no other region - that is already on that layer - */ + ++j; + if (j != overlaps_to_check.end ()) { + next_layer = temporary_layers.get (*j); + } - bool overlap = false; - for (int k = start_division; k <= end_division; ++k) { - RegionList::iterator l = layers[j-1][k].begin (); - while (l != layers[j-1][k].end()) { - if ((*l)->overlap_equivalent (*i)) { - overlap = true; - break; - } - l++; - } + /* Now we know where *i and overlaps_to_preserve should go: between previous_layer and + next_layer. + */ - if (overlap) { - break; + /* Recurse into overlaps_to_preserve to find dependents */ + RegionList recursed_overlaps_to_preserve; + + for (RegionList::const_iterator k = overlaps_to_preserve.begin(); k != overlaps_to_preserve.end(); ++k) { + recursed_overlaps_to_preserve.push_back (*k); + RegionList touched = recursive_regions_touched (*k, cache, *i); + for (RegionList::iterator m = touched.begin(); m != touched.end(); ++m) { + if (find (recursed_overlaps_to_preserve.begin(), recursed_overlaps_to_preserve.end(), *m) == recursed_overlaps_to_preserve.end()) { + recursed_overlaps_to_preserve.push_back (*m); } } - - if (overlap) { - /* overlap, so we must use layer j */ - break; - } - - --j; } - if (j == layers.size()) { - /* we need a new layer for this region */ - layers.push_back (vector (divisions)); - } + /* Put *i into the overlaps_to_preserve list */ + recursed_overlaps_to_preserve.push_back (*i); - /* put a reference to this region in each of the divisions that it exists in */ - for (int k = start_division; k <= end_division; ++k) { - layers[j][k].push_back (*i); - } + /* Sort it by layer, so that we preserve layering */ + recursed_overlaps_to_preserve.sort (SortByTemporaryLayer (temporary_layers)); - if ((*i)->layer() != j) { - changed = true; - } + /* Divide available space up into chunks so that we can relayer everything in that space */ + double const space = (next_layer - previous_layer) / (recursed_overlaps_to_preserve.size() + 1); - (*i)->set_layer (j); + /* And relayer */ + int m = 1; + for (RegionList::const_iterator k = recursed_overlaps_to_preserve.begin(); k != recursed_overlaps_to_preserve.end(); ++k) { + temporary_layers.set (*k, previous_layer + space * m); + ++m; + } } - if (changed) { - notify_layering_changed (); - } + return temporary_layers; } -/* XXX these layer functions are all deprecated */ - +/** Take a list of temporary layer indices and set up the layers of all regions + * based on them. + */ void -Playlist::raise_region (boost::shared_ptr region) +Playlist::commit_temporary_layers (TemporaryLayers const & temporary_layers) { - uint32_t top = regions.size() - 1; - layer_t target = region->layer() + 1U; + /* Sort all the playlist's regions by layer, ascending */ + RegionList all_regions = regions.rlist (); + all_regions.sort (SortByTemporaryLayer (temporary_layers)); - if (target >= top) { - /* its already at the effective top */ - return; - } + for (RegionList::iterator i = all_regions.begin(); i != all_regions.end(); ++i) { - move_region_to_layer (target, region, 1); -} + /* Go through the regions that we have already layered and hence work + out the maximum layer index that is in used at some point during + region *i. + */ + + layer_t max_layer_here = 0; + bool have_overlap = false; + for (RegionList::iterator j = all_regions.begin(); j != i; ++j) { + if ((*j)->overlap_equivalent (*i)) { + max_layer_here = max ((*j)->layer (), max_layer_here); + have_overlap = true; + } + } -void -Playlist::lower_region (boost::shared_ptr region) -{ - if (region->layer() == 0) { - /* its already at the bottom */ - return; + if (have_overlap) { + /* *i overlaps something, so put it on the next available layer */ + (*i)->set_layer (max_layer_here + 1); + } else { + /* no overlap, so put on the bottom layer */ + (*i)->set_layer (0); + } } - layer_t target = region->layer() - 1U; - - move_region_to_layer (target, region, -1); + notify_layering_changed (); } +/** Relayer a list of regions. + * + * Taking each region R in turn, this method examines the regions O that overlap R in time. + * If the session configuration option "relayer-on-all-moves" is false, we reduce O so that + * it contains only those regions with which new overlaps have been formed since the last + * relayer. + * + * We then change the layer of R and its indirect overlaps so that R meets the current + * Session layer model with respect to O. See doc/layering. + */ + void -Playlist::raise_region_to_top (boost::shared_ptr region) +Playlist::relayer (RegionList const & relayer_regions) { - /* does nothing useful if layering mode is later=higher */ - switch (_session.config.get_layer_model()) { - case LaterHigher: + if (_relayer_suspended) { return; - default: - break; } - layer_t top = regions.size() - 1; + /* We do this in two parts: first; compute `temporary layer' indices for + regions on the playlist. These are (possibly) fractional indices, which + are a convenient means of working with things when you want to insert layers + between others. + */ - if (region->layer() >= top) { - /* already on the top */ - return; - } + TemporaryLayers temporary_layers = compute_temporary_layers (relayer_regions); - move_region_to_layer (top, region, 1); - /* mark the region's last_layer_op as now, so that it remains on top when - doing future relayers (until something else takes over) - */ - timestamp_layer_op (region); + /* Second, we fix up these temporary layers and `commit' them by writing + them to the regions involved. + */ + + commit_temporary_layers (temporary_layers); } +/** Put a region on some fractional layer and sort everything else out around it. + * This can be used to force a region into some layering; for example, calling + * this method with temporary_layer == -0.5 will put the region at the bottom of + * the stack. + */ + void -Playlist::lower_region_to_bottom (boost::shared_ptr region) +Playlist::relayer (boost::shared_ptr region, double temporary_layer) { - /* does nothing useful if layering mode is later=higher */ - switch (_session.config.get_layer_model()) { - case LaterHigher: - return; - default: - break; - } - - if (region->layer() == 0) { - /* already on the bottom */ + if (_relayer_suspended) { return; } - move_region_to_layer (0, region, -1); - /* force region's last layer op to zero so that it stays at the bottom - when doing future relayers - */ - region->set_last_layer_op (0); + TemporaryLayers t; + t.set (region, temporary_layer); + commit_temporary_layers (t); } -int -Playlist::move_region_to_layer (layer_t target_layer, boost::shared_ptr region, int dir) +void +Playlist::raise_region (boost::shared_ptr region) { - RegionList::iterator i; - typedef pair,layer_t> LayerInfo; - list layerinfo; - - { - RegionLock rlock (const_cast (this)); - - for (i = regions.begin(); i != regions.end(); ++i) { - - if (region == *i) { - continue; - } - - layer_t dest; - - if (dir > 0) { - - /* region is moving up, move all regions on intermediate layers - down 1 - */ - - if ((*i)->layer() > region->layer() && (*i)->layer() <= target_layer) { - dest = (*i)->layer() - 1; - } else { - /* not affected */ - continue; - } - } else { - - /* region is moving down, move all regions on intermediate layers - up 1 - */ - - if ((*i)->layer() < region->layer() && (*i)->layer() >= target_layer) { - dest = (*i)->layer() + 1; - } else { - /* not affected */ - continue; - } - } - - LayerInfo newpair; - - newpair.first = *i; - newpair.second = dest; - - layerinfo.push_back (newpair); - } - } - - freeze (); - - /* now reset the layers without holding the region lock */ - - for (list::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) { - x->first->set_layer (x->second); - } - - region->set_layer (target_layer); - - /* now check all dependents, since we changed the layering */ - - for (list::iterator x = layerinfo.begin(); x != layerinfo.end(); ++x) { - check_dependents (x->first, false); - } + relayer (region, region->layer() + 0.5); +} - check_dependents (region, false); - notify_layering_changed (); +void +Playlist::lower_region (boost::shared_ptr region) +{ + relayer (region, region->layer() - 0.5); +} - thaw (); +void +Playlist::raise_region_to_top (boost::shared_ptr region) +{ + relayer (region, max_layer); +} - return 0; +void +Playlist::lower_region_to_bottom (boost::shared_ptr region) +{ + relayer (region, -0.5); } void @@ -2763,15 +2722,23 @@ Playlist::set_frozen (bool yn) } void -Playlist::timestamp_layer_op (boost::shared_ptr region) +Playlist::timestamp_layer_op (LayerOp op, boost::shared_ptr region) { - region->set_last_layer_op (++layer_op_counter); + region->set_last_layer_op (op, ++layer_op_counter); } +/** Find the next or previous region after `region' (next if dir > 0, previous otherwise) + * and swap its position with `region'. + */ void Playlist::shuffle (boost::shared_ptr region, int dir) { + /* As regards layering, the calls we make to set_position() will + perform layering as if the regions had been moved, which I think + is about right. + */ + bool moved = false; if (region->locked()) { @@ -2876,13 +2843,9 @@ Playlist::shuffle (boost::shared_ptr region, int dir) _shuffling = false; if (moved) { - - relayer (); check_dependents (region, false); - notify_contents_changed(); } - } bool @@ -2921,29 +2884,6 @@ Playlist::foreach_region (boost::function)> s) } } -void -Playlist::set_explicit_relayering (bool e) -{ - if (e == false && _explicit_relayering == true) { - - /* We are changing from explicit to implicit relayering; layering may have been changed whilst - we were in explicit mode, and we don't want that to be undone next time an implicit relayer - occurs. Hence now we'll set up region last_layer_op values so that an implicit relayer - at this point would keep regions on the same layers. - - From then on in, it's just you and your towel. - */ - - RegionLock rl (this); - for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) { - (*i)->set_last_layer_op ((*i)->layer ()); - } - } - - _explicit_relayering = e; -} - - bool Playlist::has_region_at (framepos_t const p) const { @@ -3323,3 +3263,146 @@ Playlist::set_orig_track_id (const PBD::ID& id) { _orig_track_id = id; } + +/** Set the temporary layer for a region */ +void +Playlist::TemporaryLayers::set (boost::shared_ptr r, double l) +{ + _map[r] = l; +} + +/** Return the temporary layer for a region, if one has been specified + * to this TemporaryLayers object; if not return the region's current + * layer. + */ +double +Playlist::TemporaryLayers::get (boost::shared_ptr r) const +{ + Map::const_iterator i = _map.find (r); + if (i != _map.end ()) { + return i->second; + } + + return double (r->layer ()); +} + +int const Playlist::OverlapCache::_divisions = 512; + +/** Set up an OverlapCache for a playlist; the cache will only be valid until + * the Playlist is changed. + */ +Playlist::OverlapCache::OverlapCache (Playlist* playlist) + : _range (0, 0) +{ + /* Find the start and end positions of the regions on this playlist */ + _range = Evoral::Range (max_framepos, 0); + RegionList const & rl = playlist->region_list().rlist (); + for (RegionList::const_iterator i = rl.begin(); i != rl.end(); ++i) { + Evoral::Range const b = (*i)->bounds (); + _range.from = min (_range.from, b.from); + _range.to = max (_range.to, b.to); + } + + /* Hence the size of each time divison */ + _division_size = (_range.to - _range.from) / double (_divisions); + + _cache.resize (_divisions); + + /* Build the cache */ + for (RegionList::const_iterator i = rl.begin(); i != rl.end(); ++i) { + pair ind = cache_indices ((*i)->bounds ()); + for (int j = ind.first; j < ind.second; ++j) { + _cache[j].push_back (*i); + } + } +} + +/** @param range Range, in frames. + * @return From and to cache indices for (to is exclusive). + */ +pair +Playlist::OverlapCache::cache_indices (Evoral::Range range) const +{ + range.from = max (range.from, _range.from); + range.to = min (range.to, _range.to); + + pair const p = make_pair ( + floor ((range.from - _range.from) / _division_size), + ceil ((range.to - _range.from) / _division_size) + ); + + assert (p.first >= 0); + assert (p.second <= _divisions); + + return p; +} + +/** Return the regions that overlap a given range. The returned list + * is not guaranteed to be in the same order as the Playlist that it was + * generated from. + */ +Playlist::RegionList +Playlist::OverlapCache::get (Evoral::Range range) const +{ + if (_range.from == max_framepos) { + return RegionList (); + } + + RegionList r; + + pair ind = cache_indices (range); + for (int i = ind.first; i < ind.second; ++i) { + for (RegionList::const_iterator j = _cache[i].begin(); j != _cache[i].end(); ++j) { + if ((*j)->coverage (range.from, range.to) != OverlapNone) { + r.push_back (*j); + } + } + } + + r.sort (); + r.unique (); + + return r; +} + +void +Playlist::suspend_relayer () +{ + _relayer_suspended = true; +} + +void +Playlist::resume_relayer () +{ + _relayer_suspended = false; +} + +/** Examine a region and return regions which overlap it, and also those which overlap those which overlap etc. + * @param ignore Optional region which should be treated as if it doesn't exist (ie not returned in the list, + * and not recursed into). + */ +Playlist::RegionList +Playlist::recursive_regions_touched (boost::shared_ptr region, OverlapCache const & cache, boost::shared_ptr ignore) const +{ + RegionList touched; + recursive_regions_touched_sub (region, cache, ignore, touched); + + touched.remove (region); + return touched; +} + +/** Recursive sub-routine of recursive_regions_touched */ +void +Playlist::recursive_regions_touched_sub ( + boost::shared_ptr region, OverlapCache const & cache, boost::shared_ptr ignore, RegionList & touched + ) const +{ + RegionList r = cache.get (region->bounds ()); + for (RegionList::iterator i = r.begin(); i != r.end(); ++i) { + if (find (touched.begin(), touched.end(), *i) == touched.end() && *i != ignore) { + touched.push_back (*i); + recursive_regions_touched_sub (*i, cache, ignore, touched); + } + } +} + -- cgit v1.2.3