mirror of
https://github.com/Ardour/ardour.git
synced 2026-01-08 06:35:46 +01:00
refactor AudioTrigger::estimate_tempo() into ARDOUR::estimate_audio_tempo()
This commit is contained in:
parent
c59dddfdae
commit
cec3db54f0
3 changed files with 141 additions and 117 deletions
|
|
@ -44,10 +44,15 @@
|
|||
|
||||
class XMLNode;
|
||||
|
||||
namespace Temporal {
|
||||
class Meter;
|
||||
}
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Route;
|
||||
class Track;
|
||||
class Region;
|
||||
|
||||
LIBARDOUR_API std::string legalize_for_path (const std::string& str);
|
||||
LIBARDOUR_API std::string legalize_for_universal_path (const std::string& str);
|
||||
|
|
@ -146,6 +151,8 @@ template<typename T> std::shared_ptr<AutomationControlList> stripable_list_to_co
|
|||
return cl;
|
||||
}
|
||||
|
||||
LIBARDOUR_API bool estimate_audio_tempo (std::shared_ptr<Region> region, Sample* data, samplecnt_t data_length, samplecnt_t sample_rate, double& qpm, Temporal::Meter& meter, double& beatcount);
|
||||
|
||||
#if __APPLE__
|
||||
LIBARDOUR_API std::string CFStringRefToStdString(CFStringRef stringRef);
|
||||
#endif // __APPLE__
|
||||
|
|
|
|||
|
|
@ -1773,123 +1773,11 @@ AudioTrigger::set_region_in_worker_thread_internal (std::shared_ptr<Region> r, b
|
|||
void
|
||||
AudioTrigger::estimate_tempo ()
|
||||
{
|
||||
using namespace Temporal;
|
||||
TempoMap::SharedPtr tm (TempoMap::use());
|
||||
double beatcount;
|
||||
ARDOUR::estimate_audio_tempo (_region, data[0], data.length, _box.session().sample_rate(), _estimated_tempo, _meter, beatcount);
|
||||
/* initialize our follow_length to match the beatcnt ... user can later change this value to have the clip end sooner or later than its data length */
|
||||
set_follow_length(Temporal::BBT_Offset( 0, rint(beatcount), 0));
|
||||
|
||||
TimelineRange range (_region->start(), _region->start() + _region->length(), 0);
|
||||
SegmentDescriptor segment;
|
||||
bool have_segment;
|
||||
|
||||
have_segment = _region->source (0)->get_segment_descriptor (range, segment);
|
||||
|
||||
if (have_segment) {
|
||||
|
||||
_estimated_tempo = segment.tempo().quarter_notes_per_minute ();
|
||||
_meter = segment.meter();
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1: tempo and meter from segment descriptor\n", index()));
|
||||
|
||||
} else {
|
||||
/* not a great guess, but what else can we do? */
|
||||
|
||||
TempoMetric const & metric (tm->metric_at (timepos_t (AudioTime)));
|
||||
|
||||
_meter = metric.meter ();
|
||||
|
||||
/* check the name to see if there's a (heuristically obvious) hint
|
||||
* about the tempo.
|
||||
*/
|
||||
|
||||
string str = _region->name();
|
||||
string::size_type bi;
|
||||
string::size_type ni;
|
||||
double text_tempo = -1.;
|
||||
|
||||
if (((bi = str.find (" bpm")) != string::npos) ||
|
||||
((bi = str.find ("bpm")) != string::npos) ||
|
||||
((bi = str.find (" BPM")) != string::npos) ||
|
||||
((bi = str.find ("BPM")) != string::npos) ){
|
||||
|
||||
string sub (str.substr (0, bi));
|
||||
|
||||
if ((ni = sub.find_last_of ("0123456789.,_-")) != string::npos) {
|
||||
|
||||
int nni = ni; /* ni is unsigned, nni is signed */
|
||||
|
||||
while (nni >= 0) {
|
||||
if (!isdigit (sub[nni]) &&
|
||||
(sub[nni] != '.') &&
|
||||
(sub[nni] != ',')) {
|
||||
break;
|
||||
}
|
||||
--nni;
|
||||
}
|
||||
|
||||
if (nni > 0) {
|
||||
std::stringstream p (sub.substr (nni + 1));
|
||||
p >> text_tempo;
|
||||
if (!p) {
|
||||
text_tempo = -1.;
|
||||
} else {
|
||||
_estimated_tempo = text_tempo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text_tempo < 0) {
|
||||
|
||||
breakfastquay::MiniBPM mbpm (_box.session().sample_rate());
|
||||
|
||||
_estimated_tempo = mbpm.estimateTempoOfSamples (data[0], data.length);
|
||||
|
||||
//cerr << name() << "MiniBPM Estimated: " << _estimated_tempo << " bpm from " << (double) data.length / _box.session().sample_rate() << " seconds\n";
|
||||
}
|
||||
}
|
||||
|
||||
const double seconds = (double) data.length / _box.session().sample_rate();
|
||||
|
||||
/* now check the determined tempo and force it to a value that gives us
|
||||
an integer beat/quarter count. This is a heuristic that tries to
|
||||
avoid clips that slightly over- or underrun a quantization point,
|
||||
resulting in small or larger gaps in output if they are repeating.
|
||||
*/
|
||||
|
||||
if ((_estimated_tempo != 0.)) {
|
||||
/* fractional beatcnt */
|
||||
double maybe_beats = (seconds / 60.) * _estimated_tempo;
|
||||
double beatcount = round (maybe_beats);
|
||||
|
||||
/* the vast majority of third-party clips are 1,2,4,8, or 16-bar 'beats'.
|
||||
* Given no other metadata, it makes things 'just work' if we assume 4/4 time signature, and power-of-2 bars (1,2,4,8 or 16)
|
||||
* TODO: someday we could provide a widget for users who have unlabeled, un-metadata'd, clips that they *know* are 3/4 or 5/4 or 11/4 */
|
||||
{
|
||||
double barcount = round (beatcount/4);
|
||||
if (barcount <= 18) { /* why not 16 here? fuzzy logic allows minibpm to misjudge the clip a bit */
|
||||
for (int pwr = 0; pwr <= 4; pwr++) {
|
||||
float bc = pow(2,pwr);
|
||||
if (barcount <= bc) {
|
||||
barcount = bc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
beatcount = round(barcount * 4);
|
||||
}
|
||||
|
||||
DEBUG_RESULT (double, est, _estimated_tempo);
|
||||
_estimated_tempo = beatcount / (seconds/60.);
|
||||
DEBUG_TRACE (DEBUG::Triggers, string_compose ("given original estimated tempo %1, rounded beatcnt is %2 : resulting in working bpm = %3\n", est, _beatcnt, _estimated_tempo));
|
||||
|
||||
/* initialize our follow_length to match the beatcnt ... user can later change this value to have the clip end sooner or later than its data length */
|
||||
set_follow_length(Temporal::BBT_Offset( 0, rint(beatcount), 0));
|
||||
}
|
||||
|
||||
#if 0
|
||||
cerr << "estimated tempo: " << _estimated_tempo << endl;
|
||||
const samplecnt_t one_beat = tm->bbt_duration_at (timepos_t (AudioTime), BBT_Offset (0, 1, 0)).samples();
|
||||
cerr << "one beat in samples: " << one_beat << endl;
|
||||
cerr << "rounded beatcount = " << round (beatcount) << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -60,8 +60,14 @@
|
|||
#include "pbd/strsplit.h"
|
||||
#include "pbd/replace_all.h"
|
||||
|
||||
#include "ardour/utils.h"
|
||||
#include "temporal/tempo.h"
|
||||
|
||||
#include "ardour/minibpm.h"
|
||||
#include "ardour/region.h"
|
||||
#include "ardour/rc_configuration.h"
|
||||
#include "ardour/segment_descriptor.h"
|
||||
#include "ardour/source.h"
|
||||
#include "ardour/utils.h"
|
||||
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
|
|
@ -793,3 +799,126 @@ ARDOUR::compute_sha1_of_file (std::string path)
|
|||
sha1_result_hash (&s, hash);
|
||||
return std::string (hash);
|
||||
}
|
||||
|
||||
bool
|
||||
ARDOUR::estimate_audio_tempo (std::shared_ptr<Region> region, Sample* data, samplecnt_t data_length, samplecnt_t sample_rate, double& qpm, Temporal::Meter& meter, double& beatcount)
|
||||
{
|
||||
using namespace Temporal;
|
||||
TempoMap::SharedPtr tm (TempoMap::use());
|
||||
|
||||
TimelineRange range (region->start(), region->start() + region->length(), 0);
|
||||
SegmentDescriptor segment;
|
||||
bool have_segment;
|
||||
|
||||
have_segment = region->source (0)->get_segment_descriptor (range, segment);
|
||||
|
||||
if (have_segment) {
|
||||
|
||||
qpm = segment.tempo().quarter_notes_per_minute ();
|
||||
meter = segment.meter();
|
||||
// DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1: tempo and meter from segment descriptor\n", index()));
|
||||
|
||||
} else {
|
||||
/* not a great guess, but what else can we do? */
|
||||
|
||||
TempoMetric const & metric (tm->metric_at (timepos_t (AudioTime)));
|
||||
|
||||
meter = metric.meter ();
|
||||
|
||||
/* check the name to see if there's a (heuristically obvious) hint
|
||||
* about the tempo.
|
||||
*/
|
||||
|
||||
string str = region->name();
|
||||
string::size_type bi;
|
||||
string::size_type ni;
|
||||
double text_tempo = -1.;
|
||||
|
||||
if (((bi = str.find (" bpm")) != string::npos) ||
|
||||
((bi = str.find ("bpm")) != string::npos) ||
|
||||
((bi = str.find (" BPM")) != string::npos) ||
|
||||
((bi = str.find ("BPM")) != string::npos) ){
|
||||
|
||||
string sub (str.substr (0, bi));
|
||||
|
||||
if ((ni = sub.find_last_of ("0123456789.,_-")) != string::npos) {
|
||||
|
||||
int nni = ni; /* ni is unsigned, nni is signed */
|
||||
|
||||
while (nni >= 0) {
|
||||
if (!isdigit (sub[nni]) &&
|
||||
(sub[nni] != '.') &&
|
||||
(sub[nni] != ',')) {
|
||||
break;
|
||||
}
|
||||
--nni;
|
||||
}
|
||||
|
||||
if (nni > 0) {
|
||||
std::stringstream p (sub.substr (nni + 1));
|
||||
p >> text_tempo;
|
||||
if (!p) {
|
||||
text_tempo = -1.;
|
||||
} else {
|
||||
qpm = text_tempo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text_tempo < 0) {
|
||||
|
||||
breakfastquay::MiniBPM mbpm (sample_rate);
|
||||
|
||||
qpm = mbpm.estimateTempoOfSamples (data, data_length);
|
||||
|
||||
//cerr << name() << "MiniBPM Estimated: " << qpm << " bpm from " << (double) data.length / _box.session().sample_rate() << " seconds\n";
|
||||
}
|
||||
}
|
||||
|
||||
const double seconds = (double) data_length / sample_rate;
|
||||
|
||||
/* now check the determined tempo and force it to a value that gives us
|
||||
an integer beat/quarter count. This is a heuristic that tries to
|
||||
avoid clips that slightly over- or underrun a quantization point,
|
||||
resulting in small or larger gaps in output if they are repeating.
|
||||
*/
|
||||
|
||||
if ((qpm != 0.)) {
|
||||
/* fractional beatcnt */
|
||||
double maybe_beats = (seconds / 60.) * qpm;
|
||||
beatcount = round (maybe_beats);
|
||||
|
||||
/* the vast majority of third-party clips are 1,2,4,8, or 16-bar 'beats'.
|
||||
* Given no other metadata, it makes things 'just work' if we assume 4/4 time signature, and power-of-2 bars (1,2,4,8 or 16)
|
||||
* TODO: someday we could provide a widget for users who have unlabeled, un-metadata'd, clips that they *know* are 3/4 or 5/4 or 11/4 */
|
||||
{
|
||||
double barcount = round (beatcount/4);
|
||||
if (barcount <= 18) { /* why not 16 here? fuzzy logic allows minibpm to misjudge the clip a bit */
|
||||
for (int pwr = 0; pwr <= 4; pwr++) {
|
||||
float bc = pow(2,pwr);
|
||||
if (barcount <= bc) {
|
||||
barcount = bc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
beatcount = round(barcount * 4);
|
||||
}
|
||||
|
||||
// DEBUG_RESULT (double, est, qpm);
|
||||
qpm = beatcount / (seconds/60.);
|
||||
// DEBUG_TRACE (DEBUG::Triggers, string_compose ("given original estimated tempo %1, rounded beatcnt is %2 : resulting in working bpm = %3\n", est, _beatcnt, qpm));
|
||||
|
||||
}
|
||||
|
||||
#if 0
|
||||
cerr << "estimated tempo: " << qpm << endl;
|
||||
const samplecnt_t one_beat = tm->bbt_duration_at (timepos_t (AudioTime), BBT_Offset (0, 1, 0)).samples();
|
||||
cerr << "one beat in samples: " << one_beat << endl;
|
||||
cerr << "rounded beatcount = " << round (beatcount) << endl;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue