#pragma once #include "c-filewatcher.hh" #include "c-project.hh" #include #include #include // Duration and current playing time struct T_SyncTime { float uDuration = 1.f / 60.f; // Duration - unit size uint32_t iDuration = 3600; // Duration - total units float time = 0; void setDuration( const float uDuration , const uint32_t iDuration ); float duration( ) const noexcept { return uDuration * iDuration; } void setTime( const float t ) noexcept { time = std::max( 0.f , std::min( t , duration( ) ) ); } }; /*============================================================================*/ // Segment of an input's curve struct T_SyncSegment { enum E_SegmentType { LINEAR , RAMP , SMOOTH , //HERMITE }; E_SegmentType type; T_Array< float > values{ T_Array< float >( 16 ) }; T_Array< uint32_t > durations{ T_Array< uint32_t >( 16 ) }; // Find the amount of time units before point uint32_t findTimeOfPoint( uint32_t index ) const noexcept; // Compute the value for the specified position, which is relative // to the start of the segment. float computeValue( float relTimeUnits ) const noexcept; // Is the segment constant? If it is, return the value. T_Optional< float > isConstant( ) const noexcept; }; M_LSHIFT_OP( T_StringBuilder , T_SyncSegment::E_SegmentType ); /*----------------------------------------------------------------------------*/ // Possible results when trying to check whether two curves have a similar // structure. enum class E_SyncCurveMatch { // Both curves have the same structure. IDENTICAL , // One of the curves has less information than the other, but the // information in the shortest curve matches the corresponding // information in the longest curve (i.e. it is only missing // segments). _SHORT/_LONG indicate that the curve for which the // comparison method was called is the shortest/longest (resp.) MATCHING_SHORT , MATCHING_LONG , // One of the curves has less information than the other, and its // last segment is shorter than the corresponding segment in the // longest record (but its end matches a point in the longest curve's // segment). LASTSEG_SHORT , LASTSEG_LONG , // The curves do not match. MISMATCH }; // An input curve struct T_SyncCurve { T_String name; T_Array< T_SyncSegment > segments{ 16 }; T_SyncCurve( ) noexcept { } T_SyncCurve( T_String const& name ) noexcept : name( name ) { } T_SyncCurve( char const* name ) noexcept : name( T_String( name ).usePool( ) ) { } // Compute the value at the specified time. float computeValue( float timeUnits ) const noexcept; // Compare this curve to another. E_SyncCurveMatch matches( T_SyncCurve const& other ) const noexcept; // Count the points in the curve. A "point" corresponds to a value // inside a segment, or the first and last value in the curve, or // a junction between segments. uint32_t points( ) const noexcept; // Does the curve represent a constant? It does if all values in // all segments are identical; in that case the value is returned. T_Optional< float > isConstant( ) const noexcept; }; /*----------------------------------------------------------------------------*/ // All configured curves. Some may not actually correspond to an input and may // have been defined for inputs that have been removed temporarily (e.g. // because some include was commented out), in which case we don't want to // waste them. struct T_SyncCurves { T_ObjectTable< T_String , T_SyncCurve > curves; T_SyncCurves( ) : curves( []( T_SyncCurve const& c ) -> T_String { return c.name; } ) { } void clear( ) noexcept { curves.clear( ); } void setCurve( T_SyncCurve curve ) noexcept { curves.set( std::move( curve ) ); } bool removeCurve( T_String const& curve ) noexcept { return curves.remove( curve ); } // Returns -1 on lookup failure int32_t indexOf( T_String const& name ) noexcept; int32_t indexOf( char const* name ) noexcept { return indexOf( T_String( name ) ); } }; // Curves data file loader/writer struct T_SyncCurvesIO : public ebcl::A_PrivateImplementation { T_SyncCurvesIO( ) noexcept; struct T_Data { T_SyncCurves curves; T_Optional< T_SyncTime > time; ebcl::T_SRDLocation tLocation; }; T_Data load( T_FSPath const& path ) const; void save( T_FSPath const& path , T_SyncCurves const& curves , T_SyncTime const& time ) const; }; /*----------------------------------------------------------------------------*/ // Pre-computed data for a curve struct T_SyncCurveCache { using T_SegRef = std::pair< uint32_t , uint32_t >; uint32_t curve; T_Array< T_SegRef > segRefs; T_Array< float > segStarts; T_Array< float > segEnds; uint32_t curPos; T_SyncCurveCache( T_SyncTime const& time , T_SyncCurves const& curves , const uint32_t curve ) noexcept; // Find the index of the segment for the specified time uint32_t findSegment( const float time ) const noexcept; // Compute the value of the curve at the specified location, ignoring // curPos. float valueAt( T_SyncTime const& time , T_SyncCurves const& curves , const float position ) const noexcept; // Compute the value of the curve at the current time, using and // updating curPos as necessary. float value( T_SyncTime const& time , T_SyncCurves const& curves ) noexcept; float segmentValue( float time , uint32_t segIndex , T_Array< T_SyncSegment > const& segments ) const noexcept; }; using P_SyncCurveCache = T_OwnPtr< T_SyncCurveCache >; /*============================================================================*/ // Synchronization values. The values vector always contains an extra entry // used for missing inputs. struct T_SyncValues { T_HashIndex index; T_Array< T_String > identifiers; T_Array< float > values; T_Array< bool > overriden; T_SyncValues( ); void clear( ); // Returns true on success, false on duplicate bool addValue( T_String const& name , const float initial = 0.f ) noexcept; bool addValue( char const* name , const float initial = 0.f ) noexcept { return addValue( T_String( name ) , initial ); } // If the name isn't found, the last entry of values[] is returned uint32_t indexOf( T_String const& name ) const noexcept; }; /*============================================================================*/ class A_SyncOverride; using P_SyncOverride = T_OwnPtr< A_SyncOverride >; struct T_SyncOverrideSection; using P_SyncOverrideSection = T_OwnPtr< T_SyncOverrideSection >; // Base class for overrides class A_SyncOverride { private: const T_String type_; bool enabled_{ false }; ebcl::T_SRDLocation location_; T_String id_; protected: ebcl::T_Buffer< char > title_; ebcl::T_Set< T_String > inputs_{ ebcl::UseTag< ebcl::ArrayBacked< 8 > >( ) }; T_AutoArray< uint32_t , 8 > inputPos_; T_String fullTitle_; A_SyncOverride( char const* type , T_String const& title ) noexcept; public: A_SyncOverride( ) = delete; virtual ~A_SyncOverride( ) = 0; T_String const& type( ) const noexcept { return type_; } auto const& inputNames( ) const noexcept { return inputs_; } auto const& inputPositions( ) const noexcept { return inputPos_; } bool& enabled( ) noexcept { return enabled_; } bool enabled( ) const noexcept { return enabled_; } T_String const& id( ) const noexcept { return id_; } char const* title( ) const noexcept { return &title_[ 0 ]; } ebcl::T_SRDLocation& location( ) noexcept { return location_; } ebcl::T_SRDLocation const& location( ) const noexcept { return location_; } T_String const& fullTitle( ) const noexcept { return fullTitle_; } void fullTitle( T_String ft ) noexcept { assert( !fullTitle_ ); fullTitle_ = std::move( ft ); } // Connect the required inputs to the sync manager. Called once // the inputs have been added. virtual void setup( ) noexcept; // Create a clone of the current override. virtual T_OwnPtr< A_SyncOverride > clone( ) const noexcept = 0; }; // Overrides section struct T_SyncOverrideSection { const T_String title; const ebcl::T_Buffer< char > cTitle; bool open{ false }; T_Array< P_SyncOverrideSection > subsections; T_Array< P_SyncOverride > overrides; T_SyncOverrideSection( ) = delete; NO_COPY( T_SyncOverrideSection ); NO_MOVE( T_SyncOverrideSection ); explicit T_SyncOverrideSection( T_String title ) noexcept; void merge( T_SyncOverrideSection& other ) noexcept; // Generate the UI. A counter and temporary string builder are passed // to help generating "fake" labels for use with ImGui. void makeUI( uint32_t& counter , T_StringBuilder& sb , bool topLevel = false ) noexcept; T_SyncOverrideSection& section( T_String const& name ) noexcept; T_SyncOverrideSection const* section( T_String const& name ) const noexcept; }; /*----------------------------------------------------------------------------*/ struct T_SyncOverrideVisitor { public: using T_Element = ebcl::T_Union< T_SyncOverrideSection* , A_SyncOverride* >; using T_OpElement = T_Optional< T_Element >; using T_Visitor = ebcl::T_Visitor< T_Element , T_Element >; using F_NodeAction = T_Visitor::F_NodeAction; static T_OpElement nodeBrowser( T_Element element , uint32_t child ); T_Visitor visitor{ nodeBrowser }; }; /*============================================================================*/ // Synchronisation manager; handles all the synchronization data and makes it // work together. struct T_SyncManager : public virtual A_ProjectPathListener { T_SyncManager( ) noexcept; ~T_SyncManager( ); void enable( ) noexcept; void disable( ) noexcept { enabled_ = false; } bool enabled( ) const noexcept { return enabled_; } // --------------------------------------------------------------------- // Duration & time controls void setDuration( const float uDuration , const uint32_t iDuration ); float duration( ) const noexcept { return time_.duration( ); } uint32_t durationUnits( ) const noexcept { return time_.iDuration; } float durationUnitSize( ) const noexcept { return time_.uDuration; } void setTime( const float time ); void timeDelta( const float delta ) { setTime( time_.time + delta ); } float time( ) const noexcept { return time_.time; } bool playing( ) const noexcept { return playing_; } bool& playing( ) noexcept { return playing_; } bool finished( ) const noexcept { return time_.time >= time_.duration( ); } void updateTime( ) noexcept; // --------------------------------------------------------------------- // Value access void clearInputs( ) noexcept; bool addInput( T_String const& name , const float initial = 0.f ) noexcept { return values_.addValue( name , initial ); } bool hasInput( T_String const& name ) const noexcept { return values_.identifiers.contains( name ); } T_Array< T_String > const& inputNames( ) const noexcept { return values_.identifiers; } uint32_t inputPos( T_String const& name ) const noexcept { return values_.indexOf( name ); } T_Array< float > const& inputs( ) const noexcept { return values_.values; } T_Array< float >& inputs( ) noexcept { return values_.values; } // --------------------------------------------------------------------- // Curves void clearCurves( ); void setCurve( T_SyncCurve curve ); void removeCurve( T_String const& curve ) noexcept; T_SyncCurve const* getCurve( T_String const& name ) const noexcept; T_Array< T_SyncCurve > const& curves( ) const noexcept { return curves_.curves.values( ); } bool curvesModified( ) const noexcept { return modified_; } bool curvesFileChanged( ) const noexcept { return fileChanged_; } bool loadCurves( bool undoable = true ); bool saveCurves( ); private: void curvesChanged_( ); void addReloadUndoData_( T_SyncCurvesIO::T_Data const& data ) const noexcept; // --------------------------------------------------------------------- // Overrides public: void clearOverrides( ) noexcept; void mergeOverrides( T_SyncOverrideSection& overrides ); bool overrideExists( T_String const& id ) const noexcept; A_SyncOverride* getOverride( T_String const& id ) const noexcept; // Mark a bunch of inputs as overridden / not overridden void setOverridesActive( bool active , uint32_t n , uint32_t const* pos ); // Visit the overrides tree void visitOverrides( T_SyncOverrideVisitor::F_NodeAction visitor ); // --------------------------------------------------------------------- // Update void updateCurveCaches( ); void updateValues( ); // --------------------------------------------------------------------- // Private data private: bool enabled_{ false }; // Interactive mode enabled? T_SyncCurvesIO io_; // Curves loader/writer T_FSPath curvesFile_; // Path to the curves file T_WatchedFiles watcher_; // Curves file watcher bool saving_{ false }; // True if file is being saved bool modified_; // Locally modified bool fileChanged_; // File modified T_SyncTime time_; // Duration/time information bool playing_{ false }; // Is it playing? T_SyncValues values_; // Value storage T_SyncCurves curves_; // Curves storage T_Array< P_SyncCurveCache > curveCaches_; // Cache for curve segments T_SyncOverrideSection soRoot_; // Root for overrides T_SyncOverrideVisitor soVisitor_; T_KeyValueTable< T_String , A_SyncOverride* > soTable_; // Table of sync overrides, by ID protected: void projectPathChanged( ) noexcept; };