#include "externals.hh" #include "common.hh" #include "c-sync.hh" #include "c-syncedit.hh" #include "c-undo.hh" #include #include #include using ebcl::T_SRDParserConfig; /*= SRD parser for the curves ================================================*/ namespace { using T_ParserOutput_ = T_SyncCurvesIO::T_Data; using namespace ebcl; bool CPEnterCurve_( T_SRDParserData const& data ) { *( data.targetData ) = T_SyncCurve{ (*data.input)[ 0 ].stringValue( ) }; return true; } bool CPExitCurve_( T_SRDParserData const& data ) { auto& curve( data.currentData->value< T_SyncCurve >( ) ); auto& po( *( data.targetData->value< T_SharedPtr< T_ParserOutput_ > >( ) ) ); if ( curve.segments.empty( ) ) { T_StringBuilder sb; sb << "curve '" << curve.name << "' is empty"; data.errors.add( std::move( sb ) , (*data.input)[ 0 ] ); } if ( po.curves.curves.contains( curve.name ) ) { T_StringBuilder sb; sb << "duplicate curve '" << curve.name << "'"; data.errors.add( std::move( sb ) , (*data.input)[ 0 ] ); } else { po.curves.setCurve( std::move( curve ) ); } return true; } void CPHandleSegment_( const T_SyncSegment::E_SegmentType type , T_SRDList const& lValues , T_SRDList const& lDurations , T_SRDErrors& errors , T_SyncCurve& curve ) { bool failed = false; if ( lDurations.size( ) != lValues.size( ) - 1 ) { errors.add( "values / durations count mismatch" , lValues[ 0 ] ); failed = true; } // Check durations const auto nd( lDurations.size( ) ); for ( auto i = 1u ; i < nd ; i ++ ) { auto const& tok( lDurations[ i ] ); const uint64_t v( tok.longValue( ) ); if ( v < 1 || v > UINT32_MAX ) { errors.add( "invalid duration" , tok ); failed = true; } } if ( !failed ) { T_SyncSegment& segment( curve.segments.addNew( ) ); segment.type = type; segment.durations.ensureCapacity( nd - 1 ); for ( auto i = 1u ; i < nd ; i ++ ) { auto const& tok( lDurations[ i ] ); segment.durations.add( uint32_t( tok.longValue( ) ) ); } segment.values.ensureCapacity( nd ); for ( auto i = 1u ; i <= nd ; i ++ ) { auto const& tok( lValues[ i ] ); segment.values.add( tok.floatValue( ) ); } } } bool CPSegmentVD_( T_SRDParserData const& data ) { auto const& input( *data.input ); const auto ev( data.config.enumValue( "segment-type" , input[ 1 ].stringValue( ) ) ); assert( ev ); CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev , input[ 2 ].list( ) , input[ 3 ].list( ) , data.errors , data.targetData->value< T_SyncCurve >( ) ); return true; } bool CPSegmentDV_( T_SRDParserData const& data ) { auto const& input( *data.input ); const auto ev( data.config.enumValue( "segment-type" , input[ 1 ].stringValue( ) ) ); assert( ev ); CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev , input[ 3 ].list( ) , input[ 2 ].list( ) , data.errors , data.targetData->value< T_SyncCurve >( ) ); return true; } bool CPSetDuration_( T_SRDParserData const& data ) { auto const& input( *data.input ); auto& po( *( data.currentData->value< T_SharedPtr< T_ParserOutput_ > >( ) ) ); if ( po.time ) { T_StringBuilder eb; eb << "duplicate duration specification; previous: " << po.tLocation; data.errors.add( std::move( eb ) , input[ 0 ].location( ) ); } else { po.time = T_SyncTime( ); po.time->uDuration = std::min( 2. , std::max( 1. / 60. , input[ 1 ].floatValue( ) ) ); po.time->iDuration = std::max( 1l , input[ 2 ].longValue( ) ); } return true; } T_SRDParserConfig MakeCurvesParser_( ) { using namespace ebcl::SRD; T_SRDParserDefs defs( "default" ); defs << OnStart( []( T_SRDParserData const& data ) -> bool { *( data.currentData ) = NewShared< T_ParserOutput_ >( ); return true; } ); defs.enumeration( "segment-type" ) << "linear" << "ramp" << "smooth"; defs.context( "default" ) << ( Rule( ) << Text( ) << EnterContext( "segments" ) << OnEnter( CPEnterCurve_ ) << OnExit( CPExitCurve_ ) ) << ( Rule( ) << "duration" << Float( ) << Int32( ) << CPSetDuration_ ); defs.context( "segments" ) << ( Rule( ) << "segment" << Enum( "segment-type" ) << ( List( ) << "values" << ( AtLeast( 2 ) << Numeric( ) ) ) << ( List( ) << "durations" << ( AtLeast( 1 ) << Integer( ) ) ) << CPSegmentVD_ ) << ( Rule( ) << "segment" << Enum( "segment-type" ) << ( List( ) << "durations" << ( AtLeast( 1 ) << Integer( ) ) ) << ( List( ) << "values" << ( AtLeast( 2 ) << Numeric( ) ) ) << CPSegmentDV_ ); return defs; } } /*= T_SyncTime ===============================================================*/ void T_SyncTime::setDuration( const float uDuration , const uint32_t iDuration ) { this->uDuration = std::max( 1e-3f , uDuration ); this->iDuration = std::max( 1u , iDuration ); time = std::min( time , duration( ) ); } /*= T_SyncSegment ============================================================*/ uint32_t T_SyncSegment::findTimeOfPoint( const uint32_t index ) const noexcept { assert( index <= durations.size( ) ); uint32_t t{ 0 }; for ( auto i = 0u ; i < index ; i ++ ) { t += durations[ i ]; } return t; } float T_SyncSegment::computeValue( const float relTimeUnits ) const noexcept { uint32_t part{ 0 } , begin{ 0 } , end{ durations[ 0 ] }; while ( end < relTimeUnits ) { part ++; begin = end; end += durations[ part ]; } const float v0{ ( relTimeUnits - begin ) / ( end - begin ) }; float v = v0; if ( type != T_SyncSegment::LINEAR ) { v *= v0; if ( type == T_SyncSegment::SMOOTH ) { v *= 3 - 2 * v0; } } const float sv{ values[ part ] }; return v * ( values[ part + 1 ] - sv ) + sv; } T_Optional< float > T_SyncSegment::isConstant( ) const noexcept { const float f{ values[ 0 ] }; for ( auto v : values ) { if ( f != v ) { return {}; } } return f; } M_LSHIFT_OP( T_StringBuilder , T_SyncSegment::E_SegmentType ) { switch ( value ) { case T_SyncSegment::LINEAR: obj << "linear"; break; case T_SyncSegment::RAMP: obj << "ramp"; break; case T_SyncSegment::SMOOTH: obj << "smooth"; break; } return obj; } /*= T_SyncCurve ==============================================================*/ float T_SyncCurve::computeValue( const float timeUnits ) const noexcept { assert( timeUnits >= 0 ); uint32_t segStart{ 0 } , prevSegStart{ 0 }; auto it{ segments.begin( ) }; while ( segStart < timeUnits && it != segments.end( ) ) { prevSegStart = segStart; for ( auto d : it->durations ) { segStart += d; } ++ it; } -- it; if ( segStart < timeUnits ) { return it->values.last( ); } return it->computeValue( timeUnits - prevSegStart ); } E_SyncCurveMatch T_SyncCurve::matches( T_SyncCurve const& other ) const noexcept { const auto tSegs{ this->segments.size( ) }; const auto oSegs{ other.segments.size( ) }; const auto nSegs{ std::min( tSegs , oSegs ) }; for ( auto i = 0u ; i < nSegs ; i ++ ) { auto const& tDur{ this->segments[ i ].durations }; auto const& oDur{ other.segments[ i ].durations }; const bool lastSeg{ i == nSegs - 1 }; if ( !lastSeg && tDur.size( ) != oDur.size( ) ) { return E_SyncCurveMatch::MISMATCH; } const auto nd{ std::min( tDur.size( ) , oDur.size( ) ) }; for ( auto j = 0u ; j < nd ; j ++ ) { if ( tDur[ j ] != oDur[ j ] ) { return E_SyncCurveMatch::MISMATCH; } } if ( tDur.size( ) != oDur.size( ) ) { return tDur.size( ) < oDur.size( ) ? E_SyncCurveMatch::LASTSEG_SHORT : E_SyncCurveMatch::LASTSEG_LONG; } } if ( tSegs == oSegs ) { return E_SyncCurveMatch::IDENTICAL; } return tSegs < oSegs ? E_SyncCurveMatch::MATCHING_SHORT : E_SyncCurveMatch::MATCHING_LONG; } uint32_t T_SyncCurve::points( ) const noexcept { uint32_t np{ 1 }; for ( auto const& s : segments ) { np += s.durations.size( ); } return np; } T_Optional< float > T_SyncCurve::isConstant( ) const noexcept { T_Optional< float > v; for ( auto const& s : segments ) { v = s.isConstant( ); if ( !v ) { break; } } return v; } /*= T_SyncCurves =============================================================*/ int32_t T_SyncCurves::indexOf( T_String const& name ) noexcept { const auto idx( curves.indexOf( name ) ); return idx == ebcl::T_HashIndex::INVALID_INDEX ? -1 : int32_t( idx ); } /*= T_SyncCurvesIO ===========================================================*/ namespace { struct T_SCIOImpl_ { T_SCIOImpl_( ) noexcept; T_SyncCurvesIO::T_Data load( T_FSPath const& path ) const; T_SRDParserConfig pConfig; }; /*----------------------------------------------------------------------------*/ inline T_SCIOImpl_::T_SCIOImpl_( ) noexcept : pConfig{ MakeCurvesParser_( ) } { } inline T_SyncCurvesIO::T_Data T_SCIOImpl_::load( T_FSPath const& path ) const { T_File file( path , E_FileMode::READ_ONLY ); file.open( ); T_FileInputStream fis( file ); T_SRDParser parser( pConfig ); T_SRDTextReader reader( parser ); reader.read( path.components( ).last( ) , fis ); return std::move( *parser.getData< T_SharedPtr< T_ParserOutput_ > >( ) ); } /*----------------------------------------------------------------------------*/ } // namespace T_SyncCurvesIO::T_SyncCurvesIO( ) noexcept : A_PrivateImplementation( new T_SCIOImpl_( ) ) { } T_SyncCurvesIO::T_Data T_SyncCurvesIO::load( T_FSPath const& path ) const { return p< T_SCIOImpl_ >( ).load( path ); } void T_SyncCurvesIO::save( T_FSPath const& path , T_SyncCurves const& curves , T_SyncTime const& time ) const { T_StringBuilder sb; sb << "(duration " << time.uDuration << ' ' << time.iDuration << ")\n"; for ( auto const& curve : curves.curves.values( ) ) { sb << "\n(" << curve.name << '\n'; for ( auto const& s : curve.segments ) { sb << "\t(segment " << s.type << "\n\t\t(values"; for ( auto v : s.values ) { sb << ' ' << v; } sb << ")\n\t\t(durations"; for ( auto d : s.durations ) { sb << ' ' << d; } sb << ")\n\t)\n"; } sb << ")\n"; } T_File{ path , E_FileMode::OVERWRITE }.write( sb.data( ) , sb.size( ) ); } /*= T_SyncCurveCache =========================================================*/ T_SyncCurveCache::T_SyncCurveCache( T_SyncTime const& time , T_SyncCurves const& curves , const uint32_t curve ) noexcept : curve( curve ) , curPos( 0 ) { auto const& c( curves.curves[ curve ] ); const auto ns( c.segments.size( ) ); assert( ns > 0 ); uint32_t s = 0; for ( auto i = 0u ; i < ns ; i ++ ) { auto const& v( c.segments[ i ] ); assert( v.durations.size( ) == v.values.size( ) - 1 ); const auto nd( v.durations.size( ) ); for ( auto j = 0u ; j < nd ; j ++ ) { const auto sStart( s * time.uDuration ); if ( time.time >= sStart ) { curPos = segStarts.size( ); } segStarts.add( sStart ); segRefs.add( std::make_pair( i , j ) ); s += v.durations[ j ]; segEnds.add( std::min( s , time.iDuration ) * time.uDuration ); if ( s > time.iDuration ) { return; } } } } /*----------------------------------------------------------------------------*/ uint32_t T_SyncCurveCache::findSegment( const float time ) const noexcept { const auto ns( segStarts.size( ) ); for ( auto i = 0u ; i < ns ; i ++ ) { if ( segStarts[ i ] <= time && segEnds[ i ] > time ) { return i; } } return ns; } /*----------------------------------------------------------------------------*/ float T_SyncCurveCache::valueAt( T_SyncTime const& time , T_SyncCurves const& curves , const float position ) const noexcept { return segmentValue( time.time , findSegment( position ) , curves.curves[ curve ].segments ); } float T_SyncCurveCache::value( T_SyncTime const& time , T_SyncCurves const& curves ) noexcept { const auto t( time.time ); // Check / update curPos const float ss0( curPos == segStarts.size( ) ? time.duration( ) : segStarts[ curPos ] ); if ( ss0 > t ) { curPos = findSegment( t ); } else { while ( curPos < segStarts.size( ) && t >= segEnds[ curPos ] ) { curPos ++; } } // We got the actual index in curPos, now compute the value. return segmentValue( t , curPos , curves.curves[ curve ].segments ); } /*----------------------------------------------------------------------------*/ float T_SyncCurveCache::segmentValue( float time , uint32_t segIndex , T_Array< T_SyncSegment > const& segments ) const noexcept { const auto sss( segStarts.size( ) ); if ( segIndex >= sss ) { assert( sss != 0 ); segIndex = sss - 1; time = segEnds[ segIndex ]; } auto const& idxp( segRefs[ segIndex ] ); auto const& seg( segments[ idxp.first ] ); // Interpolation factor const float st( segStarts[ segIndex ] ); const float et( segEnds[ segIndex ] ); const float v0 = ( time - st ) / ( et - st ); float v = v0; if ( seg.type != T_SyncSegment::LINEAR ) { v *= v0; if ( seg.type == T_SyncSegment::SMOOTH ) { v *= 3 - 2 * v0; } } const auto pid( idxp.second ); const float sv( seg.values[ pid ] ); const float ev( seg.values[ pid + 1 ] ); #if 0 printf( "[%.2f] gidx %d - seg %d idx %d - %f\n" , time , segIndex , idxp.first , idxp.second , v * ( ev - sv ) + sv ); #endif return v * ( ev - sv ) + sv; } /*= T_SyncValues =============================================================*/ T_SyncValues::T_SyncValues( ) { values.add( 0 ); } // --------------------------------------------------------------------- void T_SyncValues::clear( ) { index.clear( ); identifiers.clear( ); values.clear( ); overriden.clear( ); values.add( 0 ); } bool T_SyncValues::addValue( T_String const& name , const float initial ) noexcept { const uint32_t hash{ ComputeHash( name ) }; uint32_t existing{ index.first( hash ) }; while ( existing != T_HashIndex::INVALID_INDEX ) { if ( name == identifiers[ existing ] ) { return false; } existing = index.next( existing ); } const auto li( values.size( ) ); index.add( hash ); identifiers.add( name ); values.add( initial ); std::swap( values[ li ] , values[ li - 1 ] ); overriden.add( false ); return true; } uint32_t T_SyncValues::indexOf( T_String const& name ) const noexcept { const uint32_t hash{ ComputeHash( name ) }; uint32_t existing{ index.first( hash ) }; while ( existing != T_HashIndex::INVALID_INDEX ) { if ( name == identifiers[ existing ] ) { return existing; } existing = index.next( existing ); } return values.size( ) - 1; } /*= A_SyncOverride ===========================================================*/ A_SyncOverride::A_SyncOverride( char const* const type , T_String const& title ) noexcept : type_( T_String::Pooled( type ) ) , title_( title.size( ) + 1 ) { char const* src( title.data( ) ); for ( auto i = 0u ; i < title_.size( ) - 1 ; i ++ ) { title_[ i ] = src[ i ]; } title_[ title_.size( ) - 1 ] = 0; } A_SyncOverride::~A_SyncOverride( ) { } void A_SyncOverride::setup( ) noexcept { const auto ni( inputs_.size( ) ); assert( ni != 0 ); assert( inputPos_.size( ) == 0 ); T_StringBuilder sb; inputPos_.ensureCapacity( ni ); for ( auto i = 0u ; i < ni ; i ++ ) { // FIXME: insufficient; the manager should be made aware of // the presence of an override for that value (and it should // fail for missing values). inputPos_.add( Common::Sync( ).inputPos( inputs_[ i ] ) ); if ( sb.size( ) ) { sb << ';'; } sb << inputs_[ i ]; } id_ = std::move( sb ); } /*= T_SyncOverrideSection ====================================================*/ T_SyncOverrideSection::T_SyncOverrideSection( T_String title ) noexcept : title( std::move( title ) ) , cTitle( this->title.toOSString( ) ) { } void T_SyncOverrideSection::merge( T_SyncOverrideSection& other ) noexcept { for ( auto& os : other.subsections ) { section( os->title ).merge( *os ); } for ( auto& ov : other.overrides ) { overrides.add( ov->clone( ) ); } } T_SyncOverrideSection& T_SyncOverrideSection::section( T_String const& name ) noexcept { for ( auto& ov : subsections ) { if ( ov->title == name ) { return *ov; } } return *subsections[ subsections.add( NewOwned< T_SyncOverrideSection >( name ) ) ]; } T_SyncOverrideSection const* T_SyncOverrideSection::section( T_String const& name ) const noexcept { for ( auto& ov : subsections ) { if ( ov->title == name ) { return ov.get( ); } } return nullptr; } /*= T_SyncOverrideVisitor ====================================================*/ T_SyncOverrideVisitor::T_OpElement T_SyncOverrideVisitor::nodeBrowser( const T_Element element , const uint32_t child ) { if ( element.hasType< A_SyncOverride* >( ) ) { return {}; } auto& section( *element.value< T_SyncOverrideSection* >( ) ); const auto nss( section.subsections.size( ) ); if ( child < nss ) { return T_OpElement{ section.subsections[ child ].get( ) }; } if ( child - nss < section.overrides.size( ) ) { return T_OpElement{ section.overrides[ child - nss ].get( ) }; } return {}; } /*= T_SyncManager ============================================================*/ T_SyncManager::T_SyncManager( ) noexcept : io_{ } , watcher_{ Common::Watcher( ) , [this](){ curvesChanged_( ); } } , soRoot_( "*root*" ) { Common::Project( ).addListener( this ); } T_SyncManager::~T_SyncManager( ) { Common::Project( ).removeListener( this ); } void T_SyncManager::enable( ) noexcept { if ( !enabled_ ) { enabled_ = true; projectPathChanged( ); } } /*----------------------------------------------------------------------------*/ void T_SyncManager::setDuration( const float uDuration , const uint32_t iDuration ) { time_.setDuration( uDuration , iDuration ); updateCurveCaches( ); } void T_SyncManager::setTime( const float time ) { time_.setTime( time ); updateValues( ); playing_ = playing_ && !finished( ); } /*----------------------------------------------------------------------------*/ void T_SyncManager::clearInputs( ) noexcept { clearOverrides( ); values_.clear( ); } /*----------------------------------------------------------------------------*/ void T_SyncManager::clearCurves( ) { curves_.clear( ); updateCurveCaches( ); modified_ = true; } void T_SyncManager::setCurve( T_SyncCurve curve ) { curves_.setCurve( curve ); updateCurveCaches( ); modified_ = true; } void T_SyncManager::removeCurve( T_String const& curve ) noexcept { if ( curves_.removeCurve( curve ) ) { updateCurveCaches( ); modified_ = true; } } T_SyncCurve const* T_SyncManager::getCurve( T_String const& name ) const noexcept { return curves_.curves.get( name ); } void T_SyncManager::curvesChanged_( ) { if ( saving_ ) { saving_ = false; return; } if ( modified_ ) { fileChanged_ = true; } else { loadCurves( ); } } bool T_SyncManager::loadCurves( const bool undoable ) { printf( "Loading curves data\n" ); T_ParserOutput_ p; try { p = io_.load( curvesFile_ ); } catch ( ebcl::X_StreamError const& e ) { printf( "... ERR %s\n" , e.what( ) ); return false; } catch ( ebcl::X_SRDErrors const& e ) { T_StringBuilder sb; auto const ne( e.errors.size( ) ); for ( auto i = 0u ; i < ne ; i ++ ) { auto const& err( e.errors[ i ] ); sb << "... ERR " << err.location( ) << ": " << err.error( ) << '\n'; } sb << '\0'; printf( "%s" , sb.data( ) ); return false; } printf( "... success\n" ); if ( undoable ) { addReloadUndoData_( p ); } curves_ = std::move( p.curves ); if ( p.time ) { time_.iDuration = p.time->iDuration; time_.uDuration = p.time->uDuration; } else { time_.iDuration = 3600; time_.uDuration = 1.f / 60.f; } updateCurveCaches( ); modified_ = fileChanged_ = false; return true; } bool T_SyncManager::saveCurves( ) { saving_ = true; printf( "Saving curves...\n" ); try { io_.save( curvesFile_ , curves_ , time_ ); } catch ( ebcl::X_StreamError const& e ) { printf( "... ERR %s\n" , e.what( ) ); return false; } printf( "... OK\n" ); fileChanged_ = modified_ = false; return true; } void T_SyncManager::addReloadUndoData_( T_SyncCurvesIO::T_Data const& data ) const noexcept { auto& undo{ Common::Undo( ).add< T_UndoDurationChanges >( data.time ? data.time->iDuration : 3600 , time_.iDuration , data.time ? data.time->uDuration : ( 1.f / 60.f ) , time_.uDuration ) }; auto& nCurves{ data.curves.curves }; for ( auto const& curve : curves_.curves.values( ) ) { auto const* const nc{ nCurves.get( curve.name ) }; if ( nc ) { undo.curveReplacement( curve , *nc ); } else { undo.curveDeletion( curve ); } } for ( auto const& nc : nCurves.values( ) ) { if ( !curves_.curves.contains( nc.name ) ) { undo.curveCreation( nc ); } } } /*----------------------------------------------------------------------------*/ void T_SyncManager::updateCurveCaches( ) { curveCaches_.clear( ); const uint32_t nv( values_.identifiers.size( ) ); for ( auto i = 0u ; i < nv ; i ++ ) { auto const& id( values_.identifiers[ i ] ); const auto cp( curves_.indexOf( id ) ); if ( cp < 0 ) { curveCaches_.addNew( ); } else { curveCaches_.add( NewOwned< T_SyncCurveCache >( time_ , curves_ , cp ) ); } } updateValues( ); } void T_SyncManager::updateValues( ) { const auto nv( values_.identifiers.size( ) ); assert( nv == curveCaches_.size( ) ); for ( auto i = 0u ; i < nv ; i ++ ) { auto const& cc( curveCaches_[ i ] ); if ( !cc || values_.overriden[ i ] ) { continue; } values_.values[ i ] = cc->value( time_ , curves_ ); } } /*----------------------------------------------------------------------------*/ void T_SyncManager::clearOverrides( ) noexcept { soRoot_.subsections.clear( ); soRoot_.overrides.clear( ); soTable_.clear( ); } void T_SyncManager::mergeOverrides( T_SyncOverrideSection& overrides ) { assert( overrides.overrides.empty( ) ); soRoot_.merge( overrides ); T_StringBuilder sb; T_AutoArray< uint32_t , 16 > sbParts; soVisitor_.visitor.visit( &soRoot_ , [&]( T_SyncOverrideVisitor::T_Element node , bool exit ) -> bool { if ( node.hasType< T_SyncOverrideSection* >( ) ) { T_SyncOverrideSection const& sos{ *node.value< T_SyncOverrideSection* >( ) }; if ( sos.title == "*root*" ) { return true; } if ( exit ) { sb.truncate( sbParts.last( ) ); sbParts.removeLast( ); } else { sbParts.add( sb.length( ) ); sb << sos.title << " \u00bb "; } return true; } if ( exit ) { return true; } auto& ovr( *node.value< A_SyncOverride* >( ) ); ovr.setup( ); soTable_.add( ovr.id( ) , &ovr ); const uint32_t sblen{ sb.length( ) }; sb << ovr.title( ); ovr.fullTitle( sb ); sb.truncate( sblen ); return false; } ); } bool T_SyncManager::overrideExists( T_String const& id ) const noexcept { return soTable_.contains( id ); } A_SyncOverride* T_SyncManager::getOverride( T_String const& id ) const noexcept { const auto rv{ soTable_.get( id ) }; return rv ? *rv : nullptr; } void T_SyncManager::setOverridesActive( const bool active , const uint32_t n , uint32_t const* const pos ) { for ( auto i = 0u ; i < n ; i ++ ) { assert( values_.overriden[ pos[ i ] ] != active ); values_.overriden[ pos[ i ] ] = active; } if ( !active ) { for ( auto i = 0u ; i < n ; i ++ ) { const auto p{ pos[ i ] }; auto const& cc( curveCaches_[ p ] ); if ( cc ) { values_.values[ p ] = cc->value( time_ , curves_ ); } } } } void T_SyncManager::visitOverrides( T_SyncOverrideVisitor::F_NodeAction visitor ) { soVisitor_.visitor.visit( &soRoot_ , visitor ); } /*----------------------------------------------------------------------------*/ void T_SyncManager::projectPathChanged( ) noexcept { if ( enabled_ ) { curvesFile_ = Common::Project( ).pathOf( "curves.srd" ); watcher_.clear( ); watcher_.watch( curvesFile_ ); loadCurves( false ); } }