Emmanuel BENOîT
6557d369eb
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.
529 lines
14 KiB
C++
529 lines
14 KiB
C++
#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 <anon>
|
|
|
|
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 ) );
|
|
}
|
|
}
|