introduce Region::ensure_length_sanity()

This forces the length of AudioRegions to be an integer number of samples. It is intended
to fix a set of bugs that occur when using music time as the session time domain
and carrying out editing operations that would otherwise lead to audio regions
whose length involves fractional samples.

It is perfectly legal to specific audio distances that include fractional samples,
but there is no reason for any audio region to ever have such a length (we think).
This commit is contained in:
Paul Davis 2025-02-28 13:39:16 -07:00
parent 03458c6655
commit 434f460775
4 changed files with 48 additions and 14 deletions

View file

@ -289,6 +289,7 @@ class LIBARDOUR_API AudioRegion : public Region, public AudioReadable
int _set_state (const XMLNode&, int version, PBD::PropertyChange& what_changed, bool send_signal);
void send_change (const PBD::PropertyChange&);
void ensure_length_sanity ();
};
} /* namespace ARDOUR */

View file

@ -573,6 +573,7 @@ protected:
virtual void set_start_internal (timepos_t const &);
bool verify_start_and_length (timepos_t const &, timecnt_t&);
void first_edit ();
virtual void ensure_length_sanity () {}
void override_opaqueness (bool yn) {
_opaque = yn;

View file

@ -438,6 +438,8 @@ AudioRegion::~AudioRegion ()
void
AudioRegion::post_set (const PropertyChange& /*ignored*/)
{
ensure_length_sanity ();
if (!_sync_marked) {
_sync_position = _start;
}
@ -2701,3 +2703,19 @@ AudioRegion::apply_region_fx (BufferSet& bufs, samplepos_t start_sample, samplep
_fx_pos = end_sample;
_fx_latent_read = false;
}
void
AudioRegion::ensure_length_sanity ()
{
if (_type == DataType::AUDIO) {
/* Force audio regions to have a length that is the
rounded-down integer number of samples. No other value makes
any sort of logical sense. We tried to fix this at a lower
level, by rounding the return value of
TempoMap::superclock_at(), but the breaks the fundamental
point of a high resolution audio time unit.
*/
_length = timecnt_t (timepos_t (_length.val().samples()), _length.val().position());
}
}

View file

@ -304,6 +304,7 @@ Region::Region (Session& s, timepos_t const & start, timecnt_t const & length, c
, _changemap (0)
{
register_properties ();
ensure_length_sanity ();
/* no sources at this point */
}
@ -324,6 +325,7 @@ Region::Region (const SourceList& srcs)
register_properties ();
use_sources (srcs);
ensure_length_sanity ();
assert(_sources.size() > 0);
assert (_type == srcs.front()->type());
@ -382,6 +384,7 @@ Region::Region (std::shared_ptr<const Region> other)
_sync_position = _start;
}
ensure_length_sanity ();
assert (_type == other->data_type());
}
@ -433,6 +436,7 @@ Region::Region (std::shared_ptr<const Region> other, timecnt_t const & offset)
_sync_position = _start;
}
ensure_length_sanity ();
assert (_type == other->data_type());
}
@ -462,6 +466,7 @@ Region::Region (std::shared_ptr<const Region> other, const SourceList& srcs)
}
use_sources (srcs);
ensure_length_sanity ();
assert(_sources.size() > 0);
}
@ -567,6 +572,13 @@ Region::set_length_internal (timecnt_t const & len)
_last_length = timecnt_t (_length.val().distance(), _last_length.position());
if (_type == DataType::AUDIO) {
/* Equivalent to what AudioRegion::ensure_length_sanity() does */
_length = timecnt_t (timepos_t (len.samples()), _length.val().position());
} else {
std::shared_ptr<Playlist> pl (playlist());
if (pl) {
@ -583,10 +595,12 @@ Region::set_length_internal (timecnt_t const & len)
return;
}
}
/* either no playlist or time domain for distance is not changing */
_length = timecnt_t (len.distance(), _length.val().position());
}
}
void
Region::maybe_uncopy ()