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:
parent
5080866cbe
commit
6557d369eb
5 changed files with 273 additions and 57 deletions
|
@ -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 =============================================================*/
|
||||
|
||||
|
|
11
c-sync.hh
11
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;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
|
162
c-syncedit.cc
162
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 ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
141
ui-sequencer.cc
141
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
|
||||
|
|
Loading…
Reference in a new issue