diff options
Diffstat (limited to 'libs/ardour')
-rw-r--r-- | libs/ardour/ardour/ardour.h | 2 | ||||
-rw-r--r-- | libs/ardour/ardour/playlist.h | 91 | ||||
-rw-r--r-- | libs/ardour/ardour/region.h | 29 | ||||
-rw-r--r-- | libs/ardour/ardour/region_sorters.h | 27 | ||||
-rw-r--r-- | libs/ardour/ardour/session_configuration_vars.h | 3 | ||||
-rw-r--r-- | libs/ardour/ardour/types.h | 8 | ||||
-rw-r--r-- | libs/ardour/audio_diskstream.cc | 9 | ||||
-rw-r--r-- | libs/ardour/enums.cc | 2 | ||||
-rw-r--r-- | libs/ardour/playlist.cc | 701 | ||||
-rw-r--r-- | libs/ardour/region.cc | 79 | ||||
-rwxr-xr-x | libs/ardour/run-tests.sh | 2 | ||||
-rw-r--r-- | libs/ardour/session_state.cc | 22 | ||||
-rw-r--r-- | libs/ardour/test/playlist_layering_test.cc | 384 | ||||
-rw-r--r-- | libs/ardour/test/playlist_layering_test.h | 34 | ||||
-rw-r--r-- | libs/ardour/test/playlist_overlap_cache_test.cc | 119 | ||||
-rw-r--r-- | libs/ardour/test/playlist_overlap_cache_test.h | 20 | ||||
-rw-r--r-- | libs/ardour/test/test_needing_session.cc | 48 | ||||
-rw-r--r-- | libs/ardour/test/test_needing_session.h | 16 | ||||
-rw-r--r-- | libs/ardour/test/test_receiver.h | 37 | ||||
-rw-r--r-- | libs/ardour/wscript | 2 |
20 files changed, 1142 insertions, 493 deletions
diff --git a/libs/ardour/ardour/ardour.h b/libs/ardour/ardour/ardour.h index 51101a50ad..5ce29d7580 100644 --- a/libs/ardour/ardour/ardour.h +++ b/libs/ardour/ardour/ardour.h @@ -63,8 +63,6 @@ namespace ARDOUR { std::string translation_kill_path (); bool translations_are_disabled (); - const layer_t max_layer = UCHAR_MAX; - static inline microseconds_t get_microseconds () { return (microseconds_t) jack_get_time(); } diff --git a/libs/ardour/ardour/playlist.h b/libs/ardour/ardour/playlist.h index e125905e17..f3bf11458d 100644 --- a/libs/ardour/ardour/playlist.h +++ b/libs/ardour/ardour/playlist.h @@ -44,6 +44,9 @@ #include "ardour/session_object.h" #include "ardour/data_type.h" +class PlaylistOverlapCacheTest; +class PlaylistLayeringTest; + namespace ARDOUR { class Session; @@ -215,12 +218,6 @@ public: void drop_regions (); - bool explicit_relayering () const { - return _explicit_relayering; - } - - void set_explicit_relayering (bool e); - virtual boost::shared_ptr<Crossfade> find_crossfade (const PBD::ID &) const { return boost::shared_ptr<Crossfade> (); } @@ -228,6 +225,10 @@ public: framepos_t find_next_top_layer_position (framepos_t) const; uint32_t combine_ops() const { return _combine_ops; } + void relayer (boost::shared_ptr<Region>, double); + void suspend_relayer (); + void resume_relayer (); + protected: friend class Session; @@ -288,17 +289,10 @@ public: bool _frozen; uint32_t subcnt; PBD::ID _orig_track_id; - uint64_t layer_op_counter; framecnt_t freeze_length; bool auto_partition; uint32_t _combine_ops; - /** true if relayering should be done using region's current layers and their `pending explicit relayer' - * flags; otherwise false if relayering should be done using the layer-model (most recently moved etc.) - * Explicit relayering is used by tracks in stacked regionview mode. - */ - bool _explicit_relayering; - void init (bool hide); bool holding_state () const { @@ -361,16 +355,14 @@ public: boost::shared_ptr<Playlist> cut (framepos_t start, framecnt_t cnt, bool result_is_hidden); boost::shared_ptr<Playlist> copy (framepos_t start, framecnt_t cnt, bool result_is_hidden); - int move_region_to_layer (layer_t, boost::shared_ptr<Region> r, int dir); - void relayer (); + void relayer (boost::shared_ptr<Region>); + void relayer (RegionList const &); void begin_undo (); void end_undo (); void unset_freeze_parent (Playlist*); void unset_freeze_child (Playlist*); - void timestamp_layer_op (boost::shared_ptr<Region>); - void _split_region (boost::shared_ptr<Region>, framepos_t position); typedef std::pair<boost::shared_ptr<Region>, boost::shared_ptr<Region> > TwoRegions; @@ -391,6 +383,71 @@ public: with its constituent regions */ virtual void pre_uncombine (std::vector<boost::shared_ptr<Region> >&, boost::shared_ptr<Region>) {} + +private: + friend class ::PlaylistOverlapCacheTest; + friend class ::PlaylistLayeringTest; + + /** A class which is used to store temporary (fractional) + * layer assignments for some regions. + */ + class TemporaryLayers + { + public: + void set (boost::shared_ptr<Region>, double); + double get (boost::shared_ptr<Region>) const; + + private: + typedef std::map<boost::shared_ptr<Region>, double> Map; + Map _map; + }; + + /** Class to sort by temporary layer, for use with std::list<>::sort() */ + class SortByTemporaryLayer + { + public: + SortByTemporaryLayer (TemporaryLayers const & t) + : _temporary_layers (t) {} + + bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) const { + return _temporary_layers.get (a) < _temporary_layers.get (b); + } + + private: + Playlist::TemporaryLayers const & _temporary_layers; + }; + + /** A cache of what overlaps what, for a given playlist in a given state. + * Divides a playlist up into time periods and notes which regions cover those + * periods, so that get() is reasonably quick. + */ + class OverlapCache + { + public: + OverlapCache (Playlist *); + + RegionList get (Evoral::Range<framepos_t>) const; + + private: + std::pair<int, int> cache_indices (Evoral::Range<framepos_t>) const; + + double _division_size; + std::vector<RegionList> _cache; + Evoral::Range<framepos_t> _range; + + static int const _divisions; + }; + + TemporaryLayers compute_temporary_layers (RegionList const &); + void commit_temporary_layers (TemporaryLayers const &); + + RegionList recursive_regions_touched (boost::shared_ptr<Region>, OverlapCache const &, boost::shared_ptr<Region>) const; + void recursive_regions_touched_sub (boost::shared_ptr<Region>, OverlapCache const &, boost::shared_ptr<Region>, RegionList &) const; + + void timestamp_layer_op (LayerOp, boost::shared_ptr<Region>); + uint64_t layer_op_counter; + + bool _relayer_suspended; }; } /* namespace ARDOUR */ diff --git a/libs/ardour/ardour/region.h b/libs/ardour/ardour/region.h index a9d18f95ca..4315ed7aa8 100644 --- a/libs/ardour/ardour/region.h +++ b/libs/ardour/ardour/region.h @@ -112,6 +112,7 @@ class Region framepos_t start () const { return _start; } framecnt_t length () const { return _length; } layer_t layer () const { return _layer; } + Evoral::Range<framepos_t> bounds () const; framecnt_t source_length(uint32_t n) const; uint32_t max_source_level () const; @@ -201,7 +202,7 @@ class Region void cut_front (framepos_t new_position); void cut_end (framepos_t new_position); - void set_layer (layer_t l); /* ONLY Playlist can call this */ + void set_layer (layer_t l); /* ONLY Playlist should call this */ void raise (); void lower (); void raise_to_top (); @@ -251,8 +252,8 @@ class Region virtual boost::shared_ptr<Region> get_parent() const; - uint64_t last_layer_op() const { return _last_layer_op; } - void set_last_layer_op (uint64_t when); + uint64_t last_layer_op (LayerOp) const; + void set_last_layer_op (LayerOp, uint64_t); virtual bool is_dependent() const { return false; } virtual bool depends_on (boost::shared_ptr<Region> /*other*/) const { return false; } @@ -293,16 +294,13 @@ class Region void invalidate_transients (); - void set_pending_explicit_relayer (bool p) { - _pending_explicit_relayer = p; - } + void drop_sources (); - bool pending_explicit_relayer () const { - return _pending_explicit_relayer; + /** @return our bounds the last time our relayer() method was called */ + Evoral::Range<framepos_t> last_relayer_bounds () const { + return Evoral::Range<framepos_t> (_last_relayer_bounds_from, _last_relayer_bounds_to); } - void drop_sources (); - protected: friend class RegionFactory; @@ -387,16 +385,17 @@ class Region PBD::Property<float> _shift; PBD::EnumProperty<PositionLockStyle> _position_lock_style; + /* XXX: could use a Evoral::Range<> but I'm too lazy to make PBD::Property serialize such a thing nicely */ + PBD::Property<framepos_t> _last_relayer_bounds_from; ///< from of our bounds last time relayer() was called + PBD::Property<framepos_t> _last_relayer_bounds_to; ///< to of our bounds last time relayer() was called + PBD::Property<uint64_t> _last_layer_op_add; + PBD::Property<uint64_t> _last_layer_op_bounds_change; + framecnt_t _last_length; framepos_t _last_position; mutable RegionEditState _first_edit; Timecode::BBT_Time _bbt_time; - uint64_t _last_layer_op; ///< timestamp - - /** true if this region has had its layer explicitly set since the playlist last relayered */ - bool _pending_explicit_relayer; - void register_properties (); void use_sources (SourceList const &); diff --git a/libs/ardour/ardour/region_sorters.h b/libs/ardour/ardour/region_sorters.h index ee34dcaafe..0e3203ef20 100644 --- a/libs/ardour/ardour/region_sorters.h +++ b/libs/ardour/ardour/region_sorters.h @@ -30,31 +30,24 @@ struct RegionSortByPosition { } }; -struct RegionSortByLastLayerOp { +struct RegionSortByLayer { bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) { - return a->last_layer_op() < b->last_layer_op(); + return a->layer() < b->layer(); } }; -struct RegionSortByLayer { +struct RegionSortByAdd { bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) { - return a->layer() < b->layer(); + return ( + (a->last_layer_op (LayerOpAdd) < b->last_layer_op (LayerOpAdd)) + ); } }; -struct RegionSortByLayerWithPending { - bool operator () (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) { - - double p = a->layer (); - if (a->pending_explicit_relayer()) { - p += 0.5; - } - - double q = b->layer (); - if (b->pending_explicit_relayer()) { - q += 0.5; - } - +struct RegionSortByAddOrBounds { + bool operator() (boost::shared_ptr<Region> a, boost::shared_ptr<Region> b) { + uint64_t const p = std::max (a->last_layer_op (LayerOpAdd), a->last_layer_op (LayerOpBoundsChange)); + uint64_t const q = std::max (b->last_layer_op (LayerOpAdd), b->last_layer_op (LayerOpBoundsChange)); return p < q; } }; diff --git a/libs/ardour/ardour/session_configuration_vars.h b/libs/ardour/ardour/session_configuration_vars.h index 45300d3e0f..57f390f100 100644 --- a/libs/ardour/ardour/session_configuration_vars.h +++ b/libs/ardour/ardour/session_configuration_vars.h @@ -47,7 +47,8 @@ CONFIG_VARIABLE_SPECIAL(std::string, audio_search_path, "audio-search-path", "", CONFIG_VARIABLE_SPECIAL(std::string, midi_search_path, "midi-search-path", "", search_path_expand) CONFIG_VARIABLE (std::string, bwf_country_code, "bwf-country-code", "US") CONFIG_VARIABLE (std::string, bwf_organization_code, "bwf-organization-code", "US") -CONFIG_VARIABLE (LayerModel, layer_model, "layer-model", MoveAddHigher) +CONFIG_VARIABLE (LayerModel, layer_model, "layer-model", AddOrBoundsChangeHigher) +CONFIG_VARIABLE (bool, relayer_on_all_edits, "relayer-on-all-edits", true) CONFIG_VARIABLE (std::string, auditioner_output_left, "auditioner-output-left", "default") CONFIG_VARIABLE (std::string, auditioner_output_right, "auditioner-output-right", "default") CONFIG_VARIABLE (bool, timecode_source_is_synced, "timecode-source-is-synced", true) diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index 7d5c5626e3..b5e5d89cc1 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -75,6 +75,7 @@ namespace ARDOUR { static const framepos_t max_framepos = INT64_MAX; static const framecnt_t max_framecnt = INT64_MAX; + static const layer_t max_layer = UINT32_MAX; // a set of (time) intervals: first of pair is the offset of the start within the region, second is the offset of the end typedef std::list<std::pair<frameoffset_t, frameoffset_t> > AudioIntervalResult; @@ -410,7 +411,7 @@ namespace ARDOUR { enum LayerModel { LaterHigher, - MoveAddHigher, + AddOrBoundsChangeHigher, AddHigher }; @@ -591,6 +592,11 @@ namespace ARDOUR { FadeLogB }; + enum LayerOp { + LayerOpAdd, + LayerOpBoundsChange + }; + } // namespace ARDOUR diff --git a/libs/ardour/audio_diskstream.cc b/libs/ardour/audio_diskstream.cc index 603179a8e2..83399d2962 100644 --- a/libs/ardour/audio_diskstream.cc +++ b/libs/ardour/audio_diskstream.cc @@ -1510,15 +1510,6 @@ AudioDiskstream::transport_stopped_wallclock (struct tm& when, time_t twhen, boo } i_am_the_modifier++; - - if (_playlist->explicit_relayering()) { - /* We are in `explicit relayering' mode, so we must specify which layer this new region - should end up on. Put it at the top. - */ - region->set_layer (_playlist->top_layer() + 1); - region->set_pending_explicit_relayer (true); - } - _playlist->add_region (region, (*ci)->start, 1, non_layered()); i_am_the_modifier--; diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index 1f75f5ab43..12de366c79 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -265,7 +265,7 @@ setup_enum_writer () REGISTER (_CrossfadeModel); REGISTER_ENUM (LaterHigher); - REGISTER_ENUM (MoveAddHigher); + REGISTER_ENUM (AddOrBoundsChangeHigher); REGISTER_ENUM (AddHigher); REGISTER (_LayerModel); 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<Region> r) { Evoral::RangeMove<framepos_t> 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<Region> r) void Playlist::notify_region_start_trimmed (boost::shared_ptr<Region> 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<Region> r) void Playlist::notify_region_end_trimmed (boost::shared_ptr<Region> 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<Region> 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<Region> r) pending_contents_change = false; RegionAdded (boost::weak_ptr<Region> (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<Region> (*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<Region> (*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<Playlist> @@ -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> 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<vector<RegionList> > layers; - layers.push_back (vector<RegionList> (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<RegionList> (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> 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> 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> 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> region) +Playlist::relayer (boost::shared_ptr<Region> 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> region, int dir) +void +Playlist::raise_region (boost::shared_ptr<Region> region) { - RegionList::iterator i; - typedef pair<boost::shared_ptr<Region>,layer_t> LayerInfo; - list<LayerInfo> layerinfo; - - { - RegionLock rlock (const_cast<Playlist *> (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<LayerInfo>::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<LayerInfo>::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> region) +{ + relayer (region, region->layer() - 0.5); +} - thaw (); +void +Playlist::raise_region_to_top (boost::shared_ptr<Region> region) +{ + relayer (region, max_layer); +} - return 0; +void +Playlist::lower_region_to_bottom (boost::shared_ptr<Region> region) +{ + relayer (region, -0.5); } void @@ -2763,15 +2722,23 @@ Playlist::set_frozen (bool yn) } void -Playlist::timestamp_layer_op (boost::shared_ptr<Region> region) +Playlist::timestamp_layer_op (LayerOp op, boost::shared_ptr<Region> 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> 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> 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<void(boost::shared_ptr<Region>)> 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<Region> 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<Region> 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<framepos_t> (max_framepos, 0); + RegionList const & rl = playlist->region_list().rlist (); + for (RegionList::const_iterator i = rl.begin(); i != rl.end(); ++i) { + Evoral::Range<framepos_t> 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<int, int> 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<int, int> +Playlist::OverlapCache::cache_indices (Evoral::Range<framepos_t> range) const +{ + range.from = max (range.from, _range.from); + range.to = min (range.to, _range.to); + + pair<int, int> 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<framepos_t> range) const +{ + if (_range.from == max_framepos) { + return RegionList (); + } + + RegionList r; + + pair<int, int> 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> region, OverlapCache const & cache, boost::shared_ptr<Region> 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> region, OverlapCache const & cache, boost::shared_ptr<Region> 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); + } + } +} + diff --git a/libs/ardour/region.cc b/libs/ardour/region.cc index adc6334334..89abdbd230 100644 --- a/libs/ardour/region.cc +++ b/libs/ardour/region.cc @@ -73,6 +73,10 @@ namespace ARDOUR { PBD::PropertyDescriptor<float> stretch; PBD::PropertyDescriptor<float> shift; PBD::PropertyDescriptor<PositionLockStyle> position_lock_style; + PBD::PropertyDescriptor<framepos_t> last_relayer_bounds_from; + PBD::PropertyDescriptor<framepos_t> last_relayer_bounds_to; + PBD::PropertyDescriptor<uint64_t> last_layer_op_add; + PBD::PropertyDescriptor<uint64_t> last_layer_op_bounds_change; } } @@ -127,6 +131,14 @@ Region::make_property_quarks () DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for shift = %1\n", Properties::shift.property_id)); Properties::position_lock_style.property_id = g_quark_from_static_string (X_("positional-lock-style")); DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for position_lock_style = %1\n", Properties::position_lock_style.property_id)); + Properties::last_relayer_bounds_from.property_id = g_quark_from_static_string (X_("last-relayer-bounds-from")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_relayer_bounds_from = %1\n", Properties::last_relayer_bounds_from.property_id)); + Properties::last_relayer_bounds_to.property_id = g_quark_from_static_string (X_("last-relayer-bounds-to")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_relayer_bounds_to = %1\n", Properties::last_relayer_bounds_to.property_id)); + Properties::last_layer_op_add.property_id = g_quark_from_static_string (X_("last-layer-op-add")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_layer_op_add = %1\n", Properties::last_layer_op_add.property_id)); + Properties::last_layer_op_bounds_change.property_id = g_quark_from_static_string (X_("last-layer-op-bounds-change")); + DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for last_layer_op_bounds_change = %1\n", Properties::last_layer_op_bounds_change.property_id)); } void @@ -157,6 +169,10 @@ Region::register_properties () add_property (_stretch); add_property (_shift); add_property (_position_lock_style); + add_property (_last_relayer_bounds_from); + add_property (_last_relayer_bounds_to); + add_property (_last_layer_op_add); + add_property (_last_layer_op_bounds_change); } #define REGION_DEFAULT_STATE(s,l) \ @@ -182,7 +198,11 @@ Region::register_properties () , _ancestral_length (Properties::ancestral_length, (l)) \ , _stretch (Properties::stretch, 1.0) \ , _shift (Properties::shift, 1.0) \ - , _position_lock_style (Properties::position_lock_style, _type == DataType::AUDIO ? AudioTime : MusicTime) + , _position_lock_style (Properties::position_lock_style, _type == DataType::AUDIO ? AudioTime : MusicTime) \ + , _last_relayer_bounds_from (Properties::last_relayer_bounds_from, 0) \ + , _last_relayer_bounds_to (Properties::last_relayer_bounds_to, 0) \ + , _last_layer_op_add (Properties::last_layer_op_add, 0) \ + , _last_layer_op_bounds_change (Properties::last_layer_op_bounds_change, 0) #define REGION_COPY_STATE(other) \ _sync_marked (Properties::sync_marked, other->_sync_marked) \ @@ -207,7 +227,11 @@ Region::register_properties () , _ancestral_length (Properties::ancestral_length, other->_ancestral_length) \ , _stretch (Properties::stretch, other->_stretch) \ , _shift (Properties::shift, other->_shift) \ - , _position_lock_style (Properties::position_lock_style, other->_position_lock_style) + , _position_lock_style (Properties::position_lock_style, other->_position_lock_style) \ + , _last_relayer_bounds_from (Properties::last_relayer_bounds_from, other->_last_relayer_bounds_from) \ + , _last_relayer_bounds_to (Properties::last_relayer_bounds_to, other->_last_relayer_bounds_to) \ + , _last_layer_op_add (Properties::last_layer_op_add, other->_last_layer_op_add) \ + , _last_layer_op_bounds_change (Properties::last_layer_op_bounds_change, other->_last_layer_op_bounds_change) /* derived-from-derived constructor (no sources in constructor) */ Region::Region (Session& s, framepos_t start, framecnt_t length, const string& name, DataType type) @@ -217,11 +241,8 @@ Region::Region (Session& s, framepos_t start, framecnt_t length, const string& n , _last_length (length) , _last_position (0) , _first_edit (EditChangesNothing) - , _last_layer_op(0) - , _pending_explicit_relayer (false) { register_properties (); - /* no sources at this point */ } @@ -233,8 +254,6 @@ Region::Region (const SourceList& srcs) , _last_length (0) , _last_position (0) , _first_edit (EditChangesNothing) - , _last_layer_op (0) - , _pending_explicit_relayer (false) { register_properties (); @@ -254,9 +273,6 @@ Region::Region (boost::shared_ptr<const Region> other) , _last_length (other->_last_length) , _last_position(other->_last_position) \ , _first_edit (EditChangesNothing) - , _last_layer_op (0) - , _pending_explicit_relayer (false) - { register_properties (); @@ -326,9 +342,6 @@ Region::Region (boost::shared_ptr<const Region> other, frameoffset_t offset) , _last_length (other->_last_length) , _last_position(other->_last_position) \ , _first_edit (EditChangesNothing) - , _last_layer_op (0) - , _pending_explicit_relayer (false) - { register_properties (); @@ -383,8 +396,6 @@ Region::Region (boost::shared_ptr<const Region> other, const SourceList& srcs) , _last_length (other->_last_length) , _last_position (other->_last_position) , _first_edit (EditChangesID) - , _last_layer_op (other->_last_layer_op) - , _pending_explicit_relayer (false) { register_properties (); @@ -1123,9 +1134,12 @@ Region::set_layer (layer_t l) { if (_layer != l) { _layer = l; - send_change (Properties::layer); } + + Evoral::Range<framepos_t> const b = bounds (); + _last_relayer_bounds_from = b.from; + _last_relayer_bounds_to = b.to; } XMLNode& @@ -1317,9 +1331,16 @@ Region::send_change (const PropertyChange& what_changed) } void -Region::set_last_layer_op (uint64_t when) +Region::set_last_layer_op (LayerOp op, uint64_t when) { - _last_layer_op = when; + switch (op) { + case LayerOpAdd: + _last_layer_op_add = when; + break; + case LayerOpBoundsChange: + _last_layer_op_bounds_change = when; + break; + } } bool @@ -1661,3 +1682,25 @@ Region::post_set (const PropertyChange& pc) recompute_position_from_lock_style (); } } + +uint64_t +Region::last_layer_op (LayerOp op) const +{ + switch (op) { + case LayerOpAdd: + return _last_layer_op_add; + case LayerOpBoundsChange: + return _last_layer_op_bounds_change; + } + + /* NOTREACHED */ + return 0; +} + +Evoral::Range<framepos_t> +Region::bounds () const +{ + return Evoral::Range<framepos_t> (_position, _position + _length); +} + + diff --git a/libs/ardour/run-tests.sh b/libs/ardour/run-tests.sh index 379678c968..76fc4f8aea 100755 --- a/libs/ardour/run-tests.sh +++ b/libs/ardour/run-tests.sh @@ -19,6 +19,8 @@ export ARDOUR_PANNER_PATH=$libs/panners/2in2out:$libs/panners/1in2out:$libs/pann if [ "$1" == "--debug" ]; then gdb ./libs/ardour/run-tests +elif [ "$1" == "--valgrind" ]; then + valgrind --tool="memcheck" ./libs/ardour/run-tests else ./libs/ardour/run-tests fi diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 7d6f44b786..532d010680 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -974,7 +974,27 @@ int Session::load_options (const XMLNode& node) { LocaleGuard lg (X_("POSIX")); - config.set_variables (node); + + /* Copy the node */ + XMLNode node_copy = node; + + /* XXX: evil hack: fix up sessions from before the layering alterations + (during A3 beta) + */ + + XMLNodeList children = node_copy.children (); + for (XMLNodeIterator i = children.begin(); i != children.end(); ++i) { + XMLProperty* p = (*i)->property (X_("name")); + if (p && p->name() == X_("name") && p->value() == X_("layer-model") ) { + p = (*i)->property (X_("value")); + if (p && p->value() == X_("MoveAddHigher")) { + (*i)->add_property (X_("value"), X_("AddOrBoundsChangeHigher")); + } + } + } + + config.set_variables (node_copy); + return 0; } diff --git a/libs/ardour/test/playlist_layering_test.cc b/libs/ardour/test/playlist_layering_test.cc index 1371b0cfae..23b22f9d90 100644 --- a/libs/ardour/test/playlist_layering_test.cc +++ b/libs/ardour/test/playlist_layering_test.cc @@ -1,13 +1,14 @@ -#include "midi++/manager.h" -#include "pbd/textreceiver.h" #include "pbd/compose.h" -#include "ardour/session.h" -#include "ardour/audioengine.h" +#include "midi++/manager.h" #include "ardour/playlist_factory.h" #include "ardour/source_factory.h" #include "ardour/region.h" #include "ardour/region_factory.h" +#include "ardour/session.h" +#include "ardour/audiosource.h" +#include "ardour/audioengine.h" #include "playlist_layering_test.h" +#include "test_receiver.h" CPPUNIT_TEST_SUITE_REGISTRATION (PlaylistLayeringTest); @@ -15,68 +16,28 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -class TestReceiver : public Receiver -{ -protected: - void receive (Transmitter::Channel chn, const char * str) { - const char *prefix = ""; - - switch (chn) { - case Transmitter::Error: - prefix = ": [ERROR]: "; - break; - case Transmitter::Info: - /* ignore */ - return; - case Transmitter::Warning: - prefix = ": [WARNING]: "; - break; - case Transmitter::Fatal: - prefix = ": [FATAL]: "; - break; - case Transmitter::Throw: - /* this isn't supposed to happen */ - abort (); - } - - /* note: iostreams are already thread-safe: no external - lock required. - */ - - cout << prefix << str << endl; - - if (chn == Transmitter::Fatal) { - exit (9); - } - } -}; - -TestReceiver test_receiver; +int const PlaylistLayeringTest::num_regions = 6; void PlaylistLayeringTest::setUp () { - string const test_session_path = "libs/ardour/test/playlist_layering_test"; - string const test_wav_path = "libs/ardour/test/playlist_layering_test/playlist_layering_test.wav"; - system (string_compose ("rm -rf %1", test_session_path).c_str()); + TestNeedingSession::setUp (); + string const test_wav_path = "libs/ardour/test/test.wav"; - init (false, true); - SessionEvent::create_per_thread_pool ("test", 512); - - test_receiver.listen_to (error); - test_receiver.listen_to (info); - test_receiver.listen_to (fatal); - test_receiver.listen_to (warning); - - AudioEngine* engine = new AudioEngine ("test", ""); - MIDI::Manager::create (engine->jack ()); - CPPUNIT_ASSERT (engine->start () == 0); - - _session = new Session (*engine, test_session_path, "playlist_layering_test"); - engine->set_session (_session); - _playlist = PlaylistFactory::create (DataType::AUDIO, *_session, "test"); _source = SourceFactory::createWritable (DataType::AUDIO, *_session, test_wav_path, "", false, 44100); + + system ("pwd"); + + /* Must write some data to our source, otherwise regions which use it will + be limited in whether they can be trimmed or not. + */ + boost::shared_ptr<AudioSource> a = boost::dynamic_pointer_cast<AudioSource> (_source); + Sample silence[512]; + memset (silence, 0, 512 * sizeof (Sample)); + a->write (silence, 512); + + _region = new boost::shared_ptr<Region>[num_regions]; } void @@ -84,68 +45,303 @@ PlaylistLayeringTest::tearDown () { _playlist.reset (); _source.reset (); - for (int i = 0; i < 16; ++i) { + for (int i = 0; i < num_regions; ++i) { _region[i].reset (); } - AudioEngine::instance()->remove_session (); - delete _session; - EnumWriter::destroy (); - MIDI::Manager::destroy (); - AudioEngine::destroy (); + delete[] _region; + + TestNeedingSession::tearDown (); } void -PlaylistLayeringTest::create_three_short_regions () +PlaylistLayeringTest::create_short_regions () { PropertyList plist; plist.add (Properties::start, 0); plist.add (Properties::length, 100); - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < num_regions; ++i) { _region[i] = RegionFactory::create (_source, plist); + _region[i]->set_name (string_compose ("%1", char (int ('A') + i))); } } void -PlaylistLayeringTest::addHigherTest () +PlaylistLayeringTest::laterHigher_relayerOnAll_Test () +{ + _session->config.set_layer_model (LaterHigher); + _session->config.set_relayer_on_all_edits (true); + + create_short_regions (); + + /* three overlapping regions */ + _playlist->add_region (_region[A], 0); + _playlist->add_region (_region[B], 10); + _playlist->add_region (_region[C], 20); + /* and another non-overlapping one */ + _playlist->add_region (_region[D], 200); + + /* LaterHigher means that they should be arranged thus */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + _region[A]->set_position (5); + + /* Region move should have no effect in LaterHigher mode */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* C -> bottom should give C A B, not touching D */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* C -> top should go back to A B C, not touching D */ + _region[C]->raise_to_top (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); +} + +void +PlaylistLayeringTest::addHigher_relayerOnAll_Test () { _session->config.set_layer_model (AddHigher); - create_three_short_regions (); + _session->config.set_relayer_on_all_edits (true); + + create_short_regions (); + + /* three overlapping regions */ + _playlist->add_region (_region[A], 0); + _playlist->add_region (_region[B], 10); + _playlist->add_region (_region[C], 20); + /* and another non-overlapping one */ + _playlist->add_region (_region[D], 200); + + /* AddHigher means that they should be arranged thus */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - _playlist->add_region (_region[0], 0); - _playlist->add_region (_region[1], 10); - _playlist->add_region (_region[2], 20); + _region[A]->set_position (5); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[0]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[1]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[2]->layer ()); + /* region move should have no effect in AddHigher mode */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - _region[0]->set_position (5); + /* C -> bottom should give C A B, not touching D */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - /* region move should have no effect */ - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[0]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[1]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[2]->layer ()); + /* C -> top should go back to A B C, not touching D */ + _region[C]->raise_to_top (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); } void -PlaylistLayeringTest::moveAddHigherTest () +PlaylistLayeringTest::addOrBoundsHigher_relayerOnAll_Test () { - _session->config.set_layer_model (MoveAddHigher); - create_three_short_regions (); + _session->config.set_layer_model (AddOrBoundsChangeHigher); + _session->config.set_relayer_on_all_edits (true); + + create_short_regions (); + + /* three overlapping regions */ + _playlist->add_region (_region[A], 0); + _playlist->add_region (_region[B], 10); + _playlist->add_region (_region[C], 20); + /* and another non-overlapping one */ + _playlist->add_region (_region[D], 200); + + /* AddOrBoundsHigher means that they should be arranged thus */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* region move should put A on top for B C A, not touching D */ + _region[A]->set_position (5); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* C -> bottom should give C B A, not touching D */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* C -> top should go back to B A C, not touching D */ + _region[C]->raise_to_top (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Put C on the bottom */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Now move it slightly, and it should go back to the top again */ + _region[C]->set_position (21); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Put C back on the bottom */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Trim it slightly, and it should go back to the top again */ + _region[C]->trim_front (23); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Same with the end */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + _region[C]->trim_end (118); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); +} + +void +PlaylistLayeringTest::addOrBoundsHigher_relayerWhenNecessary_Test () +{ + _session->config.set_layer_model (AddOrBoundsChangeHigher); + _session->config.set_relayer_on_all_edits (false); + + create_short_regions (); + + /* three overlapping regions */ + _playlist->add_region (_region[A], 0); + _playlist->add_region (_region[B], 10); + _playlist->add_region (_region[C], 20); + /* and another non-overlapping one */ + _playlist->add_region (_region[D], 200); + + /* AddOrBoundsHigher means that they should be arranged thus */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + _region[A]->set_position (5); + + /* region move should not have changed anything, since in + this mode we only relayer when there is a new overlap + */ + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* C -> bottom should give C A B, not touching D */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* C -> top should go back to A B C, not touching D */ + _region[C]->raise_to_top (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Put C on the bottom */ + _region[C]->lower_to_bottom (); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + + /* Now move it slightly, and it should stay where it is */ + _region[C]->set_position (21); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); +} + +void +PlaylistLayeringTest::lastLayerOpTest () +{ + create_short_regions (); + + _playlist->add_region (_region[A], 0); + CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpAdd)); + uint64_t const last_add = _region[A]->last_layer_op (LayerOpAdd); + + _region[A]->set_position (42); + CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpBoundsChange)); + CPPUNIT_ASSERT_EQUAL (last_add, _region[A]->last_layer_op (LayerOpAdd)); + + _region[A]->trim_front (46); + CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpBoundsChange)); + CPPUNIT_ASSERT_EQUAL (last_add, _region[A]->last_layer_op (LayerOpAdd)); + + _region[A]->trim_end (102); + CPPUNIT_ASSERT_EQUAL (_playlist->layer_op_counter, _region[A]->last_layer_op (LayerOpBoundsChange)); + CPPUNIT_ASSERT_EQUAL (last_add, _region[A]->last_layer_op (LayerOpAdd)); +} + +void +PlaylistLayeringTest::recursiveRelayerTest () +{ + _session->config.set_layer_model (AddOrBoundsChangeHigher); + _session->config.set_relayer_on_all_edits (false); + + create_short_regions (); - _playlist->add_region (_region[0], 0); - _playlist->add_region (_region[1], 10); - _playlist->add_region (_region[2], 20); + _playlist->add_region (_region[A], 100); + _playlist->add_region (_region[B], 125); + _playlist->add_region (_region[C], 50); + _playlist->add_region (_region[D], 250); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[0]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[1]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[2]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[C]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); - _region[0]->set_position (5); + _region[A]->set_position (200); - /* region move should have put 0 on top */ - CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[0]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[1]->layer ()); - CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[2]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (0), _region[D]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (1), _region[A]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (2), _region[B]->layer ()); + CPPUNIT_ASSERT_EQUAL (layer_t (3), _region[C]->layer ()); } diff --git a/libs/ardour/test/playlist_layering_test.h b/libs/ardour/test/playlist_layering_test.h index 7757e017c4..ab5fe701b1 100644 --- a/libs/ardour/test/playlist_layering_test.h +++ b/libs/ardour/test/playlist_layering_test.h @@ -1,5 +1,6 @@ #include <cppunit/TestFixture.h> #include <cppunit/extensions/HelperMacros.h> +#include "test_needing_session.h" namespace ARDOUR { class Session; @@ -7,25 +8,42 @@ namespace ARDOUR { class Source; } -class PlaylistLayeringTest : public CppUnit::TestFixture +class PlaylistLayeringTest : public TestNeedingSession { CPPUNIT_TEST_SUITE (PlaylistLayeringTest); - CPPUNIT_TEST (addHigherTest); - CPPUNIT_TEST (moveAddHigherTest); + CPPUNIT_TEST (lastLayerOpTest); + CPPUNIT_TEST (addHigher_relayerOnAll_Test); + CPPUNIT_TEST (addOrBoundsHigher_relayerOnAll_Test); + CPPUNIT_TEST (laterHigher_relayerOnAll_Test); + CPPUNIT_TEST (addOrBoundsHigher_relayerWhenNecessary_Test); + CPPUNIT_TEST (recursiveRelayerTest); CPPUNIT_TEST_SUITE_END (); public: void setUp (); void tearDown (); - void addHigherTest (); - void moveAddHigherTest (); + void lastLayerOpTest (); + void addHigher_relayerOnAll_Test (); + void addOrBoundsHigher_relayerOnAll_Test (); + void laterHigher_relayerOnAll_Test (); + void addOrBoundsHigher_relayerWhenNecessary_Test (); + void recursiveRelayerTest (); private: - void create_three_short_regions (); + void create_short_regions (); + + static int const num_regions; + enum { + A = 0, + B, + C, + D, + E, + F + }; - ARDOUR::Session* _session; boost::shared_ptr<ARDOUR::Playlist> _playlist; boost::shared_ptr<ARDOUR::Source> _source; - boost::shared_ptr<ARDOUR::Region> _region[16]; + boost::shared_ptr<ARDOUR::Region>* _region; }; diff --git a/libs/ardour/test/playlist_overlap_cache_test.cc b/libs/ardour/test/playlist_overlap_cache_test.cc new file mode 100644 index 0000000000..9263d80d3b --- /dev/null +++ b/libs/ardour/test/playlist_overlap_cache_test.cc @@ -0,0 +1,119 @@ +#include "pbd/compose.h" +#include "ardour/playlist.h" +#include "ardour/playlist_factory.h" +#include "ardour/source_factory.h" +#include "ardour/region.h" +#include "ardour/region_sorters.h" +#include "ardour/region_factory.h" +#include "playlist_overlap_cache_test.h" + +using namespace std; +using namespace PBD; +using namespace ARDOUR; + +CPPUNIT_TEST_SUITE_REGISTRATION (PlaylistOverlapCacheTest); + +void +PlaylistOverlapCacheTest::tearDown () +{ + _playlist.reset (); + _source.reset (); + + TestNeedingSession::tearDown (); +} + +void +PlaylistOverlapCacheTest::basicTest () +{ + string const test_wav_path = "libs/ardour/test/test.wav"; + + _playlist = PlaylistFactory::create (DataType::AUDIO, *_session, "test"); + _source = SourceFactory::createWritable (DataType::AUDIO, *_session, test_wav_path, "", false, 44100); + + PropertyList plist; + plist.add (Properties::length, 256); + + boost::shared_ptr<Region> regionA = RegionFactory::create (_source, plist); + regionA->set_name ("A"); + _playlist->add_region (regionA, 0); + + + { + Playlist::OverlapCache cache (_playlist.get ()); + Playlist::RegionList rl = cache.get (Evoral::Range<framepos_t> (0, 256)); + CPPUNIT_ASSERT_EQUAL (size_t (1), rl.size ()); + CPPUNIT_ASSERT_EQUAL (regionA, rl.front ()); + + rl = cache.get (Evoral::Range<framepos_t> (-1000, 1000)); + CPPUNIT_ASSERT_EQUAL (size_t (1), rl.size ()); + CPPUNIT_ASSERT_EQUAL (regionA, rl.front ()); + } + + boost::shared_ptr<Region> regionB = RegionFactory::create (_source, plist); + regionA->set_name ("B"); + _playlist->add_region (regionB, 53); + + { + Playlist::OverlapCache cache (_playlist.get ()); + Playlist::RegionList rl = cache.get (Evoral::Range<framepos_t> (0, 256)); + CPPUNIT_ASSERT_EQUAL (size_t (2), rl.size ()); + rl.sort (RegionSortByPosition ()); + CPPUNIT_ASSERT_EQUAL (regionA, rl.front ()); + CPPUNIT_ASSERT_EQUAL (regionB, rl.back ()); + + rl = cache.get (Evoral::Range<framepos_t> (260, 274)); + CPPUNIT_ASSERT_EQUAL (size_t (1), rl.size ()); + CPPUNIT_ASSERT_EQUAL (regionB, rl.front ()); + } +} + +void +PlaylistOverlapCacheTest::stressTest () +{ + string const test_wav_path = "libs/ardour/test/test.wav"; + + _playlist = PlaylistFactory::create (DataType::AUDIO, *_session, "test"); + _source = SourceFactory::createWritable (DataType::AUDIO, *_session, test_wav_path, "", false, 44100); + + srand (42); + + int const num_regions = rand () % 256; + + for (int i = 0; i < num_regions; ++i) { + PropertyList plist; + plist.add (Properties::length, rand () % 32768); + boost::shared_ptr<Region> r = RegionFactory::create (_source, plist); + r->set_name (string_compose ("%1", i)); + _playlist->add_region (r, rand() % 32768); + } + + Playlist::OverlapCache cache (_playlist.get ()); + + int const tests = rand () % 256; + + for (int i = 0; i < tests; ++i) { + framepos_t const start = rand () % 32768; + framepos_t const length = rand () % 32768; + framepos_t const end = start + length; + + Playlist::RegionList cached = cache.get (Evoral::Range<framepos_t> (start, end)); + + Playlist::RegionList actual; + Playlist::RegionList regions = _playlist->region_list().rlist(); + for (Playlist::RegionList::iterator j = regions.begin(); j != regions.end(); ++j) { + if ((*j)->coverage (start, end) != OverlapNone) { + actual.push_back (*j); + } + } + + cached.sort (RegionSortByPosition ()); + actual.sort (RegionSortByPosition ()); + + CPPUNIT_ASSERT_EQUAL (actual.size (), cached.size ()); + Playlist::RegionList::iterator j = actual.begin (); + Playlist::RegionList::iterator k = cached.begin (); + for (; j != actual.end(); ++j, ++k) { + CPPUNIT_ASSERT_EQUAL (*j, *k); + } + } +} diff --git a/libs/ardour/test/playlist_overlap_cache_test.h b/libs/ardour/test/playlist_overlap_cache_test.h new file mode 100644 index 0000000000..c75d1691d9 --- /dev/null +++ b/libs/ardour/test/playlist_overlap_cache_test.h @@ -0,0 +1,20 @@ +#include "test_needing_session.h" + +class PlaylistOverlapCacheTest : public TestNeedingSession +{ +public: + CPPUNIT_TEST_SUITE (PlaylistOverlapCacheTest); + CPPUNIT_TEST (basicTest); + CPPUNIT_TEST (stressTest); + CPPUNIT_TEST_SUITE_END (); + +public: + void tearDown (); + + void basicTest (); + void stressTest (); + +private: + boost::shared_ptr<ARDOUR::Playlist> _playlist; + boost::shared_ptr<ARDOUR::Source> _source; +}; diff --git a/libs/ardour/test/test_needing_session.cc b/libs/ardour/test/test_needing_session.cc new file mode 100644 index 0000000000..5981553601 --- /dev/null +++ b/libs/ardour/test/test_needing_session.cc @@ -0,0 +1,48 @@ +#include "midi++/manager.h" +#include "pbd/textreceiver.h" +#include "pbd/compose.h" +#include "pbd/enumwriter.h" +#include "ardour/session.h" +#include "ardour/audioengine.h" +#include "test_needing_session.h" +#include "test_receiver.h" + +using namespace std; +using namespace ARDOUR; +using namespace PBD; + +TestReceiver test_receiver; + +void +TestNeedingSession::setUp () +{ + string const test_session_path = "libs/ardour/test/test_session"; + system (string_compose ("rm -rf %1", test_session_path).c_str()); + + init (false, true); + SessionEvent::create_per_thread_pool ("test", 512); + + test_receiver.listen_to (error); + test_receiver.listen_to (info); + test_receiver.listen_to (fatal); + test_receiver.listen_to (warning); + + AudioEngine* engine = new AudioEngine ("test", ""); + MIDI::Manager::create (engine->jack ()); + CPPUNIT_ASSERT (engine->start () == 0); + + _session = new Session (*engine, test_session_path, "test_session"); + engine->set_session (_session); +} + +void +TestNeedingSession::tearDown () +{ + AudioEngine::instance()->remove_session (); + + delete _session; + + EnumWriter::destroy (); + MIDI::Manager::destroy (); + AudioEngine::destroy (); +} diff --git a/libs/ardour/test/test_needing_session.h b/libs/ardour/test/test_needing_session.h new file mode 100644 index 0000000000..3839855179 --- /dev/null +++ b/libs/ardour/test/test_needing_session.h @@ -0,0 +1,16 @@ +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +namespace ARDOUR { + class Session; +} + +class TestNeedingSession : public CppUnit::TestFixture +{ +public: + void setUp (); + void tearDown (); + +protected: + ARDOUR::Session* _session; +}; diff --git a/libs/ardour/test/test_receiver.h b/libs/ardour/test/test_receiver.h new file mode 100644 index 0000000000..537bac3cff --- /dev/null +++ b/libs/ardour/test/test_receiver.h @@ -0,0 +1,37 @@ +#include "pbd/receiver.h" + +class TestReceiver : public Receiver +{ +protected: + void receive (Transmitter::Channel chn, const char * str) { + const char *prefix = ""; + + switch (chn) { + case Transmitter::Error: + prefix = ": [ERROR]: "; + break; + case Transmitter::Info: + /* ignore */ + return; + case Transmitter::Warning: + prefix = ": [WARNING]: "; + break; + case Transmitter::Fatal: + prefix = ": [FATAL]: "; + break; + case Transmitter::Throw: + /* this isn't supposed to happen */ + abort (); + } + + /* note: iostreams are already thread-safe: no external + lock required. + */ + + std::cout << prefix << str << std::endl; + + if (chn == Transmitter::Fatal) { + exit (9); + } + } +}; diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 42c72784f9..e49be33b8c 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -431,6 +431,8 @@ def build(bld): test/framepos_plus_beats_test.cc test/framepos_minus_beats_test.cc test/playlist_layering_test.cc + test/playlist_overlap_cache_test.cc + test/test_needing_session.cc test/testrunner.cc '''.split() |