2020-08-04 15:52:28 -04:00
|
|
|
/*
|
|
|
|
Copyright (C) 2017 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
#include <exception>
|
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#include "pbd/enumwriter.h"
|
|
|
|
#include "pbd/error.h"
|
|
|
|
#include "pbd/compose.h"
|
|
|
|
|
|
|
|
#include "temporal/debug.h"
|
|
|
|
#include "temporal/timeline.h"
|
|
|
|
#include "temporal/tempo.h"
|
|
|
|
|
2021-09-27 09:49:41 -04:00
|
|
|
#include "pbd/i18n.h"
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
using namespace PBD;
|
|
|
|
using namespace Temporal;
|
|
|
|
|
|
|
|
struct TemporalStatistics
|
|
|
|
{
|
|
|
|
int64_t audio_to_beats;
|
|
|
|
int64_t audio_to_bars;
|
|
|
|
int64_t beats_to_audio;
|
|
|
|
int64_t beats_to_bars;
|
|
|
|
int64_t bars_to_audio;
|
|
|
|
int64_t bars_to_beats;
|
|
|
|
|
|
|
|
TemporalStatistics ()
|
|
|
|
: audio_to_beats (0)
|
|
|
|
, audio_to_bars (0)
|
|
|
|
, beats_to_audio (0)
|
|
|
|
, beats_to_bars (0)
|
|
|
|
, bars_to_audio (0)
|
|
|
|
, bars_to_beats (0)
|
|
|
|
{}
|
|
|
|
|
|
|
|
void dump (std::ostream & str) {
|
|
|
|
str << "TemporalStatistics\n"
|
2021-02-01 20:56:16 -05:00
|
|
|
<< "Audio => Beats " << audio_to_beats << ' '
|
|
|
|
<< "Audio => Bars " << audio_to_bars << ' '
|
|
|
|
<< "Beats => Audio " << beats_to_audio << ' '
|
|
|
|
<< "Beats => Bars " << beats_to_bars << ' '
|
|
|
|
<< "Bars => Audio " << bars_to_audio << ' '
|
2020-08-04 15:52:28 -04:00
|
|
|
<< "Bars => Beats " << bars_to_beats
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static TemporalStatistics stats;
|
|
|
|
|
2021-02-01 20:56:16 -05:00
|
|
|
void Temporal::dump_stats (std::ostream& o) { stats.dump (o); }
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
/* timecnt */
|
|
|
|
|
2020-08-11 20:26:25 -04:00
|
|
|
timecnt_t timecnt_t::_max_timecnt (timecnt_t::from_superclock (int62_t::max - 1));
|
2020-08-04 15:52:28 -04:00
|
|
|
|
|
|
|
timecnt_t::timecnt_t (timecnt_t const & tc, timepos_t const & pos)
|
|
|
|
: _position (pos)
|
|
|
|
{
|
|
|
|
if (tc.distance() < 0) {
|
|
|
|
throw std::domain_error (X_("negative distance in timecnt constructor"));
|
|
|
|
}
|
|
|
|
|
|
|
|
_distance = tc.distance();
|
|
|
|
}
|
|
|
|
|
2021-11-22 12:35:52 -05:00
|
|
|
timecnt_t::timecnt_t (samplepos_t s, timepos_t const & pos)
|
|
|
|
: _position (pos)
|
|
|
|
{
|
|
|
|
assert (_position.time_domain() == AudioTime);
|
|
|
|
|
|
|
|
if (s == max_samplepos) {
|
|
|
|
_distance = int62_t (false, int62_t::max);
|
|
|
|
} else {
|
|
|
|
_distance = int62_t (false, samples_to_superclock (s, TEMPORAL_SAMPLE_RATE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t::timecnt_t (samplepos_t s)
|
|
|
|
: _position (AudioTime)
|
|
|
|
{
|
|
|
|
if (s == max_samplepos) {
|
|
|
|
_distance = int62_t (false, int62_t::max);
|
|
|
|
} else {
|
|
|
|
_distance = int62_t (false, samples_to_superclock (s, TEMPORAL_SAMPLE_RATE));
|
|
|
|
}
|
|
|
|
}
|
2021-02-26 14:21:13 -05:00
|
|
|
|
|
|
|
timepos_t
|
|
|
|
timecnt_t::end (TimeDomain return_domain) const
|
|
|
|
{
|
|
|
|
if (_distance.flagged() && _position.time_domain() == BeatTime && return_domain == BeatTime) {
|
|
|
|
/* everything in BeatTime, so just add */
|
|
|
|
return timepos_t (_position.beats() + Beats::ticks (magnitude()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!_distance.flagged() && _position.time_domain() == AudioTime && return_domain == AudioTime) {
|
|
|
|
/* everything in AudioTime, so just add */
|
|
|
|
return timepos_t::from_superclock (_position.superclocks() + magnitude());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_distance.flagged()) { /* _distance in beats */
|
|
|
|
|
|
|
|
if (_position.time_domain() == BeatTime) {
|
|
|
|
|
|
|
|
/* distance & position in beats, so return must be audio (all 3 as beats is handled above) */
|
|
|
|
return timepos_t::from_superclock (TempoMap::use()->superclock_at ( _position.beats() + Beats::ticks (magnitude())));
|
|
|
|
|
|
|
|
} else if (_position.time_domain() == AudioTime) {
|
|
|
|
|
|
|
|
const Beats b = TempoMap::use()->quarters_at_superclock (_position.superclocks() + magnitude());
|
|
|
|
|
|
|
|
if (return_domain == BeatTime) {
|
|
|
|
return timepos_t (b);
|
|
|
|
} else {
|
|
|
|
return timepos_t::from_superclock (TempoMap::use()->superclock_at (b));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 01:53:21 -04:00
|
|
|
} /* else _distance in audio time */
|
2021-02-26 14:21:13 -05:00
|
|
|
|
2021-03-24 01:53:21 -04:00
|
|
|
if (_position.time_domain() == AudioTime) {
|
|
|
|
/* distance & position in audio, so return must be beats (all 3 as audio is handled above) */
|
|
|
|
return timepos_t (TempoMap::use()->quarters_at_superclock (_position.superclocks() + magnitude()));
|
2021-02-26 14:21:13 -05:00
|
|
|
|
2021-03-24 01:53:21 -04:00
|
|
|
} /* else if (_position.time_domain() == BeatTime) { */
|
2021-02-26 14:21:13 -05:00
|
|
|
|
2021-03-24 01:53:21 -04:00
|
|
|
const superclock_t sc = TempoMap::use()->superclock_at (_position.beats()) + magnitude();
|
2021-02-26 14:21:13 -05:00
|
|
|
|
2021-03-24 01:53:21 -04:00
|
|
|
if (return_domain == AudioTime) {
|
|
|
|
return timepos_t::from_superclock (sc);
|
2021-02-26 14:21:13 -05:00
|
|
|
}
|
|
|
|
|
2021-03-24 01:53:21 -04:00
|
|
|
return timepos_t (TempoMap::use()->quarters_at_superclock (sc));
|
2021-02-26 14:21:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
timecnt_t::set_time_domain (TimeDomain td)
|
|
|
|
{
|
|
|
|
if (time_domain() == td) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_position.set_time_domain (td);
|
|
|
|
|
|
|
|
if (_distance.flagged()) {
|
|
|
|
/* beats -> superclock */
|
|
|
|
_distance = int62_t (false, TempoMap::use()->superclock_at (Beats::ticks (magnitude())));
|
|
|
|
} else {
|
|
|
|
/* superclock -> beats */
|
|
|
|
_distance = int62_t (true, TempoMap::use()->quarters_at_superclock (magnitude()).to_ticks());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
void
|
|
|
|
timecnt_t::set_position (timepos_t const & pos)
|
|
|
|
{
|
|
|
|
_position = pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t
|
|
|
|
timecnt_t::abs () const
|
|
|
|
{
|
|
|
|
return timecnt_t (_distance.abs(), _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
superclock_t
|
|
|
|
timecnt_t::compute_superclocks() const
|
|
|
|
{
|
|
|
|
assert (_distance.flagged());
|
2020-08-05 17:10:51 -04:00
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
2021-02-26 15:02:45 -05:00
|
|
|
return tm->convert_duration (*this, _position, AudioTime).superclocks();
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Beats
|
|
|
|
timecnt_t::compute_beats() const
|
|
|
|
{
|
|
|
|
assert (!_distance.flagged());
|
2021-02-26 15:02:45 -05:00
|
|
|
return TempoMap::use()->convert_duration (*this, _position, BeatTime).beats();
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t
|
2022-05-27 14:46:22 -04:00
|
|
|
timecnt_t::scale (ratio_t const & r) const
|
2020-08-04 15:52:28 -04:00
|
|
|
{
|
2022-05-28 14:56:42 -04:00
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return timecnt_t::from_superclock (PBD::muldiv (_distance.val(), r.numerator(), r.denominator()), _position);
|
|
|
|
} else {
|
|
|
|
return timecnt_t::from_ticks (PBD::muldiv (_distance.val(), r.numerator(), r.denominator()), _position);
|
|
|
|
}
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-09-29 19:53:06 -04:00
|
|
|
ratio_t
|
|
|
|
timecnt_t::operator/ (timecnt_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == other.time_domain()) {
|
|
|
|
return ratio_t (distance().val(), other.distance().val());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return ratio_t (distance().val(), other.samples());
|
|
|
|
}
|
|
|
|
|
|
|
|
return ratio_t (beats().to_ticks(), other.beats().to_ticks());
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
timecnt_t
|
|
|
|
timecnt_t::operator% (timecnt_t const & d) const
|
|
|
|
{
|
|
|
|
return timecnt_t (_distance % d.distance(), _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t &
|
|
|
|
timecnt_t::operator%= (timecnt_t const & d)
|
|
|
|
{
|
|
|
|
_distance %= d.distance();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timecnt_t::string_to (std::string const & str)
|
|
|
|
{
|
2020-08-04 17:38:33 -04:00
|
|
|
superclock_t s;
|
2020-12-28 20:11:25 -05:00
|
|
|
samplecnt_t sm;
|
|
|
|
int64_t ticks;
|
2020-08-04 17:38:33 -04:00
|
|
|
Beats beats;
|
|
|
|
char sep;
|
|
|
|
|
|
|
|
if (isdigit (str[0])) {
|
|
|
|
/* old school position format: we assume samples */
|
|
|
|
std::stringstream ss (str);
|
2020-12-28 20:11:25 -05:00
|
|
|
ss >> sm;
|
|
|
|
_distance = int62_t (false, samples_to_superclock (sm, TEMPORAL_SAMPLE_RATE));
|
|
|
|
_position = timepos_t (AudioTime);
|
2021-07-22 20:04:15 -04:00
|
|
|
// std::cerr << "deserialized timecnt from older " << str << " as " << *this << std::endl;
|
2020-08-04 17:38:33 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::stringstream ss (str.substr (1));
|
|
|
|
|
|
|
|
switch (str[0]) {
|
|
|
|
case 'a':
|
|
|
|
ss >> s;
|
2020-12-12 19:22:03 -05:00
|
|
|
_distance = int62_t (false, s);
|
2020-08-04 17:38:33 -04:00
|
|
|
break;
|
|
|
|
case 'b':
|
2020-12-28 20:11:25 -05:00
|
|
|
ss >> ticks;
|
|
|
|
_distance = int62_t (true, ticks);
|
2020-08-04 17:38:33 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* eat separator character */
|
|
|
|
|
|
|
|
ss >> sep;
|
|
|
|
|
|
|
|
/* grab what's left, generate a new string and parse _position with it */
|
|
|
|
|
|
|
|
std::string remaining;
|
|
|
|
ss >> remaining;
|
|
|
|
|
|
|
|
_position.string_to (remaining);
|
|
|
|
|
|
|
|
return true;
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string
|
2021-11-13 16:39:20 -05:00
|
|
|
timecnt_t::str () const
|
2020-08-04 15:52:28 -04:00
|
|
|
{
|
2020-08-04 17:38:33 -04:00
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
if (_distance.flagged()) {
|
|
|
|
ss << 'b';
|
|
|
|
} else {
|
|
|
|
ss << 'a';
|
|
|
|
}
|
|
|
|
|
|
|
|
ss << _distance.val();
|
|
|
|
|
|
|
|
/* add a separator. character doesn't matter as long as it will never be
|
|
|
|
parsed as part of a numerical value. Using '@' makes it "read
|
|
|
|
nicely" e.g. "3 beats at superclock 28229992292"
|
|
|
|
*/
|
|
|
|
|
|
|
|
ss << '@';
|
2021-11-13 16:39:20 -05:00
|
|
|
ss << _position.str();
|
2020-08-04 17:38:33 -04:00
|
|
|
|
|
|
|
return ss.str();
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-11-16 20:50:45 -05:00
|
|
|
timecnt_t
|
|
|
|
timecnt_t::operator+ (timecnt_t const & other) const
|
|
|
|
{
|
2020-12-23 15:15:44 -05:00
|
|
|
if (time_domain() == other.time_domain()) {
|
|
|
|
int62_t v (_distance.flagged(), _distance.val() + other.distance().val());
|
|
|
|
return timecnt_t (v, _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mismatched time domains */
|
|
|
|
|
2020-11-16 20:50:45 -05:00
|
|
|
if (time_domain() == AudioTime) {
|
2020-12-23 15:15:44 -05:00
|
|
|
/* other must be beats */
|
|
|
|
return timecnt_t (_distance + other.superclocks(), _position);
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return timecnt_t (beats() + other.beats(), _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t
|
|
|
|
timecnt_t::operator- (timecnt_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
if (other.time_domain() == AudioTime) {
|
|
|
|
return timecnt_t (_distance - other.distance(), _position);
|
|
|
|
} else {
|
|
|
|
return timecnt_t (_distance - other.samples(), _position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return timecnt_t (beats() - other.beats(), _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t &
|
|
|
|
timecnt_t::operator+= (timecnt_t const & other)
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
if (other.time_domain() == AudioTime) {
|
|
|
|
_distance += other.distance();
|
|
|
|
} else {
|
|
|
|
_distance += other.samples();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_distance += other.ticks ();
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t
|
|
|
|
timecnt_t::operator+ (timepos_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
if (other.time_domain() == AudioTime) {
|
|
|
|
/* both audio, just add and use an arbitrary position */
|
|
|
|
return timecnt_t (_distance + other.val(), _position);
|
|
|
|
} else {
|
|
|
|
return timecnt_t (_distance + other.samples(), _position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return timecnt_t (beats() + other.beats(), _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t
|
|
|
|
timecnt_t::operator- (timepos_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
if (other.time_domain() == AudioTime) {
|
|
|
|
return timecnt_t (_distance - other.val(), _position);
|
|
|
|
} else {
|
|
|
|
return timecnt_t (_distance - other.samples(), _position);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return timecnt_t (beats() - other.beats(), _position);
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t &
|
|
|
|
timecnt_t::operator-= (timecnt_t const & other)
|
|
|
|
{
|
|
|
|
if (time_domain() == other.time_domain()) {
|
|
|
|
_distance -= other.distance();
|
|
|
|
} else if (time_domain() == AudioTime) {
|
|
|
|
_distance -= other.samples();
|
|
|
|
} else {
|
|
|
|
_distance -= other.ticks ();
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
timecnt_t
|
|
|
|
timecnt_t::operator- () const
|
|
|
|
{
|
|
|
|
return timecnt_t (-_distance, _position);
|
|
|
|
}
|
|
|
|
|
2020-11-29 20:47:58 -05:00
|
|
|
bool
|
|
|
|
timecnt_t::expensive_lt (timecnt_t const & other) const
|
|
|
|
{
|
|
|
|
if (!_distance.flagged()) { /* Audio */
|
|
|
|
return _distance.val() < other.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Beats::ticks (_distance.val()) < other.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timecnt_t::expensive_gt (timecnt_t const & other) const
|
|
|
|
{
|
|
|
|
if (!_distance.flagged()) { /* Audio */
|
|
|
|
return _distance.val() > other.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Beats::ticks (_distance.val()) > other.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timecnt_t::expensive_lte (timecnt_t const & other) const
|
|
|
|
{
|
|
|
|
if (!_distance.flagged()) { /* Audio */
|
|
|
|
return _distance.val() <= other.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Beats::ticks (_distance.val()) <= other.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timecnt_t::expensive_gte (timecnt_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return _distance.val() >= other.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Beats::ticks (_distance.val()) >= other.beats ();
|
|
|
|
}
|
|
|
|
|
2020-11-30 11:59:34 -05:00
|
|
|
std::ostream&
|
|
|
|
std::operator<< (std::ostream & o, timecnt_t const & tc)
|
|
|
|
{
|
2021-11-13 16:39:20 -05:00
|
|
|
return o << tc.str();
|
2020-11-30 11:59:34 -05:00
|
|
|
}
|
|
|
|
|
2021-02-03 14:12:28 -05:00
|
|
|
std::istream&
|
|
|
|
std::operator>> (std::istream & o, timecnt_t & tc)
|
|
|
|
{
|
|
|
|
std::string str;
|
|
|
|
o >> str; /* will break at whitespace */
|
|
|
|
tc.string_to (str);
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
/* timepos */
|
|
|
|
|
|
|
|
timepos_t::timepos_t (timecnt_t const & t)
|
|
|
|
{
|
2020-08-09 16:13:28 -04:00
|
|
|
v = build (t.distance().flagged(), t.distance ().val());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2021-11-22 12:35:52 -05:00
|
|
|
timepos_t::timepos_t (samplepos_t s)
|
|
|
|
{
|
|
|
|
if (s == max_samplepos) {
|
|
|
|
v = build (false, int62_t::max);
|
|
|
|
} else {
|
|
|
|
v = build (false, samples_to_superclock (s, TEMPORAL_SAMPLE_RATE));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-15 16:51:58 -05:00
|
|
|
void
|
|
|
|
timepos_t::set_time_domain (TimeDomain td)
|
|
|
|
{
|
|
|
|
if (td == time_domain()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (td == AudioTime) {
|
2021-07-27 13:51:04 -04:00
|
|
|
v = build (false, _superclocks());
|
2021-01-15 16:51:58 -05:00
|
|
|
} else {
|
|
|
|
v = build (true, _beats().to_ticks());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
// timepos_t timepos_t::_max_timepos (Temporal::AudioTime);
|
|
|
|
|
|
|
|
timepos_t &
|
|
|
|
timepos_t::operator= (timecnt_t const & t)
|
|
|
|
{
|
2020-08-09 16:13:28 -04:00
|
|
|
v = build (t.distance().flagged(), t.distance().val());
|
2020-08-04 15:52:28 -04:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2020-11-16 20:50:45 -05:00
|
|
|
bool
|
|
|
|
timepos_t::operator< (timecnt_t const & t) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return superclocks() < t.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return beats() < t.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::operator> (timecnt_t const & t) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return superclocks() > t.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return beats() > t.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::operator<= (timecnt_t const & t) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return superclocks() <= t.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return beats() <= t.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::operator>= (timecnt_t const & t) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return superclocks() >= t.superclocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
return beats() >= t.beats ();
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
superclock_t
|
|
|
|
timepos_t::_superclocks () const
|
|
|
|
{
|
2021-02-14 00:21:43 -05:00
|
|
|
assert (time_domain() == BeatTime);
|
2020-08-04 15:52:28 -04:00
|
|
|
stats.beats_to_audio++;
|
|
|
|
|
2021-02-14 00:21:43 -05:00
|
|
|
return TempoMap::use()->superclock_at (beats ());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Temporal::Beats
|
|
|
|
timepos_t::_beats () const
|
|
|
|
{
|
2021-02-14 00:21:43 -05:00
|
|
|
assert (time_domain() == AudioTime);
|
2020-08-04 15:52:28 -04:00
|
|
|
stats.audio_to_beats++;
|
|
|
|
|
2021-02-14 00:21:43 -05:00
|
|
|
/* see notes in Temporal::TempoPoint::quarters_at_superclock() for
|
|
|
|
* more. Basically, specially case "max-superclocks" and return
|
|
|
|
* "max-beats"
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (val() == int62_t::max) {
|
|
|
|
return std::numeric_limits<Beats>::max ();
|
|
|
|
}
|
|
|
|
|
|
|
|
return TempoMap::use()->quarters_at_superclock (v);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int64_t
|
|
|
|
timepos_t::_ticks () const
|
|
|
|
{
|
2021-02-14 00:21:43 -05:00
|
|
|
assert (time_domain() == AudioTime);
|
2020-08-04 15:52:28 -04:00
|
|
|
return _beats().to_ticks();
|
|
|
|
}
|
|
|
|
|
2020-09-20 18:33:43 -04:00
|
|
|
timepos_t
|
2022-05-27 14:46:22 -04:00
|
|
|
timepos_t::scale (ratio_t const & n) const
|
2020-09-20 18:33:43 -04:00
|
|
|
{
|
2022-05-28 14:56:42 -04:00
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return timepos_t::from_superclock (PBD::muldiv (val(), n.numerator(), n.denominator()));
|
|
|
|
} else {
|
|
|
|
return timepos_t::from_ticks (PBD::muldiv (val(), n.numerator(), n.denominator()));
|
|
|
|
}
|
2020-09-20 18:33:43 -04:00
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
timepos_t
|
2020-12-03 21:17:15 -05:00
|
|
|
timepos_t::expensive_add (timepos_t const & other) const
|
2020-08-04 15:52:28 -04:00
|
|
|
{
|
2020-12-03 21:17:15 -05:00
|
|
|
/* Called when other's time domain does not match our own, requiring us
|
|
|
|
to call either ::beats() or ::superclocks() on other to convert it to
|
|
|
|
our time domain.
|
|
|
|
*/
|
2020-08-11 20:26:25 -04:00
|
|
|
|
2020-12-03 21:17:15 -05:00
|
|
|
assert (is_beats() != other.is_beats ());
|
2020-08-11 20:26:25 -04:00
|
|
|
|
|
|
|
if (is_beats()) {
|
2020-12-03 21:17:15 -05:00
|
|
|
/* we are known to use music time, so val() is in ticks */
|
|
|
|
return timepos_t::from_ticks (val() + other.ticks());
|
2020-08-11 20:26:25 -04:00
|
|
|
}
|
|
|
|
|
2020-12-03 21:17:15 -05:00
|
|
|
/* we are known to use audio time, so val() is in superclocks */
|
|
|
|
return timepos_t::from_superclock (val() + other.superclocks());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* */
|
|
|
|
|
2020-12-18 13:32:09 -05:00
|
|
|
/* ::distance() assumes that @param other is later on the timeline than this, and
|
2020-08-04 15:52:28 -04:00
|
|
|
* thus returns a positive value if this condition is satisfied.
|
|
|
|
*/
|
|
|
|
|
|
|
|
timecnt_t
|
2020-12-03 01:21:15 -05:00
|
|
|
timepos_t::distance (timepos_t const & other) const
|
2020-08-04 15:52:28 -04:00
|
|
|
{
|
2020-12-03 01:21:15 -05:00
|
|
|
if (time_domain() == other.time_domain()) {
|
2020-12-18 13:38:08 -05:00
|
|
|
// std::cerr << "\ncomputing distance in " << enum_2_string (time_domain()) << std::endl;
|
2020-12-03 01:21:15 -05:00
|
|
|
return timecnt_t (int62_t (is_beats(), other.val() - val()), *this);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-12-18 13:38:08 -05:00
|
|
|
// std::cerr << "\ncomputing distance on " << enum_2_string (time_domain()) << " w/other = " << enum_2_string (other.time_domain()) << std::endl;
|
|
|
|
|
2020-12-03 01:21:15 -05:00
|
|
|
return expensive_distance (other);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-08-11 20:26:25 -04:00
|
|
|
timecnt_t
|
2020-12-03 01:21:15 -05:00
|
|
|
timepos_t::expensive_distance (timepos_t const & other) const
|
2020-08-04 15:52:28 -04:00
|
|
|
{
|
2020-12-03 01:43:09 -05:00
|
|
|
/* Called when other's time domain does not match our own, requiring us
|
|
|
|
to call either ::beats() or ::superclocks() on other to convert it to
|
|
|
|
our time domain.
|
|
|
|
*/
|
2020-12-03 21:17:15 -05:00
|
|
|
|
|
|
|
assert (is_beats() != other.is_beats ());
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
if (is_beats()) {
|
2020-12-03 01:43:09 -05:00
|
|
|
/* we are known to use beat time: val() is ticks */
|
|
|
|
return timecnt_t::from_ticks (other.ticks() - val(), *this);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
2020-12-03 01:43:09 -05:00
|
|
|
/* we known to be audio: val() is superclocks */
|
2020-12-18 13:38:08 -05:00
|
|
|
|
|
|
|
// std::cerr << "other " << other << " SC = " << other.superclocks() << " vs. us @ " << val() << std::endl;
|
2020-12-03 01:43:09 -05:00
|
|
|
return timecnt_t::from_superclock (other.superclocks() - val(), *this);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-08-11 20:26:25 -04:00
|
|
|
/* */
|
|
|
|
|
2020-11-16 20:50:45 -05:00
|
|
|
timepos_t
|
|
|
|
timepos_t::earlier (Temporal::BBT_Offset const & offset) const
|
|
|
|
{
|
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
|
|
|
|
|
|
|
if (is_superclock()) {
|
2021-03-25 12:24:05 -04:00
|
|
|
return timepos_t (tm->superclock_at (tm->bbt_walk (tm->bbt_at (*this), -offset)));
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return timepos_t (tm->bbtwalk_to_quarters (beats(), -offset));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
timepos_t
|
|
|
|
timepos_t::earlier (timepos_t const & other) const
|
|
|
|
{
|
2020-12-02 00:30:04 -05:00
|
|
|
if (is_superclock()) {
|
|
|
|
return timepos_t::from_superclock (val() - other.superclocks());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-12-02 00:30:04 -05:00
|
|
|
return timepos_t::from_ticks (val() - other.ticks());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
timepos_t
|
|
|
|
timepos_t::earlier (timecnt_t const & distance) const
|
|
|
|
{
|
2020-12-02 00:30:04 -05:00
|
|
|
if (is_superclock()) {
|
|
|
|
return timepos_t::from_superclock (val() - distance.superclocks());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
2020-12-02 00:30:04 -05:00
|
|
|
|
|
|
|
return timepos_t::from_ticks (val() - distance.ticks());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-11-16 20:50:45 -05:00
|
|
|
bool
|
|
|
|
timepos_t::expensive_lt (timepos_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
2020-12-02 00:30:04 -05:00
|
|
|
return val() < other.superclocks();
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
2020-12-02 00:30:04 -05:00
|
|
|
return ticks() < other.ticks ();
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::expensive_gt (timepos_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
2020-11-29 20:47:58 -05:00
|
|
|
return superclocks() > other.superclocks();
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return beats() > other.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::expensive_lte (timepos_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
2020-11-29 20:47:58 -05:00
|
|
|
return superclocks() <= other.superclocks();
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return beats() <= other.beats ();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::expensive_gte (timepos_t const & other) const
|
|
|
|
{
|
|
|
|
if (time_domain() == AudioTime) {
|
2020-11-29 20:47:58 -05:00
|
|
|
return superclocks() >= other.superclocks();
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return beats() >= other.beats ();
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
/* */
|
|
|
|
|
2020-09-29 19:53:06 -04:00
|
|
|
timepos_t &
|
|
|
|
timepos_t::shift_earlier (timepos_t const & d)
|
|
|
|
{
|
2020-12-02 00:30:04 -05:00
|
|
|
if (is_superclock()) {
|
|
|
|
v = build (false, val() - d.superclocks());
|
|
|
|
} else {
|
|
|
|
v = build (true, val() - d.ticks());
|
2020-09-29 19:53:06 -04:00
|
|
|
}
|
|
|
|
|
2020-12-02 00:30:04 -05:00
|
|
|
return *this;
|
2020-09-29 19:53:06 -04:00
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
timepos_t &
|
|
|
|
timepos_t::shift_earlier (timecnt_t const & d)
|
|
|
|
{
|
2020-12-02 00:30:04 -05:00
|
|
|
if (is_superclock()) {
|
|
|
|
v = build (false, val() - d.superclocks());
|
|
|
|
} else {
|
|
|
|
v = build (true, val() - d.ticks());
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-12-02 00:30:04 -05:00
|
|
|
return *this;
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-12-02 00:30:04 -05:00
|
|
|
timepos_t &
|
|
|
|
timepos_t::shift_earlier (Temporal::BBT_Offset const & offset)
|
|
|
|
{
|
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
|
|
|
|
|
|
|
if (is_superclock()) {
|
2021-03-25 12:24:05 -04:00
|
|
|
v = build (false, (tm->superclock_at (tm->bbt_walk (tm->bbt_at (*this), -offset))));
|
2020-12-02 00:30:04 -05:00
|
|
|
} else {
|
|
|
|
v = build (true, tm->bbtwalk_to_quarters (beats(), -offset).to_ticks());
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
/* */
|
|
|
|
|
2020-11-16 20:50:45 -05:00
|
|
|
timepos_t &
|
|
|
|
timepos_t::operator+= (Temporal::BBT_Offset const & offset)
|
|
|
|
{
|
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
|
|
|
if (is_beats()) {
|
2020-12-12 19:22:03 -05:00
|
|
|
v = build (true, tm->bbtwalk_to_quarters (beats(), offset).to_ticks());
|
2020-11-16 20:50:45 -05:00
|
|
|
} else {
|
2021-03-25 12:24:05 -04:00
|
|
|
v = build (false, tm->superclock_at (tm->bbt_walk (tm->bbt_at (*this), offset)));
|
2020-11-16 20:50:45 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
/* */
|
|
|
|
|
|
|
|
timepos_t
|
|
|
|
timepos_t::operator+(timecnt_t const & d) const
|
|
|
|
{
|
2021-10-04 19:24:07 -04:00
|
|
|
if (d.time_domain() == time_domain()) {
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return operator+ (timepos_t::from_superclock (d.superclocks()));
|
|
|
|
} else {
|
|
|
|
return operator+ (timepos_t::from_ticks (d.ticks()));
|
|
|
|
}
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2021-10-04 19:24:07 -04:00
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
|
|
|
|
|
|
|
timecnt_t dur_at_this = tm->convert_duration (d, *this, time_domain());
|
|
|
|
|
|
|
|
assert (dur_at_this.time_domain() == time_domain());
|
|
|
|
|
|
|
|
return operator+ (dur_at_this);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
timepos_t &
|
|
|
|
timepos_t::operator+=(timecnt_t const & d)
|
|
|
|
{
|
2021-10-04 19:24:07 -04:00
|
|
|
if (d.time_domain() == time_domain()) {
|
|
|
|
if (time_domain() == AudioTime) {
|
|
|
|
return operator+= (timepos_t::from_superclock (d.superclocks()));
|
|
|
|
} else {
|
|
|
|
return operator+= (timepos_t::from_ticks (d.ticks()));
|
|
|
|
}
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
2021-10-04 19:24:07 -04:00
|
|
|
|
|
|
|
TempoMap::SharedPtr tm (TempoMap::use());
|
|
|
|
|
|
|
|
timecnt_t dur_at_this = tm->convert_duration (d, *this, time_domain());
|
|
|
|
|
|
|
|
assert (dur_at_this.time_domain() == time_domain());
|
|
|
|
|
|
|
|
return operator+= (dur_at_this);
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2020-08-11 20:26:25 -04:00
|
|
|
/* */
|
|
|
|
|
|
|
|
timepos_t &
|
|
|
|
timepos_t::operator+=(timepos_t const & d)
|
|
|
|
{
|
|
|
|
if (d.is_beats() == is_beats()) {
|
|
|
|
|
2020-12-23 15:16:26 -05:00
|
|
|
/* same time domain, keep flag bit, add values */
|
|
|
|
|
2020-08-11 20:26:25 -04:00
|
|
|
v = build (flagged(), val() + d.val());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2020-12-23 15:16:26 -05:00
|
|
|
/* different time domain, return a value in the same domain as
|
|
|
|
* this one
|
|
|
|
*/
|
|
|
|
|
2020-08-11 20:26:25 -04:00
|
|
|
if (is_beats()) {
|
2020-12-23 15:16:26 -05:00
|
|
|
v = build (true, val() + d.ticks());
|
2020-08-11 20:26:25 -04:00
|
|
|
} else {
|
2020-12-23 15:16:26 -05:00
|
|
|
v = build (false, val() + d.superclocks());
|
2020-08-11 20:26:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
|
|
|
|
std::ostream&
|
|
|
|
std::operator<< (std::ostream & o, timepos_t const & tp)
|
|
|
|
{
|
2021-11-13 16:39:20 -05:00
|
|
|
return o << tp.str();
|
2020-08-04 15:52:28 -04:00
|
|
|
}
|
|
|
|
|
2021-02-03 14:12:28 -05:00
|
|
|
std::istream&
|
|
|
|
std::operator>> (std::istream & o, timepos_t & tp)
|
|
|
|
{
|
|
|
|
std::string str;
|
|
|
|
o >> str; /* should break on whitespace */
|
|
|
|
tp.string_to (str);
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
std::string
|
2021-11-13 16:39:20 -05:00
|
|
|
timepos_t::str () const
|
2020-08-04 15:52:28 -04:00
|
|
|
{
|
|
|
|
if (is_beats()) {
|
|
|
|
return string_compose ("b%1", val());
|
|
|
|
}
|
|
|
|
|
|
|
|
return string_compose ("a%1", val());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
timepos_t::string_to (std::string const & str)
|
|
|
|
{
|
|
|
|
using std::string;
|
|
|
|
using std::cerr;
|
|
|
|
using std::endl;
|
|
|
|
|
|
|
|
superclock_t s;
|
2020-12-28 19:51:07 -05:00
|
|
|
samplepos_t sm;
|
|
|
|
int64_t ticks;
|
2020-08-04 15:52:28 -04:00
|
|
|
Beats beats;
|
|
|
|
|
2021-11-22 12:28:13 -05:00
|
|
|
if (isdigit (str[0]) || (str[0] == '-' && str.length() > 1)) {
|
|
|
|
|
2020-08-04 15:52:28 -04:00
|
|
|
/* old school position format: we assume samples */
|
|
|
|
std::stringstream ss (str);
|
2020-12-28 19:51:07 -05:00
|
|
|
ss >> sm;
|
2021-03-24 01:55:43 -04:00
|
|
|
v = build (false, samples_to_superclock (sm, TEMPORAL_SAMPLE_RATE));
|
2021-07-22 20:04:15 -04:00
|
|
|
// cerr << "deserialized timepos from older " << str << " as " << *this << " with sm = " << sm << " and sr = " << TEMPORAL_SAMPLE_RATE << " s2sc " << endl;
|
2020-08-04 15:52:28 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::stringstream ss (str.substr (1));
|
|
|
|
|
|
|
|
switch (str[0]) {
|
|
|
|
case 'a':
|
|
|
|
ss >> s;
|
2020-12-28 19:51:07 -05:00
|
|
|
v = build (false, s);
|
2021-07-22 20:04:15 -04:00
|
|
|
// cerr << "deserialized timepos from " << str << " as " << *this << endl;
|
2020-08-04 15:52:28 -04:00
|
|
|
return true;
|
|
|
|
case 'b':
|
2020-12-28 19:51:07 -05:00
|
|
|
ss >> ticks;
|
|
|
|
v = build (true, ticks);
|
2021-07-22 20:04:15 -04:00
|
|
|
// cerr << "deserialized timepos from " << str << " as " << *this << endl;
|
2020-08-04 15:52:28 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::cerr << "Unknown timepos string representation \"" << str << "\"" << std::endl;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|