#include "externals.hh" #include "common.hh" #include "c-syncedit.hh" /*= T_UndoSyncChanges ========================================================*/ void T_UndoSyncChanges::undo( ) const noexcept { auto& sync{ Common::Sync( ) }; const auto n{ changes_.size( ) }; for ( auto i = 0u ; i < n ; i ++ ) { auto const& c( changes_[ i ] ); if ( c.before ) { sync.setCurve( *c.before ); } else { sync.removeCurve( c.inputId ); } } } void T_UndoSyncChanges::redo( ) const noexcept { auto& sync{ Common::Sync( ) }; const auto n{ changes_.size( ) }; for ( auto i = 0u ; i < n ; i ++ ) { auto const& c( changes_[ i ] ); if ( c.after ) { sync.setCurve( *c.after ); } else { sync.removeCurve( c.inputId ); } } } /*----------------------------------------------------------------------------*/ T_UndoSyncChanges& T_UndoSyncChanges::curveCreation( T_SyncCurve curve ) noexcept { #ifndef NDEBUG { const auto n{ changes_.size( ) }; for ( auto i = 0u ; i < n ; i ++ ) { assert( changes_[ i ].inputId != curve.name ); } } #endif changes_.addNew( true , std::move( curve ) ); return *this; } T_UndoSyncChanges& T_UndoSyncChanges::curveDeletion( T_SyncCurve curve ) noexcept { #ifndef NDEBUG { const auto n{ changes_.size( ) }; for ( auto i = 0u ; i < n ; i ++ ) { assert( changes_[ i ].inputId != curve.name ); } } #endif changes_.addNew( std::move( curve ) ); return *this; } T_UndoSyncChanges& T_UndoSyncChanges::curveReplacement( T_SyncCurve before , T_SyncCurve after ) noexcept { assert( before.name == after.name ); #ifndef NDEBUG { const auto n{ changes_.size( ) }; for ( auto i = 0u ; i < n ; i ++ ) { assert( changes_[ i ].inputId != before.name ); } } #endif changes_.addNew( std::move( before ) , std::move( after ) ); return *this; } /*= T_UndoDurationChanges ====================================================*/ T_UndoDurationChanges::T_UndoDurationChanges( const uint32_t units , const uint32_t oldUnits , const float unitSize , const float oldUnitSize ) noexcept : T_UndoSyncChanges( ) , unitsBefore_{ oldUnits } , unitsAfter_{ units } , uSizeBefore_{ oldUnitSize } , uSizeAfter_{ unitSize } { } void T_UndoDurationChanges::undo( ) const noexcept { Common::Sync( ).setDuration( uSizeBefore_ , unitsBefore_ ); T_UndoSyncChanges::undo( ); } void T_UndoDurationChanges::redo( ) const noexcept { Common::Sync( ).setDuration( uSizeAfter_ , unitsAfter_ ); T_UndoSyncChanges::redo( ); } /*= SyncEditor ===============================================================*/ namespace { T_SyncCurve SESDScaleCurve_( const float uSizeBefore , const float uSizeAfter , T_SyncCurve const& curve ) noexcept { T_SyncCurve nCurve; nCurve.name = curve.name; for ( auto const& segment : curve.segments ) { const auto nsid{ nCurve.segments.add( segment ) }; T_SyncSegment& nSeg{ nCurve.segments[ nsid ] }; const auto nd{ nSeg.durations.size( ) }; for ( auto i = 0u ; i < nd ; i ++ ) { const float duration{ nSeg.durations[ i ] * uSizeBefore }; nSeg.durations[ i ] = std::max( 1u , uint32_t( std::roundf( duration / uSizeAfter ) ) ); #if 0 printf( "initial duration %f (%d units) ; new duration %f (%d units)\n" , duration , segment.durations[ i ] , uSizeAfter * nSeg.durations[ i ] , nSeg.durations[ i ] ); #endif } } return nCurve; } } // namespace void SyncEditor::SetDuration( const uint32_t units , const float uSize , const bool scaleCurves ) noexcept { auto& sync{ Common::Sync( ) }; const float oldUnitSize{ sync.durationUnitSize( ) }; const uint32_t oldUnits{ sync.durationUnits( ) }; if ( oldUnits == units && oldUnitSize == uSize ) { return; } auto& undo{ dynamic_cast< T_UndoDurationChanges& >( Common::Undo( ).add< T_UndoDurationChanges >( units , oldUnits , uSize , oldUnitSize ) ) }; sync.setDuration( uSize , units ); if ( !scaleCurves || uSize == oldUnitSize ) { return; } auto const& curves( sync.curves( ) ); for ( auto const& curve : curves ) { auto nc{ SESDScaleCurve_( oldUnitSize , uSize , curve ) }; undo.curveReplacement( curve , nc ); sync.setCurve( std::move( nc ) ); } } /*----------------------------------------------------------------------------*/ void SyncEditor::ReplaceCurve( T_SyncCurve replacement ) noexcept { auto* const curve{ Common::Sync( ).getCurve( replacement.name ) }; if ( !curve ) { return; } // Create undo entry auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; undo.curveReplacement( *curve , replacement ); Common::Sync( ).setCurve( std::move( replacement ) ); } /*----------------------------------------------------------------------------*/ void SyncEditor::DeleteCurve( T_String const& id ) noexcept { auto const* const curve{ Common::Sync( ).getCurve( id ) }; if ( !curve ) { return; } // Create undo entry auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; undo.curveDeletion( *curve ); // Delete curve Common::Sync( ).removeCurve( id ); } /*----------------------------------------------------------------------------*/ void SyncEditor::AppendSegment( T_String const& id , uint32_t nsDuration ) noexcept { assert( nsDuration > 0 ); auto& sync{ Common::Sync( ) }; auto const* const curve{ sync.getCurve( id ) }; auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; if ( curve && !curve->segments.empty( ) ) { const float lastValue{ [&](){ auto const& lSeg{ curve->segments.last( ) }; return lSeg.values.last( ); }( ) }; auto nCurve{ *curve }; auto& nSeg{ nCurve.segments.addNew( ) }; nSeg.type = T_SyncSegment::LINEAR; nSeg.values.add( lastValue ); nSeg.values.add( lastValue ); nSeg.durations.add( nsDuration ); undo.curveReplacement( *curve , nCurve ); sync.setCurve( std::move( nCurve ) ); } else { T_SyncCurve nCurve; nCurve.name = id; const auto value{ sync.inputs( )[ sync.inputPos( id ) ] }; auto& nSeg{ nCurve.segments.addNew( ) }; nSeg.type = T_SyncSegment::LINEAR; nSeg.values.add( value ); nSeg.values.add( value ); nSeg.durations.add( nsDuration ); undo.curveCreation( nCurve ); sync.setCurve( std::move( nCurve ) ); } } /*----------------------------------------------------------------------------*/ void SyncEditor::DeleteSegment( T_String const& id , const uint32_t segmentIndex ) noexcept { auto& sync{ Common::Sync( ) }; auto const* const curve{ sync.getCurve( id ) }; if ( !curve || segmentIndex >= curve->segments.size( ) ) { return; } auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; if ( curve->segments.size( ) == 1 ) { undo.curveDeletion( *curve ); sync.removeCurve( id ); } else { auto nCurve{ *curve }; nCurve.segments.remove( segmentIndex ); undo.curveReplacement( *curve , nCurve ); sync.setCurve( std::move( nCurve ) ); } } /*----------------------------------------------------------------------------*/ void SyncEditor::SetSegmentType( T_SyncCurve const& initial , const uint32_t segmentIndex , const T_SyncSegment::E_SegmentType newType ) noexcept { assert( segmentIndex < initial.segments.size( ) ); T_SyncSegment const& iSegment{ initial.segments[ segmentIndex ] }; if ( iSegment.type == newType ) { return; } // Create new curve T_SyncCurve copy{ initial }; copy.segments[ segmentIndex ].type = newType; // Create undo entry auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; undo.curveReplacement( initial , copy ); // Replace curve Common::Sync( ).setCurve( std::move( copy ) ); } /*----------------------------------------------------------------------------*/ void SyncEditor::InsertPoint( T_String const& id , const uint32_t segmentIndex , const uint32_t pointIndex ) noexcept { auto& sync{ Common::Sync( ) }; auto const* const curve{ sync.getCurve( id ) }; if ( !curve || segmentIndex >= curve->segments.size( ) ) { return; } auto const& segment{ curve->segments[ segmentIndex ] }; if ( pointIndex == 0 || pointIndex > segment.values.size( ) || segment.durations[ pointIndex - 1 ] == 1 ) { return; } auto c{ *curve }; auto& ns{ c.segments[ segmentIndex ] }; const auto hd{ ns.durations[ pointIndex - 1 ] / 2 }; const float hv{ ns.computeValue( ns.findTimeOfPoint( pointIndex - 1 ) + hd ) }; ns.durations[ pointIndex - 1 ] -= hd; ns.durations.insert( pointIndex - 1 , hd ); ns.values.insert( pointIndex , hv ); auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; undo.curveReplacement( *curve , c ); sync.setCurve( std::move( c ) ); } /*----------------------------------------------------------------------------*/ void SyncEditor::DeletePoint( T_String const& id , const uint32_t segmentIndex , const uint32_t pointIndex ) noexcept { auto& sync{ Common::Sync( ) }; auto const* const curve{ sync.getCurve( id ) }; if ( !curve || segmentIndex >= curve->segments.size( ) ) { return; } auto const& segment{ curve->segments[ segmentIndex ] }; if ( pointIndex == 0 || pointIndex >= segment.values.size( ) ) { return; } auto c{ *curve }; auto& ns{ c.segments[ segmentIndex ] }; ns.durations[ pointIndex ] += ns.durations[ pointIndex - 1 ]; ns.durations.remove( pointIndex - 1 ); ns.values.remove( pointIndex ); auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; undo.curveReplacement( *curve , c ); sync.setCurve( std::move( c ) ); } /*----------------------------------------------------------------------------*/ namespace { bool MOCRequiresChanges_( ebcl::T_Set< T_String > const& iNames ) noexcept { auto& sync{ Common::Sync( ) }; const auto nInputs{ iNames.size( ) }; for ( auto i = 0u ; i < nInputs ; i ++ ) { auto const* const c0{ sync.getCurve( iNames[ i ] ) }; if ( !c0 ) { continue; } for ( auto j = i + 1 ; j < nInputs ; j ++ ) { auto const* const c1{ sync.getCurve( iNames[ j ] ) }; if ( !c1 || c1->matches( *c0 ) != E_SyncCurveMatch::IDENTICAL ) { return true; } } } return false; } T_SyncCurve const& MOCFindLongest_( ebcl::T_Set< T_String > const& iNames ) noexcept { auto& sync{ Common::Sync( ) }; const auto nInputs{ iNames.size( ) }; uint32_t longestLen{ 0 }; T_SyncCurve const* longest{ nullptr }; for ( auto i = 0u ; i < nInputs ; i ++ ) { auto const* const c{ sync.getCurve( iNames[ i ] ) }; const auto l{ c ? c->points( ) : 0 }; if ( l > longestLen ) { longestLen = l; longest = c; } } assert( longest ); return *longest; } } void SyncEditor::MakeOverrideConsistent( T_String const& id ) noexcept { auto& sync{ Common::Sync( ) }; auto const* const src{ sync.getOverride( id ) }; if ( !src ) { return; } // Check whether it's actually necessary to enact changes if ( !MOCRequiresChanges_( src->inputNames( ) ) ) { return; } // Prepate undo structure auto& undo{ dynamic_cast< T_UndoSyncChanges& >( Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; // Find longest curve auto const& iNames{ src->inputNames( ) }; const auto nInputs{ iNames.size( ) }; T_SyncCurve const& longest{ MOCFindLongest_( iNames ) }; // For other curves: for ( auto i = 0u ; i < nInputs ; i ++ ) { auto const& name{ iNames[ i ] }; if ( name == longest.name ) { continue; } T_SyncCurve const* const ori{ sync.getCurve( iNames[ i ] ) }; if ( !ori ) { // If missing, create it with the same structure const float v{ sync.inputs( )[ sync.inputPos( iNames[ i ] ) ] }; T_SyncCurve nc; nc.name = name; for ( auto const& ss : longest.segments ) { auto& ns{ nc.segments.addNew( ) }; ns.type = ss.type; ns.durations = ss.durations; ns.values.resize( ss.values.size( ) , v ); } sync.setCurve( nc ); undo.curveCreation( std::move( nc ) ); continue; } // If identical, don't change anything const auto diff{ ori->matches( longest ) }; assert( diff != E_SyncCurveMatch::LASTSEG_LONG && diff != E_SyncCurveMatch::MATCHING_LONG ); if ( diff == E_SyncCurveMatch::IDENTICAL ) { continue; } T_SyncCurve nc; nc.name = name; if ( diff == E_SyncCurveMatch::MISMATCH ) { // Recreate it completely, keeping the values but using // the timings from the longest curve. auto oss{ 0u } , osp{ 0u }; auto lv{ 0.f }; for ( auto const& ss : longest.segments ) { auto& ns{ nc.segments.addNew( ) }; ns.type = ss.type; ns.durations = ss.durations; for ( auto j = 0u ; j < ss.values.size( ) ; j ++ ) { if ( oss < ori->segments.size( ) ) { lv = ori->segments[ oss ].values[ osp++ ]; if ( osp == ori->segments[ oss ].values.size( ) ) { osp = 0; oss ++; } } ns.values.add( lv ); } } undo.curveReplacement( *ori , nc ); sync.setCurve( std::move( nc ) ); continue; } assert( diff == E_SyncCurveMatch::LASTSEG_SHORT || diff == E_SyncCurveMatch::MATCHING_SHORT ); // Copy the segments from the original, as they match. for ( auto const& os : ori->segments ) { nc.segments.add( os ); } if ( diff == E_SyncCurveMatch::LASTSEG_SHORT ) { // The last segment is too short, repeat last value // with the correct durations auto const& ols{ longest.segments[ nc.segments.size( ) - 1 ] }; auto& ls{ nc.segments.last( ) }; while ( ls.durations.size( ) < ols.durations.size( ) ) { ls.durations.add( ols.durations[ ls.durations.size( ) ] ); ls.values.add( ls.values.last( ) ); } } // Add missing segments const float lv{ nc.segments.last( ).values.last( ) }; while ( nc.segments.size( ) < longest.segments.size( ) ) { auto const& os{ longest.segments[ nc.segments.size( ) - 1 ] }; auto& ns{ nc.segments.addNew( ) }; ns.type = os.type; ns.durations = os.durations; ns.values.resize( os.values.size( ) , lv ); } undo.curveReplacement( *ori , nc ); sync.setCurve( std::move( nc ) ); } }