demotool/c-syncedit.cc
Emmanuel BENOîT 6557d369eb 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.
2017-11-28 15:17:34 +01:00

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 ) );
}
}