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:
Paul Davis 2020-07-16 18:35:53 -06:00
parent 4c4e4e545a
commit e0b5b12129
4 changed files with 271 additions and 4 deletions

View File

@ -499,7 +499,7 @@ operator>>(std::istream& istr, Beats& b)
return istr;
}
} // namespace Evoral
} // namespace Temporal
namespace PBD {
namespace DEBUG {

View File

@ -21,14 +21,16 @@
#include <stdint.h>
namespace ARDOUR {
#include "pbd/integer_division.h"
namespace Temporal {
typedef uint64_t superclock_t;
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 samples_to_superclock (int samples, int sr) { return (samples * superclock_ticks_per_second) / sr; }
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 int_div_round (samples * superclock_ticks_per_second, superclock_t (sr)); }
}

View 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
View 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;
}