first pass at integrating nutempo v1 with new 64 bit superclock/ticks representation
This compiles libtemporal, but nothing more
This commit is contained in:
parent
e0b5b12129
commit
a80960468d
|
@ -1,39 +1,27 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
|
||||
*
|
||||
* 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.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
Copyright (C) 2002-2010 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
#include "temporal/bbt_time.h"
|
||||
|
||||
using namespace Timecode;
|
||||
|
||||
/* This defines the smallest division of a "beat".
|
||||
|
||||
The number is intended to have as many integer factors as possible so that
|
||||
1/Nth divisions are integer numbers of ticks.
|
||||
|
||||
1920 has many factors, though going up to 3840 gets a couple more.
|
||||
|
||||
This needs to match Temporal::Beats::PPQN
|
||||
*/
|
||||
|
||||
const double BBT_Time::ticks_per_beat = 1920.0;
|
||||
using namespace Temporal;
|
||||
|
||||
BBT_Offset::BBT_Offset (double dbeats)
|
||||
{
|
||||
|
@ -46,5 +34,53 @@ BBT_Offset::BBT_Offset (double dbeats)
|
|||
|
||||
bars = 0;
|
||||
beats = lrint (floor (dbeats));
|
||||
ticks = lrint (floor (BBT_Time::ticks_per_beat * fmod (dbeats, 1.0)));
|
||||
ticks = lrint (floor (Temporal::ticks_per_beat * fmod (dbeats, 1.0)));
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
std::operator<< (std::ostream& o, Temporal::BBT_Time const & bbt)
|
||||
{
|
||||
o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
|
||||
return o;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
std::operator<< (std::ostream& o, const Temporal::BBT_Offset& bbt)
|
||||
{
|
||||
o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
|
||||
return o;
|
||||
}
|
||||
|
||||
std::istream&
|
||||
std::operator>>(std::istream& i, Temporal::BBT_Offset& bbt)
|
||||
{
|
||||
int32_t B, b, t;
|
||||
char skip_pipe_char;
|
||||
|
||||
i >> B;
|
||||
i >> skip_pipe_char;
|
||||
i >> b;
|
||||
i >> skip_pipe_char;
|
||||
i >> t;
|
||||
|
||||
bbt = Temporal::BBT_Time (B, b, t);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
std::istream&
|
||||
std::operator>>(std::istream& i, Temporal::BBT_Time& bbt)
|
||||
{
|
||||
int32_t B, b, t;
|
||||
char skip_pipe_char;
|
||||
|
||||
i >> B;
|
||||
i >> skip_pipe_char;
|
||||
i >> b;
|
||||
i >> skip_pipe_char;
|
||||
i >> t;
|
||||
|
||||
bbt = Temporal::BBT_Time (B, b, t);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
|
|
@ -1,38 +1,51 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
|
||||
*
|
||||
* 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.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
Copyright (C) 2002-2010 Paul Davis
|
||||
|
||||
#ifndef __timecode_bbt_time_h__
|
||||
#define __timecode_bbt_time_h__
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser 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 Lesser General Public
|
||||
License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser 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_bbt_time_h__
|
||||
#define __libtemporal_bbt_time_h__
|
||||
|
||||
#include <ostream>
|
||||
#include <istream>
|
||||
#include <sstream>
|
||||
#include <stdint.h>
|
||||
#include <iomanip>
|
||||
#include <exception>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
|
||||
#include "pbd/failed_constructor.h"
|
||||
#include "pbd/string_convert.h"
|
||||
|
||||
#include "temporal/visibility.h"
|
||||
#include "temporal/types.h"
|
||||
|
||||
namespace Timecode {
|
||||
namespace Temporal {
|
||||
|
||||
struct IllegalBBTTimeException : public std::exception {
|
||||
virtual const char* what() const throw() { return "illegal BBT time (bars or beats were zero, or ticks was too large)"; }
|
||||
};
|
||||
|
||||
struct BBT_Offset;
|
||||
|
||||
/** Bar, Beat, Tick Time (i.e. Tempo-Based Time) */
|
||||
struct LIBTEMPORAL_API BBT_Time
|
||||
{
|
||||
static const double ticks_per_beat;
|
||||
|
||||
/* note that it is illegal for BBT_Time to have bars==0 or
|
||||
* beats==0. The "neutral" or "default" value is 1|1|0
|
||||
*/
|
||||
|
@ -41,12 +54,15 @@ struct LIBTEMPORAL_API BBT_Time
|
|||
int32_t beats;
|
||||
int32_t ticks;
|
||||
|
||||
struct IllegalBBTTimeException : public std::exception {
|
||||
virtual const char* what() const throw() { return "illegal BBT time (bars or beats were zero)"; }
|
||||
};
|
||||
bool is_bar() const { return beats == 1 && ticks == 0; }
|
||||
bool is_beat() const { return ticks == 0; }
|
||||
|
||||
BBT_Time () : bars (1), beats (1), ticks (0) {}
|
||||
BBT_Time (int32_t ba, uint32_t be, uint32_t t) : bars (ba), beats (be), ticks (t) { if (!bars || !beats) { throw IllegalBBTTimeException(); } }
|
||||
BBT_Time (int32_t ba, uint32_t be, uint32_t t) : bars (ba), beats (be), ticks (t) {
|
||||
if (!bars || !beats) {
|
||||
throw IllegalBBTTimeException();
|
||||
}
|
||||
}
|
||||
|
||||
bool operator< (const BBT_Time& other) const {
|
||||
return bars < other.bars ||
|
||||
|
@ -80,10 +96,17 @@ struct LIBTEMPORAL_API BBT_Time
|
|||
return bars != other.bars || beats != other.beats || ticks != other.ticks;
|
||||
}
|
||||
|
||||
/* it would be nice to provide operator+(BBT_Time const&) and
|
||||
* operator-(BBT_Time const&) but this math requires knowledge of the
|
||||
* meter (time signature) used to define 1 bar, and so cannot be
|
||||
* carried out with only two BBT_Time values.
|
||||
bool operator< (const BBT_Offset& other) const;
|
||||
bool operator<= (const BBT_Offset& other) const;
|
||||
bool operator> (const BBT_Offset& other) const;
|
||||
bool operator>= (const BBT_Offset& other) const;
|
||||
bool operator== (const BBT_Offset& other) const;
|
||||
bool operator!= (const BBT_Offset& other) const;
|
||||
|
||||
/* it would be nice to provide +,-,* and / operators for BBT_Time but
|
||||
* this math requires knowledge of the meter (time signature) used to
|
||||
* define 1 bar, and so cannot be carried out with only two BBT_Time
|
||||
* values.
|
||||
*/
|
||||
|
||||
BBT_Time round_to_beat () const { return ticks >= (ticks_per_beat/2) ? BBT_Time (bars, beats+1, 0) : BBT_Time (bars, beats, 0); }
|
||||
|
@ -91,8 +114,22 @@ struct LIBTEMPORAL_API BBT_Time
|
|||
BBT_Time round_up_to_beat () const { return ticks ? BBT_Time (bars, beats+1, 0) : *this; }
|
||||
|
||||
/* cannot implement round_to_bar() without knowing meter (time
|
||||
* signature) information.
|
||||
* signature) information, since it requires knowing how many beats
|
||||
* are in a bar, in order to decide if we are closer to the previous or
|
||||
* next bar time.
|
||||
*/
|
||||
|
||||
BBT_Time round_up_to_bar () const { return beats > 1 ? BBT_Time (bars+1, 1, 0) : BBT_Time (bars, 1, 0); }
|
||||
BBT_Time round_down_to_bar () const { return BBT_Time (bars, 1, 0); }
|
||||
BBT_Time next_bar () const { return (bars == -1) ? BBT_Time (1, 1, 0) : BBT_Time (bars+1, 1, 0); }
|
||||
BBT_Time prev_bar () const { return (bars == 1) ? BBT_Time (-1, 1, 0) : BBT_Time (bars-1, 1, 0); }
|
||||
|
||||
void print_padded (std::ostream& o) {
|
||||
o << std::setfill ('0') << std::right
|
||||
<< std::setw (3) << bars << "|"
|
||||
<< std::setw (2) << beats << "|"
|
||||
<< std::setw (4) << ticks;
|
||||
}
|
||||
};
|
||||
|
||||
struct LIBTEMPORAL_API BBT_Offset
|
||||
|
@ -110,33 +147,217 @@ struct LIBTEMPORAL_API BBT_Offset
|
|||
BBT_Offset (int32_t ba, uint32_t be, uint32_t t) : bars (ba), beats (be), ticks (t) {}
|
||||
BBT_Offset (BBT_Time const & bbt) : bars (bbt.bars), beats (bbt.beats), ticks (bbt.ticks) {}
|
||||
BBT_Offset (double beats);
|
||||
|
||||
/* unlike BBT_Time, we can define +,-,* and / operators for BBT_Offset
|
||||
* because there is no requirement that the result is "well-formed" or
|
||||
* reflect the structure of a tempo map. It is just as valid to talk
|
||||
* about an offset of 18 beats as an offset of 4 bars and 2 beats.
|
||||
*/
|
||||
|
||||
BBT_Offset operator+(const BBT_Offset& other) const {
|
||||
return BBT_Offset (bars+other.bars, beats+other.beats, ticks+other.ticks);
|
||||
}
|
||||
|
||||
BBT_Offset operator-() const {
|
||||
return BBT_Offset (-bars, -beats, -ticks);
|
||||
}
|
||||
|
||||
BBT_Offset operator-(const BBT_Offset& other) const {
|
||||
return BBT_Offset (bars-other.bars, beats-other.beats, ticks-other.ticks);
|
||||
}
|
||||
|
||||
BBT_Offset & operator+=(const BBT_Offset& other) {
|
||||
bars += other.bars;
|
||||
beats += other.beats;
|
||||
ticks += other.ticks;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BBT_Offset & operator-=(const BBT_Offset& other) {
|
||||
bars -= other.bars;
|
||||
beats -= other.beats;
|
||||
ticks -= other.ticks;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BBT_Offset & operator*=(int factor) {
|
||||
bars *= factor;
|
||||
beats *= factor;
|
||||
ticks *= factor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BBT_Offset & operator*=(double factor) {
|
||||
bars = lrint (bars * factor);
|
||||
beats = lrint (beats * factor);
|
||||
ticks = lrint (ticks * factor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BBT_Offset & operator/=(int factor) {
|
||||
bars /= factor;
|
||||
beats /= factor;
|
||||
ticks /= factor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BBT_Offset & operator/=(double factor) {
|
||||
bars = lrint (bars / factor);
|
||||
beats = lrint (beats / factor);
|
||||
ticks = lrint (ticks / factor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator< (const BBT_Offset& other) const {
|
||||
return bars < other.bars ||
|
||||
(bars == other.bars && beats < other.beats) ||
|
||||
(bars == other.bars && beats == other.beats && ticks < other.ticks);
|
||||
}
|
||||
|
||||
bool operator<= (const BBT_Offset& other) const {
|
||||
return bars < other.bars ||
|
||||
(bars <= other.bars && beats <= other.beats) ||
|
||||
(bars <= other.bars && beats <= other.beats && ticks <= other.ticks);
|
||||
}
|
||||
|
||||
bool operator> (const BBT_Offset& other) const {
|
||||
return bars > other.bars ||
|
||||
(bars == other.bars && beats > other.beats) ||
|
||||
(bars == other.bars && beats == other.beats && ticks > other.ticks);
|
||||
}
|
||||
|
||||
bool operator>= (const BBT_Offset& other) const {
|
||||
return bars > other.bars ||
|
||||
(bars >= other.bars && beats >= other.beats) ||
|
||||
(bars >= other.bars && beats >= other.beats && ticks >= other.ticks);
|
||||
}
|
||||
|
||||
bool operator== (const BBT_Offset& other) const {
|
||||
return bars == other.bars && beats == other.beats && ticks == other.ticks;
|
||||
}
|
||||
|
||||
bool operator!= (const BBT_Offset& other) const {
|
||||
return bars != other.bars || beats != other.beats || ticks != other.ticks;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
inline BBT_Offset LIBTEMPORAL_API bbt_delta (Temporal::BBT_Time const & a, Temporal::BBT_Time const & b) {
|
||||
return Temporal::BBT_Offset (a.bars - b.bars, a.beats - b.beats, a.ticks - b.ticks);
|
||||
}
|
||||
|
||||
inline std::ostream&
|
||||
operator<< (std::ostream& o, const Timecode::BBT_Time& bbt)
|
||||
inline bool
|
||||
BBT_Time::operator< (const BBT_Offset& other) const
|
||||
{
|
||||
o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
|
||||
return o;
|
||||
return bars < other.bars ||
|
||||
(bars == other.bars && beats < other.beats) ||
|
||||
(bars == other.bars && beats == other.beats && ticks < other.ticks);
|
||||
}
|
||||
|
||||
inline std::ostream&
|
||||
operator<< (std::ostream& o, const Timecode::BBT_Offset& bbt)
|
||||
inline bool
|
||||
BBT_Time::operator<= (const BBT_Offset& other) const
|
||||
{
|
||||
o << bbt.bars << '|' << bbt.beats << '|' << bbt.ticks;
|
||||
return o;
|
||||
return bars < other.bars ||
|
||||
(bars <= other.bars && beats <= other.beats) ||
|
||||
(bars <= other.bars && beats <= other.beats && ticks <= other.ticks);
|
||||
}
|
||||
|
||||
inline std::ostream&
|
||||
print_padded (std::ostream& o, const Timecode::BBT_Time& bbt)
|
||||
inline bool
|
||||
BBT_Time::operator> (const BBT_Offset& other) const
|
||||
{
|
||||
o << std::setfill ('0') << std::right
|
||||
<< std::setw (3) << bbt.bars << "|"
|
||||
<< std::setw (2) << bbt.beats << "|"
|
||||
<< std::setw (4) << bbt.ticks;
|
||||
|
||||
return o;
|
||||
return bars > other.bars ||
|
||||
(bars == other.bars && beats > other.beats) ||
|
||||
(bars == other.bars && beats == other.beats && ticks > other.ticks);
|
||||
}
|
||||
|
||||
#endif /* __timecode_bbt_time_h__ */
|
||||
inline bool
|
||||
BBT_Time::operator>= (const BBT_Offset& other) const
|
||||
{
|
||||
return bars > other.bars ||
|
||||
(bars >= other.bars && beats >= other.beats) ||
|
||||
(bars >= other.bars && beats >= other.beats && ticks >= other.ticks);
|
||||
}
|
||||
|
||||
inline bool
|
||||
BBT_Time::operator== (const BBT_Offset& other) const
|
||||
{
|
||||
return bars == other.bars && beats == other.beats && ticks == other.ticks;
|
||||
}
|
||||
|
||||
inline bool
|
||||
BBT_Time::operator!= (const BBT_Offset& other) const
|
||||
{
|
||||
return bars != other.bars || beats != other.beats || ticks != other.ticks;
|
||||
}
|
||||
|
||||
} /* end of namespace Temporal */
|
||||
|
||||
/* Putting these into std:: seems wrong, but g++ is unable to find them
|
||||
* otherwise
|
||||
*/
|
||||
|
||||
namespace std {
|
||||
|
||||
std::ostream& operator<< (std::ostream& o, Temporal::BBT_Time const & bbt);
|
||||
std::ostream& operator<< (std::ostream& o, Temporal::BBT_Offset const & bbt);
|
||||
std::istream& operator>> (std::istream& i, Temporal::BBT_Time& bbt);
|
||||
std::istream& operator>> (std::istream& i, Temporal::BBT_Offset& bbt);
|
||||
|
||||
template<>
|
||||
struct numeric_limits<Temporal::BBT_Time> {
|
||||
static Temporal::BBT_Time lowest() {
|
||||
return Temporal::BBT_Time(1, 1, 0);
|
||||
}
|
||||
static Temporal::BBT_Time min() {
|
||||
return Temporal::BBT_Time(1, 1, 0);
|
||||
}
|
||||
|
||||
static Temporal::BBT_Time max() {
|
||||
return Temporal::BBT_Time(std::numeric_limits<int32_t>::max(),
|
||||
std::numeric_limits<int32_t>::max(),
|
||||
std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct numeric_limits<Temporal::BBT_Offset> {
|
||||
static Temporal::BBT_Offset lowest() {
|
||||
return Temporal::BBT_Time(0, 0, 0);
|
||||
}
|
||||
|
||||
static Temporal::BBT_Offset min() {
|
||||
return Temporal::BBT_Time(0, 0, 0);
|
||||
}
|
||||
|
||||
static Temporal::BBT_Time max() {
|
||||
return Temporal::BBT_Time(std::numeric_limits<int32_t>::max(),
|
||||
std::numeric_limits<int32_t>::max(),
|
||||
std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} /* end of namespace std */
|
||||
|
||||
namespace PBD {
|
||||
|
||||
template<>
|
||||
inline bool to_string (Temporal::BBT_Time val, std::string & str)
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << val;
|
||||
str = ostr.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool string_to (std::string const & str, Temporal::BBT_Time & val)
|
||||
{
|
||||
std::istringstream istr (str);
|
||||
istr >> val;
|
||||
return (bool) istr;
|
||||
}
|
||||
|
||||
} /* end namespace PBD */
|
||||
|
||||
#endif /* __libtemporal_bbt_time_h__ */
|
||||
|
|
|
@ -135,6 +135,8 @@ public:
|
|||
return Beats(ticks / ppqn, (ticks % ppqn) * PPQN / ppqn);
|
||||
}
|
||||
|
||||
static int64_t make_ticks (Beats const & b) { return b.get_beats() * ticks_per_beat + b.get_ticks(); }
|
||||
|
||||
int64_t to_ticks() const { return (int64_t)_beats * PPQN + _ticks; }
|
||||
int64_t to_ticks(uint32_t ppqn) const { return (int64_t)_beats * ppqn + (_ticks * ppqn / PPQN); }
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright (C) 2018 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 __temporal_debug_h__
|
||||
#define __temporal_debug_h__
|
||||
|
||||
#include "temporal/visibility.h"
|
||||
#include "pbd/debug.h"
|
||||
|
||||
namespace PBD {
|
||||
namespace DEBUG {
|
||||
LIBTEMPORAL_API extern DebugBits TemporalDomainConvert;
|
||||
LIBTEMPORAL_API extern DebugBits TemporalMap;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __ardour_debug_h__ */
|
||||
|
|
@ -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) llround (bpm * (big_numerator / 60));
|
||||
_scpb = (superclock_t) llround ((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__ */
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2020 Paul Davis
|
||||
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
|
||||
|
@ -16,41 +16,130 @@
|
|||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __libtemporal_tempo_h__
|
||||
#define __libtemporal_tempo_h__
|
||||
#ifndef __temporal_tempo_h__
|
||||
#define __temporal_tempo_h__
|
||||
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "pbd/enumwriter.h"
|
||||
#include "pbd/integer_division.h"
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include <glibmm/threads.h>
|
||||
|
||||
#include "pbd/enum_convert.h"
|
||||
#include "pbd/signals.h"
|
||||
#include "pbd/statefuldestructible.h"
|
||||
|
||||
#include "temporal/beats.h"
|
||||
#include "temporal/types.h"
|
||||
#include "temporal/superclock.h"
|
||||
#include "temporal/visibility.h"
|
||||
#include "temporal/beats.h"
|
||||
#include "temporal/bbt_time.h"
|
||||
#include "temporal/superclock.h"
|
||||
#include "temporal/timeline.h"
|
||||
#include "temporal/types.h"
|
||||
|
||||
/************************* !!!! ATTENTION !!!! *************************
|
||||
/* A tempo map is built from 3 types of entities
|
||||
|
||||
NO FLOATING POINT ARITHMETIC IS ALLOWED IN THIS HEADER OR
|
||||
ANY OF THE OBJECTS DEFINED HERE.
|
||||
1) tempo markers
|
||||
2) meter (time signature) markers
|
||||
3) position markers
|
||||
|
||||
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.
|
||||
|
||||
************************************************************************/
|
||||
Beats increase monotonically throughout the tempo map (BBT may not).
|
||||
|
||||
The map has a single time domain at any time, and can only be using either
|
||||
AudioTime or BeatTime. BarTime is not legal as a map time domain.
|
||||
*/
|
||||
|
||||
namespace Temporal {
|
||||
|
||||
class TempoValue {
|
||||
class Meter;
|
||||
class TempoMap;
|
||||
|
||||
/* Conceptually, Point is similar to timepos_t. However, whereas timepos_t can
|
||||
* use the TempoMap to translate between time domains, Point cannot. Why not?
|
||||
* Because Point is foundational in building the tempo map, and we cannot
|
||||
* create a circular functional dependency between them. So a Point always has
|
||||
* its superclock and beat time defined and no translation between them is possible.
|
||||
*/
|
||||
|
||||
class LIBTEMPORAL_API Point {
|
||||
public:
|
||||
Point (TempoMap const & map, superclock_t sc, Beats const & b, BBT_Time const & bbt) : _sclock (sc), _quarters (b), _bbt (bbt), _map (&map) {}
|
||||
Point (TempoMap const & map, XMLNode const &);
|
||||
|
||||
virtual ~Point() {}
|
||||
|
||||
virtual void set (superclock_t sc, Beats const & b, BBT_Time const & bbt) {
|
||||
_sclock = sc;
|
||||
_quarters = b;
|
||||
_bbt = bbt;
|
||||
}
|
||||
|
||||
superclock_t sclock() const { return _sclock; }
|
||||
Beats const & beats() const { return _quarters; }
|
||||
BBT_Time const & bbt() const { return _bbt; }
|
||||
|
||||
#if 0
|
||||
samplepos_t sample() const;
|
||||
#endif
|
||||
timepos_t time() const;
|
||||
|
||||
struct sclock_comparator {
|
||||
bool operator() (Point const & a, Point const & b) const {
|
||||
return a.sclock() < b.sclock();
|
||||
}
|
||||
};
|
||||
|
||||
struct beat_comparator {
|
||||
bool operator() (Point const & a, Point const & b) const {
|
||||
return a.beats() < b.beats();
|
||||
}
|
||||
};
|
||||
|
||||
struct bbt_comparator {
|
||||
bool operator() (Point const & a, Point const & b) const {
|
||||
return a.bbt() < b.bbt();
|
||||
}
|
||||
};
|
||||
|
||||
/* all time members are supposed to be synced at all times, so we need
|
||||
test only one.
|
||||
*/
|
||||
inline bool operator== (Point const & other) const { return _sclock == other._sclock; }
|
||||
inline bool operator!= (Point const & other) const { return _sclock != other._sclock; }
|
||||
|
||||
TempoMap const & map() const { return *_map; }
|
||||
|
||||
boost::intrusive::list_member_hook<> _point_hook;
|
||||
|
||||
protected:
|
||||
superclock_t _sclock;
|
||||
Beats _quarters;
|
||||
BBT_Time _bbt;
|
||||
TempoMap const * _map;
|
||||
|
||||
void add_state (XMLNode &) const;
|
||||
|
||||
protected:
|
||||
friend class TempoMap;
|
||||
void map_reset_set_sclock_for_sr_change (superclock_t sc) { _sclock = sc; }
|
||||
};
|
||||
|
||||
class LIBTEMPORAL_API Rampable {
|
||||
protected:
|
||||
virtual ~Rampable() {}
|
||||
|
||||
private:
|
||||
friend class TempoMap;
|
||||
virtual bool set_ramped (bool yn) = 0;
|
||||
};
|
||||
|
||||
/** Tempo, the speed at which musical time progresses (BPM).
|
||||
*/
|
||||
|
||||
class LIBTEMPORAL_API Tempo : public Rampable {
|
||||
private:
|
||||
/* beats per minute * big_numerator => rational number expressing (possibly fractional) bpm as superbeats-per-minute
|
||||
*
|
||||
|
@ -60,78 +149,629 @@ class TempoValue {
|
|||
static const superclock_t big_numerator = superclock_ticks_per_second;
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
Ramped,
|
||||
Constant
|
||||
};
|
||||
|
||||
TempoValue (double bpm) : val (bpm) {
|
||||
static std::string xml_node_name;
|
||||
|
||||
/* 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);
|
||||
Tempo (XMLNode const &);
|
||||
|
||||
/**
|
||||
* @param npm Note Types per minute
|
||||
* @param note_type Note Type (default `4': quarter note)
|
||||
*/
|
||||
Tempo (double npm, int note_type = 4)
|
||||
: _npm (npm)
|
||||
, _enpm (npm)
|
||||
, _superclocks_per_note_type (double_npm_to_scpn (npm))
|
||||
, _end_superclocks_per_note_type (double_npm_to_scpn (npm))
|
||||
, _super_note_type_per_second (double_npm_to_snps (npm))
|
||||
, _end_super_note_type_per_second (double_npm_to_snps (npm))
|
||||
, _note_type (note_type)
|
||||
, _active (true)
|
||||
, _locked_to_meter (false)
|
||||
, _clamped (false)
|
||||
, _type (Tempo::Constant) {}
|
||||
|
||||
Tempo (double npm, double enpm, int note_type = 4)
|
||||
: _npm (npm)
|
||||
, _enpm (npm)
|
||||
, _superclocks_per_note_type (double_npm_to_scpn (npm))
|
||||
, _end_superclocks_per_note_type (double_npm_to_scpn (enpm))
|
||||
, _super_note_type_per_second (double_npm_to_snps (npm))
|
||||
, _end_super_note_type_per_second (double_npm_to_snps (enpm))
|
||||
, _note_type (note_type)
|
||||
, _active (true)
|
||||
, _locked_to_meter (false)
|
||||
, _clamped (false)
|
||||
, _type (npm != enpm ? Tempo::Ramped : Tempo::Constant) {}
|
||||
|
||||
/* these five methods should only be used to show and collect information to the user (for whom
|
||||
* bpm as a floating point number is the obvious representation)
|
||||
*/
|
||||
double note_types_per_minute () const { return (superclock_ticks_per_second * 60.0) / _superclocks_per_note_type; }
|
||||
double end_note_types_per_minute () const { return (superclock_ticks_per_second * 60.0) / _end_superclocks_per_note_type; }
|
||||
double quarter_notes_per_minute() const { return (superclock_ticks_per_second * 60.0 * 4.0) / (_note_type * _superclocks_per_note_type); }
|
||||
double samples_per_note_type(samplecnt_t sr) const { return superclock_to_samples (superclocks_per_note_type (), sr); }
|
||||
double samples_per_quarter_note(samplecnt_t sr) const { return superclock_to_samples (superclocks_per_quarter_note(), sr); }
|
||||
void set_note_types_per_minute (double npm) { _superclocks_per_note_type = double_npm_to_scpn (npm); }
|
||||
|
||||
int note_type () const { return _note_type; }
|
||||
|
||||
superclock_t superclocks_per_note_type () const {
|
||||
return _superclocks_per_note_type;
|
||||
}
|
||||
superclock_t superclocks_per_note_type (int note_type) const {
|
||||
return (_superclocks_per_note_type * _note_type) / note_type;
|
||||
}
|
||||
superclock_t superclocks_per_quarter_note () const {
|
||||
return superclocks_per_note_type (4);
|
||||
}
|
||||
superclock_t end_superclocks_per_note_type () const {
|
||||
return _end_superclocks_per_note_type;
|
||||
}
|
||||
superclock_t end_superclocks_per_note_type (int note_type) const {
|
||||
return (_end_superclocks_per_note_type * _note_type) / note_type;
|
||||
}
|
||||
superclock_t end_superclocks_per_quarter_note () const {
|
||||
return end_superclocks_per_note_type (4);
|
||||
}
|
||||
superclock_t superclocks_per_ppqn () const {
|
||||
return superclocks_per_quarter_note() / ticks_per_beat;
|
||||
}
|
||||
|
||||
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; }
|
||||
static void superbeats_to_beats_ticks (int64_t sb, int32_t& b, int32_t& t) {
|
||||
b = sb / big_numerator;
|
||||
uint64_t remain = sb - (b * big_numerator);
|
||||
t = int_div_round ((Temporal::ticks_per_beat * remain), big_numerator);
|
||||
}
|
||||
|
||||
bool active () const { return _active; }
|
||||
void set_active (bool yn) { _active = yn; }
|
||||
|
||||
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);
|
||||
bool locked_to_meter () const { return _locked_to_meter; }
|
||||
void set_locked_to_meter (bool yn) { _locked_to_meter = yn; }
|
||||
|
||||
/* convert superbeats to beats:ticks */
|
||||
bool clamped() const { return _clamped; }
|
||||
bool set_clamped (bool yn);
|
||||
|
||||
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);
|
||||
Type type() const { return _type; }
|
||||
|
||||
return Beats (b, t);
|
||||
bool ramped () const { return _type != Constant; }
|
||||
|
||||
XMLNode& get_state () const;
|
||||
int set_state (XMLNode const&, int version);
|
||||
|
||||
bool operator== (Tempo const & other) const {
|
||||
return _superclocks_per_note_type == other._superclocks_per_note_type &&
|
||||
_end_superclocks_per_note_type == other._end_superclocks_per_note_type &&
|
||||
_note_type == other._note_type &&
|
||||
_active == other._active &&
|
||||
_locked_to_meter == other._locked_to_meter &&
|
||||
_clamped == other._clamped &&
|
||||
_type == other._type;
|
||||
}
|
||||
|
||||
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));
|
||||
bool operator!= (Tempo const & other) const {
|
||||
return _superclocks_per_note_type != other._superclocks_per_note_type ||
|
||||
_end_superclocks_per_note_type != other._end_superclocks_per_note_type ||
|
||||
_note_type != other._note_type ||
|
||||
_active != other._active ||
|
||||
_locked_to_meter != other._locked_to_meter ||
|
||||
_clamped != other._clamped ||
|
||||
_type != other._type;
|
||||
}
|
||||
|
||||
Temporal::Beats seconds_as_beats (uint64_t num, uint64_t denom) const {
|
||||
return superclocks_as_beats ((num * superclock_ticks_per_second) / denom);
|
||||
}
|
||||
protected:
|
||||
double _npm;
|
||||
double _enpm;
|
||||
superclock_t _superclocks_per_note_type;
|
||||
superclock_t _end_superclocks_per_note_type;
|
||||
uint64_t _super_note_type_per_second;
|
||||
uint64_t _end_super_note_type_per_second;
|
||||
int8_t _note_type;
|
||||
bool _active;
|
||||
bool _locked_to_meter; /* XXX name has unclear meaning with nutempo */
|
||||
bool _clamped;
|
||||
Type _type;
|
||||
|
||||
double beats_as_float_seconds_avoid_me (Temporal::Beats const & b) const {
|
||||
return beats_as_superclocks (b) / (double) superclock_ticks_per_second;
|
||||
}
|
||||
static inline uint64_t double_npm_to_snps (double npm) { return (uint64_t) llround (npm * big_numerator / 60); }
|
||||
static inline superclock_t double_npm_to_scpn (double npm) { return (superclock_t) llround ((60./npm) * superclock_ticks_per_second); }
|
||||
|
||||
private:
|
||||
double val; /* as given to constructor */
|
||||
uint64_t _sbps; /* superbeats-per-second */
|
||||
superclock_t _scpb; /* superclocks-per-beat */
|
||||
bool set_ramped (bool yn);
|
||||
};
|
||||
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& os, const TempoValue& v)
|
||||
{
|
||||
os << v.actual_bpm_for_display_only ();
|
||||
return os;
|
||||
}
|
||||
/** Meter, or time signature (subdivisions per bar, and which note type is a single subdivision). */
|
||||
class LIBTEMPORAL_API Meter {
|
||||
public:
|
||||
|
||||
inline std::istream&
|
||||
operator>>(std::istream& istr, TempoValue& v)
|
||||
static std::string xml_node_name;
|
||||
|
||||
Meter (XMLNode const &);
|
||||
Meter (int8_t dpb, int8_t nv) : _note_value (nv), _divisions_per_bar (dpb) {}
|
||||
|
||||
int divisions_per_bar () const { return _divisions_per_bar; }
|
||||
int note_value() const { return _note_value; }
|
||||
|
||||
int32_t ticks_per_grid () const { return (4 * Beats::PPQN) / _note_value; }
|
||||
|
||||
inline bool operator==(const Meter& other) const { return _divisions_per_bar == other.divisions_per_bar() && _note_value == other.note_value(); }
|
||||
inline bool operator!=(const Meter& other) const { return _divisions_per_bar != other.divisions_per_bar() || _note_value != other.note_value(); }
|
||||
|
||||
Meter& operator=(Meter const & other) {
|
||||
if (&other != this) {
|
||||
_divisions_per_bar = other._divisions_per_bar;
|
||||
_note_value = other._note_value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
BBT_Time bbt_add (BBT_Time const & bbt, BBT_Offset const & add) const;
|
||||
BBT_Time bbt_subtract (BBT_Time const & bbt, BBT_Offset const & sub) const;
|
||||
BBT_Time round_to_bar (BBT_Time const &) const;
|
||||
BBT_Time round_down_to_bar (BBT_Time const &) const;
|
||||
BBT_Time round_up_to_bar (BBT_Time const &) const;
|
||||
BBT_Time round_up_to_beat (BBT_Time const &) const;
|
||||
BBT_Time round_to_beat (BBT_Time const &) const;
|
||||
Beats to_quarters (BBT_Offset const &) const;
|
||||
|
||||
XMLNode& get_state () const;
|
||||
int set_state (XMLNode const&, int version);
|
||||
|
||||
protected:
|
||||
/** The type of "note" that a division represents. For example, 4 is
|
||||
a quarter (crotchet) note, 8 is an eighth (quaver) note, etc.
|
||||
*/
|
||||
int8_t _note_value;
|
||||
/* how many of '_note_value' make up a bar or measure */
|
||||
int8_t _divisions_per_bar;
|
||||
};
|
||||
|
||||
/* A MeterPoint is literally just the combination of a Meter with a Point
|
||||
*/
|
||||
class LIBTEMPORAL_API MeterPoint : public Meter, public Point
|
||||
{
|
||||
#if 0
|
||||
uint16_t w;
|
||||
uint64_t f;
|
||||
char d; /* delimiter, whatever it is */
|
||||
istr >> w >> d >> f;
|
||||
v = TempoValue (w, f);
|
||||
public:
|
||||
MeterPoint (TempoMap const & map, Meter const & m, superclock_t sc, Beats const & b, BBT_Time const & bbt) : Meter (m), Point (map, sc, b, bbt) {}
|
||||
MeterPoint (TempoMap const & map, XMLNode const &);
|
||||
|
||||
Beats quarters_at (BBT_Time const & bbt) const;
|
||||
BBT_Time bbt_at (Beats const & beats) const;
|
||||
|
||||
bool operator== (MeterPoint const & other) const {
|
||||
return Meter::operator== (other) && Point::operator== (other);
|
||||
}
|
||||
bool operator!= (MeterPoint const & other) const {
|
||||
return Meter::operator!= (other) || Point::operator!= (other);
|
||||
}
|
||||
|
||||
boost::intrusive::list_member_hook<> _meter_hook;
|
||||
|
||||
XMLNode& get_state () const;
|
||||
};
|
||||
|
||||
/* A TempoPoint is a combination of a Tempo with a Point. However, if the temp
|
||||
* is ramped, then at some point we will need to compute the ramp coefficients
|
||||
* (c-per-quarter and c-per-superclock) and store them so that we can compute
|
||||
* time-at-quarter-note on demand.
|
||||
*/
|
||||
|
||||
class LIBTEMPORAL_API TempoPoint : public Tempo, public Point
|
||||
{
|
||||
public:
|
||||
TempoPoint (TempoMap const & map, Tempo const & t, superclock_t sc, Beats const & b, BBT_Time const & bbt) : Tempo (t), Point (map, sc, b, bbt), _omega (0.0) {}
|
||||
TempoPoint (Tempo const & t, Point const & p) : Tempo (t), Point (p), _omega (0) {}
|
||||
TempoPoint (TempoMap const & map, XMLNode const &);
|
||||
|
||||
/* just change the tempo component, without moving */
|
||||
TempoPoint& operator=(Tempo const & t) {
|
||||
*((Tempo*)this) = t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
superclock_t superclock_at (Beats const & qn) const;
|
||||
|
||||
void compute_omega (samplecnt_t sr, superclock_t end_superclocks_per_note_type, Beats const & duration);
|
||||
|
||||
bool actually_ramped () const { return Tempo::ramped() && (_omega != 0); }
|
||||
|
||||
Beats quarters_at (superclock_t sc) const;
|
||||
|
||||
superclock_t superclocks_per_note_type_at (timepos_t const &) const;
|
||||
#ifdef ALLOW_DOUBLE_TEMPO_MATH
|
||||
double note_types_per_minute_at (timepos_t const & pos) const {
|
||||
return Tempo::sc_to_double_npm (superclocks_per_note_type_at (pos));
|
||||
}
|
||||
#endif
|
||||
return istr;
|
||||
|
||||
XMLNode& get_state () const;
|
||||
int set_state (XMLNode const&, int version);
|
||||
|
||||
bool operator== (TempoPoint const & other) const {
|
||||
return Tempo::operator== (other) && Point::operator== (other);
|
||||
}
|
||||
bool operator!= (TempoPoint const & other) const {
|
||||
return Tempo::operator!= (other) || Point::operator!= (other);
|
||||
}
|
||||
|
||||
double omega() const { return _omega; }
|
||||
|
||||
boost::intrusive::list_member_hook<> _tempo_hook;
|
||||
|
||||
private:
|
||||
double _omega;
|
||||
};
|
||||
|
||||
/** Helper class to perform computations that require both Tempo and Meter
|
||||
at a given point in time.
|
||||
|
||||
It may seem nicer to make this IS-A TempoPoint and IS-A MeterPoint. Doing
|
||||
so runs into multiple inheritance of Point, plus the major semantic issue
|
||||
that pairing a tempo and a meter does in fact allow for two positions, not
|
||||
one. That means we have to provide accessors to the TempoPoint and
|
||||
MeterPoint and thus it may as well be HAS-A rather than IS-A.
|
||||
|
||||
This object should always be short lived. It holds references to a
|
||||
TempoPoint and a MeterPoint that are not lifetime-managed. It's just a
|
||||
convenience object, in essence, to avoid having to replicate the
|
||||
computation code that requires both tempo and meter information every place
|
||||
it is used.
|
||||
*/
|
||||
class LIBTEMPORAL_API TempoMetric {
|
||||
public:
|
||||
TempoMetric (TempoPoint & t, MeterPoint & m) : _tempo (&t), _meter (&m) {}
|
||||
~TempoMetric () {}
|
||||
|
||||
TempoPoint & tempo() const { return *_tempo; }
|
||||
MeterPoint & meter() const { return *_meter; }
|
||||
|
||||
/* even more convenient wrappers for individual aspects of a
|
||||
* TempoMetric (i.e. just tempo or just meter information required
|
||||
*/
|
||||
|
||||
superclock_t superclock_at (Beats const & qn) const { return _tempo->superclock_at (qn); }
|
||||
Beats quarters_at (superclock_t sc) const { return _tempo->quarters_at (sc); }
|
||||
Beats quarters_at (BBT_Time const & bbt) const { return _meter->quarters_at (bbt); }
|
||||
BBT_Time bbt_at (Beats const & beats) const { return _meter->bbt_at (beats); }
|
||||
|
||||
superclock_t superclocks_per_note_type () const { return _tempo->superclocks_per_note_type (); }
|
||||
superclock_t end_superclocks_per_note_type () const {return _tempo->end_superclocks_per_note_type (); }
|
||||
superclock_t superclocks_per_note_type (int note_type) const {return _tempo->superclocks_per_note_type (note_type); }
|
||||
superclock_t superclocks_per_quarter_note () const {return _tempo->superclocks_per_quarter_note (); }
|
||||
superclock_t superclocks_per_ppqn () const {return _tempo->superclocks_per_ppqn (); }
|
||||
|
||||
int note_type () const { return _tempo->note_type(); }
|
||||
int divisions_per_bar () const { return _meter->divisions_per_bar(); }
|
||||
int note_value() const { return _meter->note_value(); }
|
||||
BBT_Time bbt_add (BBT_Time const & bbt, BBT_Offset const & add) const { return _meter->bbt_add (bbt, add); }
|
||||
BBT_Time bbt_subtract (BBT_Time const & bbt, BBT_Offset const & sub) const { return _meter->bbt_subtract (bbt, sub); }
|
||||
BBT_Time round_to_bar (BBT_Time const & bbt) const { return _meter->round_to_bar (bbt); }
|
||||
Beats to_quarters (BBT_Offset const & bbo) const { return _meter->to_quarters (bbo); }
|
||||
|
||||
/* combination methods that require both tempo and meter information */
|
||||
|
||||
superclock_t superclocks_per_bar (samplecnt_t sr) const {
|
||||
return superclocks_per_grid (sr) * _meter->divisions_per_bar();
|
||||
}
|
||||
superclock_t superclocks_per_grid (samplecnt_t sr) const {
|
||||
return llrint (_tempo->superclocks_per_note_type() * ((double) _tempo->note_type() / _meter->note_value()));
|
||||
}
|
||||
|
||||
superclock_t superclocks_per_note_type_at_superclock (superclock_t sc) const {
|
||||
if (!_tempo->actually_ramped()) {
|
||||
return _tempo->superclocks_per_note_type ();
|
||||
}
|
||||
return _tempo->superclocks_per_note_type() * exp (-_tempo->omega() * (sc - _tempo->sclock()));
|
||||
}
|
||||
|
||||
BBT_Time bbt_at (superclock_t sc) const;
|
||||
superclock_t superclock_at (BBT_Time const &) const;
|
||||
|
||||
protected:
|
||||
TempoPoint* _tempo;
|
||||
MeterPoint* _meter;
|
||||
};
|
||||
|
||||
/* A music time point is a place where BBT time (BarTime) is reset from
|
||||
* whatever it would be when just inferring from the usual counting. Its
|
||||
* position is given by a Point that might use superclock or Beats, and the
|
||||
* Point's BBT time member is overwritten.
|
||||
*/
|
||||
class LIBTEMPORAL_API MusicTimePoint : public Point
|
||||
{
|
||||
public:
|
||||
MusicTimePoint (TempoMap const & map) : Point (map, 0, Beats(), BBT_Time()) {}
|
||||
MusicTimePoint (BBT_Time const & bbt_time, Point const & p) : Point (p) { _bbt = bbt_time; }
|
||||
MusicTimePoint (TempoMap const & map, XMLNode const &);
|
||||
|
||||
boost::intrusive::list_member_hook<> _bartime_hook;
|
||||
|
||||
XMLNode & get_state () const;
|
||||
};
|
||||
|
||||
/** Tempo Map - mapping of timecode to musical time.
|
||||
* convert audio-samples, sample-rate to Bar/Beat/Tick, Meter/Tempo
|
||||
*/
|
||||
|
||||
/* TempoMap concepts
|
||||
|
||||
we have several different ways of talking about time:
|
||||
|
||||
* PULSE : whole notes, just because. These are linearly related to any other
|
||||
note type, so if you know a number of pulses (whole notes), you
|
||||
know the corresponding number of any other note type (e.g. quarter
|
||||
notes).
|
||||
|
||||
* QUARTER NOTES : just what the name says. A lot of MIDI software and
|
||||
concepts assume that a "beat" is a quarter-note.
|
||||
|
||||
* BEAT : a fraction of a PULSE. Defined by the meter in effect, so requires
|
||||
meter (time signature) information to convert to/from PULSE or QUARTER NOTES.
|
||||
In a 5/8 time, a BEAT is 1/8th note. In a 4/4 time, a beat is quarter note.
|
||||
This means that measuring time in BEATS is potentially non-linear (if
|
||||
the time signature changes, there will be a different number of BEATS
|
||||
corresponding to a given time in any other unit).
|
||||
|
||||
* SUPERCLOCK : a very high resolution clock whose frequency
|
||||
has as factors all common sample rates and all common note
|
||||
type divisors. Related to MINUTES or SAMPLES only when a
|
||||
sample rate is known. Related to PULSE or QUARTER NOTES only
|
||||
when a tempo is known.
|
||||
|
||||
* MINUTES : wallclock time measurement. related to SAMPLES or SUPERCLOCK
|
||||
only when a sample rate is known.
|
||||
|
||||
|
||||
* SAMPLES : audio time measurement. Related to MINUTES or SUPERCLOCK only
|
||||
when a sample rate is known
|
||||
|
||||
* BBT : bars|beats|ticks ... linearly related to BEATS but with the added
|
||||
semantics of bars ("measures") added, in which beats are broken up
|
||||
into groups of bars ("measures"). Requires meter (time signature)
|
||||
information to compute to/from a given BEATS value. Contains no
|
||||
additional time information compared to BEATS, but does have
|
||||
additional semantic information.
|
||||
|
||||
Nick sez: not every note onset is on a tick
|
||||
Paul wonders: if it's 8 samples off, does it matter?
|
||||
Nick sez: it should not phase with existing audio
|
||||
|
||||
*/
|
||||
|
||||
class LIBTEMPORAL_API TempoMapPoint : public Point, public TempoMetric
|
||||
{
|
||||
public:
|
||||
TempoMapPoint (TempoMap & map, TempoMetric const & tm, superclock_t sc, Beats const & q, BBT_Time const & bbt)
|
||||
: Point (map, sc, q, bbt), TempoMetric (tm), _floating (false) {}
|
||||
~TempoMapPoint () {}
|
||||
|
||||
/* called by a GUI that is manipulating the position of this point */
|
||||
void start_float ();
|
||||
void end_float ();
|
||||
bool floating() const { return _floating; }
|
||||
|
||||
bool is_explicit_meter() const { return _meter->sclock() == sclock(); }
|
||||
bool is_explicit_tempo() const { return _tempo->sclock() == sclock(); }
|
||||
bool is_explicit_position() const { return false; }
|
||||
bool is_explicit () const { return is_explicit_meter() || is_explicit_tempo() || is_explicit_position(); }
|
||||
|
||||
private:
|
||||
bool _floating;
|
||||
};
|
||||
|
||||
typedef std::list<TempoMapPoint> TempoMapPoints;
|
||||
|
||||
class LIBTEMPORAL_API TempoMap : public PBD::StatefulDestructible
|
||||
{
|
||||
public:
|
||||
TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, samplecnt_t sr);
|
||||
~TempoMap();
|
||||
|
||||
void set_dirty (bool yn);
|
||||
|
||||
bool set_ramped (TempoPoint&, bool);
|
||||
|
||||
void set_sample_rate (samplecnt_t sr);
|
||||
samplecnt_t sample_rate() const { return _sample_rate; }
|
||||
|
||||
void insert_time (timepos_t const & pos, timecnt_t const & duration);
|
||||
bool remove_time (timepos_t const & pos, timecnt_t const & duration);
|
||||
|
||||
void change_tempo (TempoPoint&, Tempo const &);
|
||||
|
||||
MusicTimePoint & set_bartime (BBT_Time const &, timepos_t const &);
|
||||
void remove_bartime (MusicTimePoint const & tp);
|
||||
|
||||
TempoPoint & set_tempo (Tempo const &, BBT_Time const &);
|
||||
TempoPoint & set_tempo (Tempo const &, Beats const &);
|
||||
TempoPoint & set_tempo (Tempo const &, timepos_t const &);
|
||||
|
||||
void remove_tempo (TempoPoint const &);
|
||||
|
||||
MeterPoint & set_meter (Meter const &, BBT_Time const &);
|
||||
MeterPoint & set_meter (Meter const &, Beats const &);
|
||||
MeterPoint & set_meter (Meter const &, timepos_t const &);
|
||||
|
||||
void remove_meter (MeterPoint const &);
|
||||
|
||||
/* these are a convenience method that just wrap some odd semantics */
|
||||
bool move_tempo (TempoPoint const & point, timepos_t const & destination, bool push = false);
|
||||
bool move_meter (MeterPoint const & point, timepos_t const & destination, bool push = false);
|
||||
|
||||
bool can_remove (TempoPoint const &) const;
|
||||
bool can_remove (MeterPoint const &) const;
|
||||
|
||||
bool is_initial (TempoPoint const &) const;
|
||||
bool is_initial (MeterPoint const &) const;
|
||||
|
||||
uint32_t n_meters() const;
|
||||
uint32_t n_tempos() const;
|
||||
|
||||
Tempo const * next_tempo (Tempo const &) const;
|
||||
Meter const * next_meter (Meter const &) const;
|
||||
|
||||
TempoMetric metric_at (timepos_t const &) const;
|
||||
TempoMetric metric_at (superclock_t sc) const;
|
||||
TempoMetric metric_at (Beats const &b) const;
|
||||
TempoMetric metric_at (BBT_Time const & bbt) const;
|
||||
|
||||
TempoPoint const * previous_tempo (TempoPoint const &) const;
|
||||
|
||||
/* convenience function */
|
||||
BBT_Time round_to_bar (BBT_Time const & bbt) const {
|
||||
return metric_at (bbt).meter().round_to_bar (bbt);
|
||||
}
|
||||
|
||||
BBT_Time bbt_at (superclock_t sc) const;
|
||||
BBT_Time bbt_at (Beats const &) const;
|
||||
BBT_Time bbt_at (timepos_t const &) const;
|
||||
|
||||
Beats quarter_note_at (superclock_t sc) const;
|
||||
Beats quarter_note_at (BBT_Time const &) const;
|
||||
Beats quarter_note_at (timepos_t const &) const;
|
||||
|
||||
#if 0
|
||||
samplepos_t sample_at (Beats const &) const;
|
||||
samplepos_t sample_at (BBT_Time const &) const;
|
||||
samplepos_t sample_at (timepos_t const &) const;
|
||||
#endif
|
||||
superclock_t superclock_at (Beats const &) const;
|
||||
superclock_t superclock_at (BBT_Time const &) const;
|
||||
superclock_t superclock_at (timepos_t const &) const;
|
||||
|
||||
#if 0
|
||||
int update_music_times (int gen, samplepos_t, Beats & b, BBT_Time & bbt, bool force);
|
||||
int update_samples_and_beat_times (int gen, BBT_Time const & bbt, samplepos_t & pos, Beats & b, bool force);
|
||||
int update_samples_and_bbt_times (int gen, Beats const & b, samplepos_t & pos, BBT_Time & bbt, bool force);
|
||||
void update_one_domain_from_another (timepos_t const & src, void* dst, TimeDomain) const;
|
||||
#endif
|
||||
|
||||
/* ways to walk along the tempo map, measure distance between points,
|
||||
* etc.
|
||||
*/
|
||||
|
||||
Beats superclock_delta_as_quarters (superclock_t start, superclock_t distance) const;
|
||||
Beats scwalk_to_quarters (superclock_t pos, superclock_t distance) const;
|
||||
Beats scwalk_to_quarters (Beats const & pos, superclock_t distance) const;
|
||||
superclock_t superclock_plus_quarters_as_superclock (superclock_t start, Beats const & distance) const;
|
||||
superclock_t superclock_quarters_delta_as_superclock (superclock_t start, Beats const & distance) const;
|
||||
superclock_t superclock_plus_bbt (superclock_t pos, BBT_Time op) const;
|
||||
|
||||
superclock_t bbt_duration_at (superclock_t pos, const BBT_Time& bbt, int dir) const;
|
||||
Beats bbtwalk_to_quarters (Beats const & start, BBT_Offset const & distance) const;
|
||||
|
||||
superclock_t superclock_per_quarter_note_at (superclock_t) const;
|
||||
|
||||
Temporal::timecnt_t full_duration_at (Temporal::timepos_t const &, Temporal::timecnt_t const & duration, Temporal::TimeDomain domain) const;
|
||||
|
||||
BBT_Time bbt_walk (BBT_Time const &, BBT_Offset const &) const;
|
||||
|
||||
TimeDomain time_domain() const { return _time_domain; }
|
||||
void set_time_domain (TimeDomain td);
|
||||
|
||||
void get_grid (TempoMapPoints& points, superclock_t start, superclock_t end, uint32_t bar_mod);
|
||||
|
||||
typedef std::list<Point*> Metrics;
|
||||
|
||||
template<class T> void apply_with_metrics (T& obj, void (T::*method)(Metrics &)) {
|
||||
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
||||
Metrics metrics;
|
||||
for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
|
||||
metrics.push_back (&*t);
|
||||
}
|
||||
for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) {
|
||||
metrics.push_back (&*m);
|
||||
}
|
||||
(obj.*method)(metrics);
|
||||
}
|
||||
|
||||
struct EmptyTempoMapException : public std::exception {
|
||||
virtual const char* what() const throw() { return "TempoMap is empty"; }
|
||||
};
|
||||
|
||||
void dump (std::ostream&) const;
|
||||
|
||||
PBD::Signal0<void> Changed;
|
||||
|
||||
XMLNode& get_state();
|
||||
int set_state (XMLNode const&, int version);
|
||||
|
||||
typedef boost::intrusive::member_hook<TempoPoint,boost::intrusive::list_member_hook<>, &TempoPoint::_tempo_hook> TempoHookOption;
|
||||
typedef boost::intrusive::member_hook<MeterPoint,boost::intrusive::list_member_hook<>, &MeterPoint::_meter_hook> MeterHookOption;
|
||||
typedef boost::intrusive::member_hook<MusicTimePoint,boost::intrusive::list_member_hook<>, &MusicTimePoint::_bartime_hook> BarTimeHookOption;
|
||||
typedef boost::intrusive::member_hook<Point,boost::intrusive::list_member_hook<>, &Point::_point_hook> PointHookOption;
|
||||
|
||||
typedef boost::intrusive::list<TempoPoint,TempoHookOption> Tempos;
|
||||
typedef boost::intrusive::list<MeterPoint,MeterHookOption> Meters;
|
||||
typedef boost::intrusive::list<MusicTimePoint,BarTimeHookOption> MusicTimes;
|
||||
typedef boost::intrusive::list<Point,PointHookOption> Points;
|
||||
|
||||
private:
|
||||
Tempos _tempos;
|
||||
Meters _meters;
|
||||
MusicTimes _bartimes;
|
||||
Points _points;
|
||||
|
||||
samplecnt_t _sample_rate;
|
||||
mutable Glib::Threads::RWLock _lock;
|
||||
bool _dirty;
|
||||
int _generation;
|
||||
TimeDomain _time_domain;
|
||||
TempoPoint _initial_tempo;
|
||||
MeterPoint _initial_meter;
|
||||
MusicTimePoint _initial_music_time;
|
||||
|
||||
/* These return the TempoMetric in effect at the given time. If
|
||||
can_match is true, then the TempoMetric may refer to a Tempo or
|
||||
Meter at the given time. If can_match is false, the TempoMetric will
|
||||
only refer to the Tempo or Metric preceding the given time.
|
||||
*/
|
||||
TempoMetric metric_at_locked (superclock_t, bool can_match = true) const;
|
||||
TempoMetric metric_at_locked (Beats const &, bool can_match = true) const;
|
||||
TempoMetric metric_at_locked (BBT_Time const &, bool can_match = true) const;
|
||||
|
||||
int set_tempos_from_state (XMLNode const &);
|
||||
int set_meters_from_state (XMLNode const &);
|
||||
int set_music_times_from_state (XMLNode const &);
|
||||
|
||||
TempoPoint & set_tempo (Tempo const &, superclock_t);
|
||||
MeterPoint & set_meter (Meter const &, superclock_t);
|
||||
|
||||
void dump_locked (std::ostream&) const;
|
||||
|
||||
TempoPoint* add_tempo (TempoPoint &);
|
||||
MeterPoint* add_meter (MeterPoint &);
|
||||
MusicTimePoint* add_or_replace_bartime (MusicTimePoint &);
|
||||
|
||||
void add_point (Point &);
|
||||
|
||||
void reset_starting_at (superclock_t);
|
||||
void reset_starting_at (Beats const &);
|
||||
};
|
||||
|
||||
} /* end of namespace Temporal */
|
||||
|
||||
#ifdef COMPILER_MSVC
|
||||
#pragma warning(disable:4101)
|
||||
#endif
|
||||
|
||||
namespace PBD {
|
||||
DEFINE_ENUM_CONVERT(Temporal::Tempo::Type);
|
||||
DEFINE_ENUM_CONVERT(Temporal::TimeDomain);
|
||||
} /* namespace PBD */
|
||||
|
||||
|
||||
namespace std {
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::TempoMapPoint const &);
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::Tempo const &);
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::Meter const &);
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::Point const &);
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::TempoPoint const &);
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::MeterPoint const &);
|
||||
std::ostream& operator<<(std::ostream& str, Temporal::TempoMetric const &);
|
||||
}
|
||||
|
||||
} /* namespace Temporal */
|
||||
|
||||
#endif /* __libtemporal_tempo_h__ */
|
||||
#endif /* __temporal_tempo_h__ */
|
||||
|
|
|
@ -24,7 +24,7 @@ APPNAME = 'temporal'
|
|||
VERSION = TEMPORAL_VERSION
|
||||
I18N_PACKAGE = 'libtemporal'
|
||||
|
||||
temporal_sources = [ 'time.cc', 'bbt_time.cc' ]
|
||||
temporal_sources = [ 'time.cc', 'bbt_time.cc', 'tempo.cc' ]
|
||||
|
||||
def options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
@ -50,7 +50,9 @@ def build(bld):
|
|||
obj.target = 'temporal'
|
||||
obj.vnum = TEMPORAL_LIB_VERSION
|
||||
obj.install_path = bld.env['LIBDIR']
|
||||
obj.defines += [ 'PACKAGE="' + I18N_PACKAGE + '"' ]
|
||||
obj.defines += [ 'PACKAGE="' + I18N_PACKAGE + '"' ]
|
||||
obj.uselib = [ 'GLIBMM', 'XML' ]
|
||||
obj.use = [ 'libpbd' ]
|
||||
|
||||
def shutdown():
|
||||
autowaf.shutdown()
|
||||
|
|
Loading…
Reference in New Issue