From fa376b709d46cbd9d1002ef46f96adece36cecaf Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Wed, 29 Jan 2025 17:24:12 -0700 Subject: [PATCH] add new sample_aligned_superclock() method and use it when converting Beats to superclock There can never be fractional audio time, and since superclock_t is used to represent audio time, when we convert from Beat time, we should never, ever return a superclock value that does not correspond to an integer number of samples. This fixes a number of bugs, including any use of ARDOUR::Filter which writes a new (audio) file to disk that must be an integer number of samples long, but may be derived from and later used by a region that had an audio-time duration that is slightly longer (less than one sample) than the audio file --- libs/temporal/tempo.cc | 70 +++++++++++++++-------------- libs/temporal/temporal/superclock.h | 1 + 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/libs/temporal/tempo.cc b/libs/temporal/tempo.cc index ba004a3995..dd1700551f 100644 --- a/libs/temporal/tempo.cc +++ b/libs/temporal/tempo.cc @@ -524,49 +524,53 @@ TempoPoint::superclock_at (Temporal::Beats const & qn) const TEMPO_MAP_ASSERT (qn >= _quarters); } + superclock_t r; + if (!actually_ramped()) { /* not ramped, use linear */ const Beats delta = qn - _quarters; const superclock_t spqn = superclocks_per_quarter_note (); - return _sclock + (spqn * delta.get_beats()) + muldiv_round (spqn, delta.get_ticks(), superclock_t (Temporal::ticks_per_beat)); - } - - superclock_t r; - const double log_expr = superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double(); - - // std::cerr << "logexpr " << log_expr << " from " << superclocks_per_quarter_note() << " * " << _omega << " * " << (qn - _quarters) << std::endl; - - if (log_expr < -1) { - - r = _sclock + llrint (log (-log_expr - 1.0) / -_omega); - - if (r < 0) { - std::cerr << "CASE 1: " << *this << endl << " scpqn = " << superclocks_per_quarter_note() << std::endl; - std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log (" << log_expr << ") " - << log (-log_expr - 1.0) - << " - omega = " << -_omega - << " => " - << r << std::endl; - abort (); - } - + r = _sclock + (spqn * delta.get_beats()) + muldiv_round (spqn, delta.get_ticks(), superclock_t (Temporal::ticks_per_beat)); } else { - r = _sclock + llrint (log1p (log_expr) / _omega); - // std::cerr << "r = " << _sclock << " + " << log1p (log_expr) / _omega << " => " << r << std::endl; + const double log_expr = superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double(); - if (r < 0) { - std::cerr << "CASE 2: scpqn = " << superclocks_per_quarter_note() << std::endl; - std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log1p (" << superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double() << " = " - << log1p (superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double()) - << " => " - << r << std::endl; - _map->dump (std::cerr); - abort (); + // std::cerr << "logexpr " << log_expr << " from " << superclocks_per_quarter_note() << " * " << _omega << " * " << (qn - _quarters) << std::endl; + + if (log_expr < -1) { + + r = _sclock + llrint (log (-log_expr - 1.0) / -_omega); + + if (r < 0) { + std::cerr << "CASE 1: " << *this << endl << " scpqn = " << superclocks_per_quarter_note() << std::endl; + std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log (" << log_expr << ") " + << log (-log_expr - 1.0) + << " - omega = " << -_omega + << " => " + << r << std::endl; + abort (); + } + + } else { + r = _sclock + llrint (log1p (log_expr) / _omega); + + // std::cerr << "r = " << _sclock << " + " << log1p (log_expr) / _omega << " => " << r << std::endl; + + if (r < 0) { + std::cerr << "CASE 2: scpqn = " << superclocks_per_quarter_note() << std::endl; + std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log1p (" << superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double() << " = " + << log1p (superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double()) + << " => " + << r << std::endl; + _map->dump (std::cerr); + abort (); + } } } - return r; + /* Now round up to the nearest sample-equivalent superclock value */ + + return sample_aligned_superclock (r, TEMPORAL_SAMPLE_RATE); } superclock_t diff --git a/libs/temporal/temporal/superclock.h b/libs/temporal/temporal/superclock.h index a3f4783f30..8aa4bb2a99 100644 --- a/libs/temporal/temporal/superclock.h +++ b/libs/temporal/temporal/superclock.h @@ -47,6 +47,7 @@ static inline superclock_t superclock_ticks_per_second() { return _superclock_ti static inline superclock_t superclock_to_samples (superclock_t s, int sr) { return PBD::muldiv_floor (s, sr, superclock_ticks_per_second()); } static inline superclock_t samples_to_superclock (int64_t samples, int sr) { return PBD::muldiv_round (samples, superclock_ticks_per_second(), superclock_t (sr)); } +static inline superclock_t sample_aligned_superclock (superclock_t s, int sr) { return PBD::muldiv_floor (s, sr, sr); } LIBTEMPORAL_API extern int most_recent_engine_sample_rate;