basic work on a Tempo object that avoids (almost) all floating point arithmetic
Basic conversions between superclock and Beats are provided
This commit is contained in:
parent
4c4e4e545a
commit
e0b5b12129
@ -499,7 +499,7 @@ operator>>(std::istream& istr, Beats& b)
|
|||||||
return istr;
|
return istr;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Evoral
|
} // namespace Temporal
|
||||||
|
|
||||||
namespace PBD {
|
namespace PBD {
|
||||||
namespace DEBUG {
|
namespace DEBUG {
|
||||||
|
@ -21,14 +21,16 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace ARDOUR {
|
#include "pbd/integer_division.h"
|
||||||
|
|
||||||
|
namespace Temporal {
|
||||||
|
|
||||||
typedef uint64_t superclock_t;
|
typedef uint64_t superclock_t;
|
||||||
|
|
||||||
static const superclock_t superclock_ticks_per_second = 508032000; // 2^10 * 3^4 * 5^3 * 7^2
|
static const superclock_t superclock_ticks_per_second = 508032000; // 2^10 * 3^4 * 5^3 * 7^2
|
||||||
|
|
||||||
static inline superclock_t superclock_to_samples (superclock_t s, int sr) { return (s * sr) / superclock_ticks_per_second; }
|
static inline superclock_t superclock_to_samples (superclock_t s, int sr) { return int_div_round (s * sr, superclock_ticks_per_second); }
|
||||||
static inline superclock_t samples_to_superclock (int samples, int sr) { return (samples * superclock_ticks_per_second) / sr; }
|
static inline superclock_t samples_to_superclock (int samples, int sr) { return int_div_round (samples * superclock_ticks_per_second, superclock_t (sr)); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
137
libs/temporal/temporal/tempo.h
Normal file
137
libs/temporal/temporal/tempo.h
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
Copyright (C) 2020 Paul Davis
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __libtemporal_tempo_h__
|
||||||
|
#define __libtemporal_tempo_h__
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <exception>
|
||||||
|
#include <limits>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "pbd/enumwriter.h"
|
||||||
|
#include "pbd/integer_division.h"
|
||||||
|
|
||||||
|
#include "temporal/beats.h"
|
||||||
|
#include "temporal/types.h"
|
||||||
|
#include "temporal/superclock.h"
|
||||||
|
#include "temporal/visibility.h"
|
||||||
|
|
||||||
|
/************************* !!!! ATTENTION !!!! *************************
|
||||||
|
|
||||||
|
NO FLOATING POINT ARITHMETIC IS ALLOWED IN THIS HEADER OR
|
||||||
|
ANY OF THE OBJECTS DEFINED HERE.
|
||||||
|
|
||||||
|
Exceptions:
|
||||||
|
|
||||||
|
1) constructors/methods that accept double from the user
|
||||||
|
2) clearly labelled methods that warn a developer to avoid
|
||||||
|
their use or use them only for display purposes.
|
||||||
|
|
||||||
|
************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
namespace Temporal {
|
||||||
|
|
||||||
|
class TempoValue {
|
||||||
|
private:
|
||||||
|
/* beats per minute * big_numerator => rational number expressing (possibly fractional) bpm as superbeats-per-minute
|
||||||
|
*
|
||||||
|
* It is not required that big_numerator equal superclock_ticks_per_second but since the values in both cases have similar
|
||||||
|
* desired properties (many, many factors), it doesn't hurt to use the same number.
|
||||||
|
*/
|
||||||
|
static const superclock_t big_numerator = superclock_ticks_per_second;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
TempoValue (double bpm) : val (bpm) {
|
||||||
|
|
||||||
|
/* since we allow the user to provide bpm as a floating point value,
|
||||||
|
we allow use of floating point math to determine the two critical
|
||||||
|
integer values (superbeats-per-second and superclocks-per-beat
|
||||||
|
*/
|
||||||
|
_sbps = (superclock_t) round (bpm * (big_numerator / 60));
|
||||||
|
_scpb = (superclock_t) round ((60./ bpm) * superclock_ticks_per_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
double given_bpm_for_display_only () const { return val; }
|
||||||
|
double actual_bpm_for_display_only () const { return (_sbps * 60) / (double) big_numerator; }
|
||||||
|
uint64_t ticks_per_second() const { return int_div_round ((_sbps * Temporal::ticks_per_beat), big_numerator); }
|
||||||
|
superclock_t superclocks_per_beat() const { return _scpb; }
|
||||||
|
|
||||||
|
Temporal::Beats superclocks_as_beats (superclock_t sc) const {
|
||||||
|
/* convert sc into superbeats, given that sc represents some number of seconds */
|
||||||
|
const superclock_t whole_seconds = sc / superclock_ticks_per_second;
|
||||||
|
const superclock_t remainder = sc - (whole_seconds * superclock_ticks_per_second);
|
||||||
|
const superclock_t superbeats = (_sbps * whole_seconds) + int_div_round ((_sbps * remainder), superclock_ticks_per_second);
|
||||||
|
|
||||||
|
/* convert superbeats to beats:ticks */
|
||||||
|
|
||||||
|
uint32_t b = superbeats / big_numerator;
|
||||||
|
const uint64_t remain = superbeats - (b * big_numerator);
|
||||||
|
uint32_t t = int_div_round ((Temporal::ticks_per_beat * remain), big_numerator);
|
||||||
|
|
||||||
|
return Beats (b, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
superclock_t beats_as_superclocks (Temporal::Beats const & b) const {
|
||||||
|
/* no symmetrical with superclocks_as_beats() because Beats already breaks apart the beats:ticks,
|
||||||
|
with the ticks value denominated by Temporal::ticks_per_beat
|
||||||
|
*/
|
||||||
|
return (_scpb * b.get_beats()) + int_div_round ((_scpb * b.get_ticks()), superclock_t (Temporal::ticks_per_beat));
|
||||||
|
}
|
||||||
|
|
||||||
|
Temporal::Beats seconds_as_beats (uint64_t num, uint64_t denom) const {
|
||||||
|
return superclocks_as_beats ((num * superclock_ticks_per_second) / denom);
|
||||||
|
}
|
||||||
|
|
||||||
|
double beats_as_float_seconds_avoid_me (Temporal::Beats const & b) const {
|
||||||
|
return beats_as_superclocks (b) / (double) superclock_ticks_per_second;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double val; /* as given to constructor */
|
||||||
|
uint64_t _sbps; /* superbeats-per-second */
|
||||||
|
superclock_t _scpb; /* superclocks-per-beat */
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::ostream&
|
||||||
|
operator<<(std::ostream& os, const TempoValue& v)
|
||||||
|
{
|
||||||
|
os << v.actual_bpm_for_display_only ();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::istream&
|
||||||
|
operator>>(std::istream& istr, TempoValue& v)
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
uint16_t w;
|
||||||
|
uint64_t f;
|
||||||
|
char d; /* delimiter, whatever it is */
|
||||||
|
istr >> w >> d >> f;
|
||||||
|
v = TempoValue (w, f);
|
||||||
|
#endif
|
||||||
|
return istr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace Temporal */
|
||||||
|
|
||||||
|
#endif /* __libtemporal_tempo_h__ */
|
128
libs/temporal/test.cc
Normal file
128
libs/temporal/test.cc
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/* COMPILE: c++ -o test -I../pbd -I. test.cc */
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include "temporal/tempo.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace Temporal;
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char* argv[])
|
||||||
|
{
|
||||||
|
double bpm;
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
cerr << "Usage: " << argv[0] << " BPM\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sscanf (argv[1], "%lf", &bpm) != 1) {
|
||||||
|
cerr << "Cannot parse " << argv[1] << " as floating point value\n";
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
TempoValue tempo (bpm);
|
||||||
|
uint64_t numerator_seconds = 1;
|
||||||
|
uint64_t denominator_seconds = 2;
|
||||||
|
Temporal::Beats fb = tempo.seconds_as_beats (numerator_seconds, denominator_seconds);
|
||||||
|
|
||||||
|
/* compute 20 minutes of beats:ticks */
|
||||||
|
Temporal::Beats b20 = tempo.seconds_as_beats ((20 * 60), 1);
|
||||||
|
|
||||||
|
double d20 = 20.0 * bpm;
|
||||||
|
double d20r = 20.0 * tempo.actual_bpm_for_display_only ();
|
||||||
|
|
||||||
|
cout << "bpm " << setprecision (12) << bpm << " => " << tempo << " ticks/second = " << tempo.ticks_per_second ()
|
||||||
|
<< " tps " << tempo.ticks_per_second ()
|
||||||
|
<< " bpm " << tempo.actual_bpm_for_display_only ()
|
||||||
|
<< ' ' << numerator_seconds << '/' << denominator_seconds << " sec = " << fb
|
||||||
|
<< " 20 mins = " << b20
|
||||||
|
<< " 20 mins " << d20 << " computed " << d20r
|
||||||
|
<< " b-as-sc " << tempo.beats_as_superclocks (Beats (1,0))
|
||||||
|
<< " scpb " << tempo.superclocks_per_beat ()
|
||||||
|
<< endl;
|
||||||
|
|
||||||
|
for (uint32_t tn = 0; tn < Temporal::ticks_per_beat; ++tn) {
|
||||||
|
Temporal::Beats b (1, tn);
|
||||||
|
|
||||||
|
double sec = tempo.beats_as_float_seconds_avoid_me (b);
|
||||||
|
double csec = (60. / bpm) * (1 + (tn / 1920.));
|
||||||
|
|
||||||
|
if (fabs (sec - csec) > 0.00000001) {
|
||||||
|
cout << b << " sec " << sec << " csec " << csec << " err " << sec - csec << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rate[] = { 16000, 22050, 24000, 32000, 33075, 44100, 48000, 88200, 96000, 0 };
|
||||||
|
|
||||||
|
for (int s = 0; rate[s] != 0; ++s) {
|
||||||
|
|
||||||
|
cout << "Checking with SR = " << rate[s] << endl;
|
||||||
|
|
||||||
|
double tempos[] = {
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
30,
|
||||||
|
60,
|
||||||
|
120,
|
||||||
|
240,
|
||||||
|
1200,
|
||||||
|
33,
|
||||||
|
47,
|
||||||
|
91 + 4./7,
|
||||||
|
100./3.,
|
||||||
|
100./7.,
|
||||||
|
100./5.,
|
||||||
|
100./9.,
|
||||||
|
M_PI,
|
||||||
|
M_PI * 20.,
|
||||||
|
0.
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
for (int t = 0; tempos[t] != 0; ++t) {
|
||||||
|
|
||||||
|
tempo = TempoValue (tempos[t]);
|
||||||
|
cout << "\tChecking tempo " << tempo.given_bpm_for_display_only () << endl;
|
||||||
|
|
||||||
|
for (uint32_t tn = 0; tn < Temporal::ticks_per_beat; ++tn) {
|
||||||
|
Temporal::Beats b (1, tn);
|
||||||
|
|
||||||
|
superclock_t sc = tempo.beats_as_superclocks (b);
|
||||||
|
Temporal::Beats b2 = tempo.superclocks_as_beats (sc);
|
||||||
|
|
||||||
|
if (b2 != b) {
|
||||||
|
cout << "\t\tb2 " << b2 << " != b " << b << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t samples = superclock_to_samples (sc, rate[s]);
|
||||||
|
superclock_t sc2 = samples_to_superclock (samples, rate[s]);
|
||||||
|
|
||||||
|
Temporal::Beats b3 = tempo.superclocks_as_beats (sc2);
|
||||||
|
|
||||||
|
if (b3 != b) {
|
||||||
|
cout << "\t\tb3 " << b3 << " != b " << b << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Now checking sample positions\n";
|
||||||
|
|
||||||
|
for (int p = 0; p < rate[s]; ++p) {
|
||||||
|
superclock_t sc = samples_to_superclock (p, rate[s]);
|
||||||
|
Temporal::Beats b = tempo.superclocks_as_beats (sc);
|
||||||
|
superclock_t sc2 = tempo.beats_as_superclocks (b);
|
||||||
|
uint64_t sm = superclock_to_samples (sc2, rate[s]);
|
||||||
|
|
||||||
|
if (p != sm) {
|
||||||
|
cout << "\t\tsm " << sm << " != " << p << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user