diff --git a/c-sync.cc b/c-sync.cc index bbd255e..982c177 100644 --- a/c-sync.cc +++ b/c-sync.cc @@ -288,6 +288,15 @@ E_SyncCurveMatch T_SyncCurve::matches( : 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 =============================================================*/ diff --git a/c-sync.hh b/c-sync.hh index 62487be..52e750b 100644 --- a/c-sync.hh +++ b/c-sync.hh @@ -39,8 +39,8 @@ struct T_SyncSegment }; E_SegmentType type; - T_Array< float > values; - T_Array< uint32_t > durations; // n(values) - 1 items + 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; @@ -83,7 +83,7 @@ enum class E_SyncCurveMatch struct T_SyncCurve { T_String name; - T_Array< T_SyncSegment > segments; + T_Array< T_SyncSegment > segments{ 16 }; T_SyncCurve( ) noexcept { } @@ -101,6 +101,11 @@ struct T_SyncCurve // 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; }; /*----------------------------------------------------------------------------*/ diff --git a/c-syncedit.cc b/c-syncedit.cc index 23ae3d8..b96fe0c 100644 --- a/c-syncedit.cc +++ b/c-syncedit.cc @@ -365,3 +365,165 @@ void SyncEditor::DeletePoint( 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 ) ); + } +} diff --git a/c-syncedit.hh b/c-syncedit.hh index 7595258..c064ee1 100644 --- a/c-syncedit.hh +++ b/c-syncedit.hh @@ -134,4 +134,11 @@ struct SyncEditor final T_String const& id , uint32_t segmentIndex , uint32_t pointIndex ) noexcept; + + //---------------------------------------------------------------------- + + // Make an override consistent by aligning all segment boundaries and + // durations on the first longest curve. + static void MakeOverrideConsistent( + T_String const& id ) noexcept; }; diff --git a/ui-sequencer.cc b/ui-sequencer.cc index 907d4e6..0edec80 100644 --- a/ui-sequencer.cc +++ b/ui-sequencer.cc @@ -259,6 +259,15 @@ struct T_SyncViewImpl_ void displayInputSelector( ) noexcept; void displayOverrideSelector( ) noexcept; + // Helpers for selecting overrides + static bool areOverrideInputsConsistent( + A_SyncOverride const& ov ) noexcept; + bool areOverrideInputsDisplayed( + A_SyncOverride const& ov ) const noexcept; + void overrideTrackToggled( + A_SyncOverride const& ov , + bool selected ) noexcept; + // Selection display/edition windows void displayTrackWindow( ) noexcept; void displaySegmentWindow( ) noexcept; @@ -1326,14 +1335,6 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept * An override can be selected directly if the inputs that are part of * it are consistent. * - * A pair of inputs are consistent if - * - one or both inputs do not have curve information attached; - * - the durations and segment boundaries of the shortest curve - * attached to the inputs match the boundaries and durations from the - * other input's curve OR the last segment is shorter but its end - * matches the location of one of the points in the corresponding - * segment of the other curve. - * * If the inputs are not consistent, it will be necessary to reset * some of their curves; best option would be to try and be clever, but * a temporary measure (resetting all curves to match the structure of @@ -1360,62 +1361,23 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept auto const& ov{ *element.value< A_SyncOverride* >( ) }; auto const& id{ ov.id( ) }; - auto const& in{ ov.inputNames( ) }; const bool present{ sOverrides.contains( id ) }; - const bool hasCurves{ !present && [&](){ - for ( auto i = 0u ; i < in.size( ) ; i ++ ) { - if ( sCurves.contains( in[ i ] ) ) { - return true; - } - } - return false; - }() }; - - // FIXME MOVE THIS \/ // - bool bad{ false }; - for ( auto i = 0u ; i < in.size( ) - 1 && !bad ; i ++ ) { - T_SyncCurve const* const c0{ - Common::Sync( ).getCurve( in[ i ] ) - }; - if ( !c0 ) { - continue; - } - for ( auto j = i + 1 ; j < in.size( ) && !bad ; j ++ ) { - T_SyncCurve const* const c1{ - Common::Sync( ).getCurve( in[ j ] ) - }; - if ( !c1 ) { - continue; - } - const auto res{ c0->matches( *c1 ) }; - bad = ( res == E_SyncCurveMatch::MISMATCH ); - } - } - // FIXME MOVE THIS /\ // + const bool hasCurves{ !present && areOverrideInputsDisplayed( ov ) }; + const bool consistent{ areOverrideInputsConsistent( ov ) }; if ( hasCurves ) { PushDisabled( ); } - if ( bad ) { + if ( !consistent ) { PushStyleColor( ImGuiCol_Text , 0xff0000ff ); } - bool select{ present }; - if ( Checkbox( ov.title( ) , &select ) ) { - if ( select ) { - sOverrides.add( id ); - for ( auto i = 0u ; i < in.size( ) ; i ++ ) { - sCurves.add( in[ i ] , true ); - } - } else { - sOverrides.remove( id ); - for ( auto i = 0u ; i < in.size( ) ; i ++ ) { - sCurves.remove( in[ i ] ); - } - } + bool selected{ present }; + if ( Checkbox( ov.title( ) , &selected ) ) { + overrideTrackToggled( ov , selected ); } - if ( bad ) { + if ( !consistent ) { PopStyleColor( ); } if ( hasCurves ) { @@ -1427,6 +1389,77 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept EndChild( ); } +bool T_SyncViewImpl_::areOverrideInputsConsistent( + A_SyncOverride const& ov ) noexcept +{ + /* A pair of inputs are consistent if + * - one or both inputs do not have curve information attached; + * - the durations and segment boundaries of the shortest curve + * attached to the inputs match the boundaries and durations from the + * other input's curve OR the last segment is shorter but its end + * matches the location of one of the points in the corresponding + * segment of the other curve. + */ + auto const& in{ ov.inputNames( ) }; + for ( auto i = 0u ; i < in.size( ) - 1 ; i ++ ) { + T_SyncCurve const* const c0{ + Common::Sync( ).getCurve( in[ i ] ) + }; + if ( !c0 ) { + continue; + } + for ( auto j = i + 1 ; j < in.size( ) ; j ++ ) { + T_SyncCurve const* const c1{ + Common::Sync( ).getCurve( in[ j ] ) + }; + if ( !c1 ) { + continue; + } + const auto res{ c0->matches( *c1 ) }; + if ( res == E_SyncCurveMatch::MISMATCH ) { + return false; + } + } + } + return true; +} + +bool T_SyncViewImpl_::areOverrideInputsDisplayed( + A_SyncOverride const& ov ) const noexcept +{ + auto const& in{ ov.inputNames( ) }; + for ( auto i = 0u ; i < in.size( ) ; i ++ ) { + if ( sCurves.contains( in[ i ] ) ) { + return true; + } + } + return false; +} + +void T_SyncViewImpl_::overrideTrackToggled( + A_SyncOverride const& ov , + const bool selected ) noexcept +{ + auto const& in{ ov.inputNames( ) }; + + // Handle de-selection + if ( !selected ) { + sOverrides.remove( ov.id( ) ); + for ( auto i = 0u ; i < in.size( ) ; i ++ ) { + sCurves.remove( in[ i ] ); + } + return; + } + + // If the override is not consistent, we need to make it so + SyncEditor::MakeOverrideConsistent( ov.id( ) ); + + sOverrides.add( ov.id( ) ); + for ( auto i = 0u ; i < in.size( ) ; i ++ ) { + sCurves.add( in[ i ] , true ); + } +} + /*----------------------------------------------------------------------------*/ void T_SyncViewImpl_::displayTrackWindow( ) noexcept