From b205c0bc4b51274697fa251784ad454892a47dce Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Tue, 12 Aug 2025 07:53:30 -0600 Subject: [PATCH] scoped tempo maps: logic fixes and some comment-documentation --- libs/temporal/tempo.cc | 59 +++++++++++++++++++++++++++ libs/temporal/temporal/scope.h | 73 +++++++++++++++++++++++----------- libs/temporal/temporal/tempo.h | 7 ++-- 3 files changed, 112 insertions(+), 27 deletions(-) diff --git a/libs/temporal/tempo.cc b/libs/temporal/tempo.cc index 01d73d9aee..4d5d4cc008 100644 --- a/libs/temporal/tempo.cc +++ b/libs/temporal/tempo.cc @@ -32,6 +32,7 @@ #include "pbd/string_convert.h" #include "temporal/debug.h" +#include "temporal/scope.h" #include "temporal/tempo.h" #include "temporal/types_convert.h" @@ -5172,3 +5173,61 @@ TempoMapCutBuffer::clear () _bartimes.clear (); _points.clear (); } + +void +ScopedTempoMapOwner::start_local_tempo_map (std::shared_ptr map) +{ + /* This one is a little complicated to explain. This is where we set + * the _local_tempo_map pointer which holds the local tempo map that + * this ScopedTempoMapOwner wants to use every time one of its methods + * is used. But we must also emulate the side effects of calling + * ::in(), which include incrementing the depth and setting the + * thread-local tempo map pointer. + * + * The caller should have placed an EC_LOCAL_TEMPO_SCOPE macro before + * the call to this method, so that when the caller returns, we will + * call ::out() and decrement the depth back to zero. + * + * Subsequent to that, all later in() and out() calls will install and + * uninstall the local tempo map as the depth transitions from and to + * zero, and will maintain the depth counter appropriately. + */ + + + DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: starting local tempo scope\n", scope_name())); + map->set_scope_owner (*this); + _local_tempo_map = map; + local_tempo_map_depth = 1; + Temporal::TempoMap::set (_local_tempo_map); +} + +void +ScopedTempoMapOwner::end_local_tempo_map () +{ + /* Like ::start_local_tempo_map() and ::in(), this needs to emulate the + * side effects of calling ::out(), ehich are reducing the depth + * counter to zero and resetting the thread local tempo map pointer to + * the current global tempo map. + * + * The caller need not have used EC_LOCAL_TEMPO_SCOPE before calling + * this, however, since there are no side effects to undo when we leave + * the caller's scope. + */ + + DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: ending local tempo scope\n", scope_name())); + assert (_local_tempo_map); + _local_tempo_map->clear_scope_owner (); + _local_tempo_map.reset (); + local_tempo_map_depth = 0; + Temporal::TempoMap::fetch (); +} + +/* This is defined here for use in an assert() made in tempo.h. Circular + * dependencies between ScopedTempoMapOwner and TempoMap mean that we can't + * place uses of a ScopedTempoMapOwner in tempo.h + */ +bool +TempoMap::fetch_condition () +{ + return !_tempo_map_p || !_tempo_map_p->scope_owner() || _tempo_map_p->scope_owner()->depth() == 0; +} diff --git a/libs/temporal/temporal/scope.h b/libs/temporal/temporal/scope.h index caeb16b718..ffbb4d6828 100644 --- a/libs/temporal/temporal/scope.h +++ b/libs/temporal/temporal/scope.h @@ -25,35 +25,59 @@ #include "temporal/debug.h" #include "temporal/tempo.h" +#include "temporal/visibility.h" namespace Temporal { -class ScopedTempoMapOwner +class TempoMap; + +/* This object aand the use of the EC_LOCAL_TEMPO_MAP_SCOPE allows derived + * classes to have a "local" (i.e. non-global) tempo map in use. This is + * intended for custom editing contexts where all conversion between + * sample/pixel and beat time should use a local tempo map, rather than the + * global one. + * + * The downside is a bit ugly: every method of a derived class should have a + * call to EC_LOCAL_TEMPO_MAP_SCOPE before anything else. However, in C++ there + * is no other way to accomplish this, since there is no method-based code + * injection. This macro uses an RAII technique (via TempoMapScope) to call the + * ::in() and ::out() methods of the ScopedTempoMapOwner on entry and exist + * from the method scope. + * + * A derived class will call start_local_tempo_map() to provide the map it + * should be using, and end_local_tempo_map() when for whatever reason that map + * is no longer relevant. + * + * start_local_tempo_map() will set the local_tempo_map (shared) ptr, which + * gives us an indication as we enter and leave the scope of class methods that + * there is a map which should be in use. There is also a depth counter so that + * we only set the thread-local tempo map pointer when we transition from + * depth==0 to depth==1. See notes in the method definition for some important + * caveats. + * + * end_local_tempo_map() is called when the object's methods should no longer + * use the previously provided local tempo map. + * + * The cost of this is low (but not zero, obviously): + * + * - extra pointer on the stack (the scope member of TempoMapScope) + * - two conditionals, the first of which will frequently fail + * - writes to the thread-local pointer whenever the method dept + * - transitions between 0 and 1 (in either direction). + */ + +class LIBTEMPORAL_API ScopedTempoMapOwner { public: - ScopedTempoMapOwner () : local_tempo_map_depth (0) {} - virtual ~ScopedTempoMapOwner () {} + LIBTEMPORAL_API ScopedTempoMapOwner () : local_tempo_map_depth (0) {} + virtual LIBTEMPORAL_API ~ScopedTempoMapOwner () {} - void start_local_tempo_map (std::shared_ptr map) { - DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: starting local tempo scope\n", scope_name())); - map->set_scope_owner (*this); - _local_tempo_map = map; - Temporal::TempoMap::set (_local_tempo_map); - local_tempo_map_depth = 1; - } + void LIBTEMPORAL_API start_local_tempo_map (std::shared_ptr map); + void LIBTEMPORAL_API end_local_tempo_map (); - void end_local_tempo_map () { - DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: ending local tempo scope\n", scope_name())); - assert (_local_tempo_map); - local_tempo_map_depth = 0; - _local_tempo_map->clear_scope_owner (); - _local_tempo_map.reset (); - Temporal::TempoMap::fetch (); - } + uint64_t LIBTEMPORAL_API depth() const { return local_tempo_map_depth; } - uint64_t depth() const { return local_tempo_map_depth; } - - virtual std::string scope_name() const = 0; + virtual LIBTEMPORAL_API std::string scope_name() const = 0; protected: mutable std::shared_ptr _local_tempo_map; @@ -64,8 +88,10 @@ class ScopedTempoMapOwner void in () const { if (_local_tempo_map && local_tempo_map_depth++ == 0 ) { - DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: in to local tempo %2\n", scope_name(), local_tempo_map_depth)); + DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: in to local tempo %2, TMAP set\n", scope_name(), local_tempo_map_depth)); Temporal::TempoMap::set (_local_tempo_map); + } else { + DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: in to local tempo %2, no tm set\n", scope_name(), local_tempo_map_depth)); } } @@ -96,5 +122,4 @@ struct TempoMapScope { } // namespace -// #define EC_LOCAL_TEMPO_SCOPE Temporal::TempoMapScope __tms (*this); -#define EC_LOCAL_TEMPO_SCOPE +#define EC_LOCAL_TEMPO_SCOPE Temporal::TempoMapScope __tms (*this); diff --git a/libs/temporal/temporal/tempo.h b/libs/temporal/temporal/tempo.h index 9caff963d3..385bb431ff 100644 --- a/libs/temporal/temporal/tempo.h +++ b/libs/temporal/temporal/tempo.h @@ -739,12 +739,13 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible private: static thread_local SharedPtr _tempo_map_p; static SerializedRCUManager _map_mgr; + static bool fetch_condition (); public: LIBTEMPORAL_API static void init (); LIBTEMPORAL_API static void update_thread_tempo_map() { _tempo_map_p = _map_mgr.reader(); } LIBTEMPORAL_API static SharedPtr use() { assert (_tempo_map_p); return _tempo_map_p; } - LIBTEMPORAL_API static SharedPtr fetch() { assert (!_tempo_map_p || !_tempo_map_p->scope_owner()); update_thread_tempo_map(); return _tempo_map_p; } + LIBTEMPORAL_API static SharedPtr fetch() { assert (fetch_condition()); update_thread_tempo_map(); return _tempo_map_p; } /* Used only by the ARDOUR::AudioEngine API to reset the process thread * tempo map only when it has changed. @@ -1010,8 +1011,8 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible static void map_assert (bool expr, char const * exprstr, char const * file, int line); - void set_scope_owner (ScopedTempoMapOwner&); - void clear_scope_owner (); + void LIBTEMPORAL_API set_scope_owner (ScopedTempoMapOwner&); + void LIBTEMPORAL_API clear_scope_owner (); ScopedTempoMapOwner* scope_owner() const { return _scope_owner; } private: