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.
This commit is contained in:
Emmanuel BENOîT 2017-11-28 15:04:28 +01:00
parent 5080866cbe
commit 6557d369eb
5 changed files with 273 additions and 57 deletions

View file

@ -288,6 +288,15 @@ E_SyncCurveMatch T_SyncCurve::matches(
: E_SyncCurveMatch::MATCHING_LONG; : 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 =============================================================*/ /*= T_SyncCurves =============================================================*/

View file

@ -39,8 +39,8 @@ struct T_SyncSegment
}; };
E_SegmentType type; E_SegmentType type;
T_Array< float > values; T_Array< float > values{ T_Array< float >( 16 ) };
T_Array< uint32_t > durations; // n(values) - 1 items T_Array< uint32_t > durations{ T_Array< uint32_t >( 16 ) };
// Find the amount of time units before point // Find the amount of time units before point
uint32_t findTimeOfPoint( uint32_t index ) const noexcept; uint32_t findTimeOfPoint( uint32_t index ) const noexcept;
@ -83,7 +83,7 @@ enum class E_SyncCurveMatch
struct T_SyncCurve struct T_SyncCurve
{ {
T_String name; T_String name;
T_Array< T_SyncSegment > segments; T_Array< T_SyncSegment > segments{ 16 };
T_SyncCurve( ) noexcept { } T_SyncCurve( ) noexcept { }
@ -101,6 +101,11 @@ struct T_SyncCurve
// Compare this curve to another. // Compare this curve to another.
E_SyncCurveMatch matches( E_SyncCurveMatch matches(
T_SyncCurve const& other ) const noexcept; 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;
}; };
/*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/

View file

@ -365,3 +365,165 @@ void SyncEditor::DeletePoint(
undo.curveReplacement( *curve , c ); undo.curveReplacement( *curve , c );
sync.setCurve( std::move( 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 ) );
}
}

View file

@ -134,4 +134,11 @@ struct SyncEditor final
T_String const& id , T_String const& id ,
uint32_t segmentIndex , uint32_t segmentIndex ,
uint32_t pointIndex ) noexcept; 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;
}; };

View file

@ -259,6 +259,15 @@ struct T_SyncViewImpl_
void displayInputSelector( ) noexcept; void displayInputSelector( ) noexcept;
void displayOverrideSelector( ) 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 // Selection display/edition windows
void displayTrackWindow( ) noexcept; void displayTrackWindow( ) noexcept;
void displaySegmentWindow( ) 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 * An override can be selected directly if the inputs that are part of
* it are consistent. * 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 * 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 * 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 * 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& ov{ *element.value< A_SyncOverride* >( ) };
auto const& id{ ov.id( ) }; auto const& id{ ov.id( ) };
auto const& in{ ov.inputNames( ) };
const bool present{ sOverrides.contains( id ) }; const bool present{ sOverrides.contains( id ) };
const bool hasCurves{ !present && [&](){ const bool hasCurves{ !present && areOverrideInputsDisplayed( ov ) };
for ( auto i = 0u ; i < in.size( ) ; i ++ ) { const bool consistent{ areOverrideInputsConsistent( ov ) };
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 /\ //
if ( hasCurves ) { if ( hasCurves ) {
PushDisabled( ); PushDisabled( );
} }
if ( bad ) { if ( !consistent ) {
PushStyleColor( ImGuiCol_Text , 0xff0000ff ); PushStyleColor( ImGuiCol_Text , 0xff0000ff );
} }
bool select{ present }; bool selected{ present };
if ( Checkbox( ov.title( ) , &select ) ) { if ( Checkbox( ov.title( ) , &selected ) ) {
if ( select ) { overrideTrackToggled( ov , selected );
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 ] );
}
}
} }
if ( bad ) { if ( !consistent ) {
PopStyleColor( ); PopStyleColor( );
} }
if ( hasCurves ) { if ( hasCurves ) {
@ -1427,6 +1389,77 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept
EndChild( ); 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 void T_SyncViewImpl_::displayTrackWindow( ) noexcept