do tempo estimation within AudioSources, after capture and upon import

we read 10 seconds from the middle of the data; not infallible but a reasonable
first pass at a heuristic.
This commit is contained in:
Paul Davis 2025-10-14 14:31:45 -06:00
parent 40f6859905
commit 6d92be80c1
6 changed files with 69 additions and 9 deletions

View file

@ -95,6 +95,8 @@ class LIBARDOUR_API AudioSource : virtual public Source, public ARDOUR::AudioRea
/** @return true if the each source sample s must be clamped to -1 < s < 1 */
virtual bool clamped_at_unity () const = 0;
void estimate_tempo ();
protected:
static bool _build_missing_peakfiles;
static bool _build_peakfiles;

View file

@ -156,6 +156,8 @@ public:
bool get_segment_descriptor (TimelineRange const &, SegmentDescriptor&);
int set_segment_descriptor (SegmentDescriptor const &, bool replace = false);
void copy_segment_descriptors (Source const & other);
protected:
DataType _type;
Flag _flags;

View file

@ -61,6 +61,7 @@
#include "ardour/rc_configuration.h"
#include "ardour/runtime_functions.h"
#include "ardour/session.h"
#include "ardour/utils.h"
#include "pbd/i18n.h"
@ -131,6 +132,46 @@ AudioSource::~AudioSource ()
delete [] peak_leftovers;
}
void
AudioSource::estimate_tempo ()
{
/* CALLER MUST HOLD WRITER LOCK */
const samplecnt_t ten_seconds = _session.sample_rate() * 10;
if (length().samples() < ten_seconds) {
return;
}
samplecnt_t data_size = ten_seconds;
std::unique_ptr<Sample[]> data (new Sample[data_size]);
/* Read ten seconds from the middle of the data */
if (read_unlocked (data.get(), std::max ((samplecnt_t) 0, (length().samples() - data_size) / 2), data_size) == data_size) {
double tempo;
double beatcount;
Temporal::Tempo t (120, 4);
Temporal::Meter m (4, 4);
TimelineRange r (timepos_t (0), timepos_t (0) + length(), 0);
estimate_audio_tempo_source (r, shared_from_this(), data.get(), data_size, _session.sample_rate(), tempo, m, beatcount);
t = Temporal::Tempo (tempo, 4);
SegmentDescriptor sd;
sd.set_tempo (t);
sd.set_meter (m);
sd.set_position (0);
sd.set_duration (length().samples());
/* One segment descriptor to rule them all */
set_segment_descriptor (sd, true);
}
}
XMLNode&
AudioSource::get_state () const
{
@ -1166,9 +1207,14 @@ AudioSource::available_peaks (double zoom_factor) const
void
AudioSource::mark_streaming_write_completed (const WriterLock& lock, Temporal::timecnt_t const &)
{
Glib::Threads::Mutex::Lock lm (_peaks_ready_lock);
estimate_tempo ();
if (_peaks_built) {
PeaksReady (); /* EMIT SIGNAL */
{
Glib::Threads::Mutex::Lock lm (_peaks_ready_lock);
if (_peaks_built) {
PeaksReady (); /* EMIT SIGNAL */
}
}
}

View file

@ -329,6 +329,8 @@ SndFileSource::SndFileSource (Session& s, const AudioFileSource& other, const st
tbuf.modtime = statbuf.st_mtime; // = time ((time_t*) 0);
g_utime (path.c_str(), &tbuf);
}
copy_segment_descriptors (other);
}
void

View file

@ -582,3 +582,9 @@ Source::set_segment_descriptor (SegmentDescriptor const & sr, bool replace)
return 0;
}
void
Source::copy_segment_descriptors (Source const & other)
{
segment_descriptors = other.segment_descriptors;
}

View file

@ -200,7 +200,7 @@ SourceFactory::create (Session& s, const XMLNode& node, bool defer_peaks)
} else {
try {
Source* src = new SndFileSource (s, node);
Source* src = new SndFileSource (s, node);
std::shared_ptr<Source> ret (src);
BOOST_MARK_SOURCE (ret);
if (setup_peakfile (ret, defer_peaks)) {
@ -214,7 +214,7 @@ SourceFactory::create (Session& s, const XMLNode& node, bool defer_peaks)
#ifdef HAVE_COREAUDIO
try {
Source* src = new CoreAudioSource (s, node);
Source* src = new CoreAudioSource (s, node);
std::shared_ptr<Source> ret (src);
BOOST_MARK_SOURCE (ret);
@ -252,13 +252,14 @@ SourceFactory::createExternal (DataType type, Session& s, const string& path,
{
if (type == DataType::AUDIO) {
try {
Source* src = new SndFileSource (s, path, chn, flags);
AudioSource* src = new SndFileSource (s, path, chn, flags);
std::shared_ptr<Source> ret (src);
BOOST_MARK_SOURCE (ret);
if (setup_peakfile (ret, defer_peaks)) {
throw failed_constructor ();
}
ret->check_for_analysis_data_on_disk ();
src->estimate_tempo ();
if (announce) {
SourceCreated (ret);
}
@ -268,13 +269,14 @@ SourceFactory::createExternal (DataType type, Session& s, const string& path,
#ifdef HAVE_COREAUDIO
try {
Source* src = new CoreAudioSource (s, path, chn, flags);
AudioSource* src = new CoreAudioSource (s, path, chn, flags);
std::shared_ptr<Source> ret (src);
BOOST_MARK_SOURCE (ret);
if (setup_peakfile (ret, defer_peaks)) {
throw failed_constructor ();
}
ret->check_for_analysis_data_on_disk ();
src->estimate_tempo ();
if (announce) {
SourceCreated (ret);
}
@ -415,8 +417,7 @@ SourceFactory::createFromPlaylist (DataType type, Session& s, std::shared_ptr<Pl
start = timecnt_t::zero (Temporal::AudioTime);
}
Source* src = new AudioPlaylistSource (s, orig, name, ap, chn, start, len, Source::Flag (0));
AudioSource* src = new AudioPlaylistSource (s, orig, name, ap, chn, start, len, Source::Flag (0));
std::shared_ptr<Source> ret (src);
if (setup_peakfile (ret, defer_peaks)) {
@ -424,6 +425,7 @@ SourceFactory::createFromPlaylist (DataType type, Session& s, std::shared_ptr<Pl
}
ret->check_for_analysis_data_on_disk ();
src->estimate_tempo ();
SourceCreated (ret);
return ret;
}