demotool/c-sync.cc
Emmanuel BENOîT 6557d369eb Sequencer - Selecting inconsistent overrides
If an inconsistent override (i.e. an override that has segments of
different lengths or durations) is selected, it will be modified in
order to make it consistent. Also, if some (but not all) of the inputs
are missing segments or points, they will be added.
2017-11-28 15:17:34 +01:00

901 lines
22 KiB
C++

#include "externals.hh"
#include "common.hh"
#include "c-sync.hh"
#include "c-syncedit.hh"
#include "c-undo.hh"
#include <ebcl/Files.hh>
#include <ebcl/SRDText.hh>
#include <ebcl/SRDParser.hh>
using ebcl::T_SRDParserConfig;
/*= SRD parser for the curves ================================================*/
namespace {
struct T_ParserOutput_
{
T_SyncCurves curves;
T_Optional< T_SyncTime > time;
ebcl::T_SRDLocation tLocation;
};
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;
}
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_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_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
: pConfig_( MakeCurvesParser_( ) ) ,
watcher_{ Common::Watcher( ) , [this](){ curvesChanged_( ); } } ,
soRoot_( "*root*" )
{
auto& p{ Common::Project( ) };
p.addListener( this );
curvesFile_ = p.pathOf( "curves.srd" );
watcher_.watch( curvesFile_ );
loadCurves( false );
}
T_SyncManager::~T_SyncManager( )
{
Common::Project( ).removeListener( this );
}
/*----------------------------------------------------------------------------*/
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_SharedPtr< T_ParserOutput_ > p;
try {
using namespace ebcl;
const T_SRDParserConfig cfg( MakeCurvesParser_( ) );
T_File file( curvesFile_ , E_FileMode::READ_ONLY );
file.open( );
T_FileInputStream fis( file );
T_SRDParser parser( cfg );
T_SRDTextReader reader( parser );
reader.read( curvesFile_ , fis );
p = parser.getData< T_SharedPtr< T_ParserOutput_ > >( );
} 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" );
assert( p );
if ( undoable ) {
addReloadUndoData_( (void*)(T_ParserOutput_*) 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( )
{
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";
}
saving_ = true;
printf( "Saving curves...\n" );
try {
T_File out{ "curves.srd" , E_FileMode::OVERWRITE };
out.write( sb.data( ) , sb.size( ) );
} 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_(
void* const data ) const noexcept
{
T_ParserOutput_& p{ *(T_ParserOutput_*)data };
auto& undo{ Common::Undo( ).add< T_UndoDurationChanges >(
p.time ? p.time->iDuration : 3600 ,
time_.iDuration ,
p.time ? p.time->uDuration : ( 1.f / 60.f ) ,
time_.uDuration ) };
auto& nCurves{ p.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 );
soVisitor_.visitor.visit( &soRoot_ ,
[this]( T_SyncOverrideVisitor::T_Element node , bool exit ) -> bool {
if ( exit || node.hasType< T_SyncOverrideSection* >( ) ) {
return true;
}
auto& ovr( *node.value< A_SyncOverride* >( ) );
ovr.setup( );
soTable_.add( ovr.id( ) , &ovr );
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
{
curvesFile_ = Common::Project( ).pathOf( "curves.srd" );
watcher_.clear( );
watcher_.watch( curvesFile_ );
loadCurves( false );
}