13
0
livetrax/libs/temporal/tempo.cc
Paul Davis da175cc2e8 temporal: remove TempoMap::insert_time() and use shift() instead
The former was incorrectly implemented, and the latter has already been tested more
in real life.

We should likely remove ::remove_time also and use shift() there too, but that
requires testing negative shifts more broadly.
2023-11-19 12:14:10 -07:00

5072 lines
130 KiB
C++

/*
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 <algorithm>
#include <cmath>
#include <vector>
#include <inttypes.h>
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/enumwriter.h"
#include "pbd/error.h"
#include "pbd/integer_division.h"
#include "pbd/failed_constructor.h"
#include "pbd/stacktrace.h"
#include "pbd/string_convert.h"
#include "temporal/debug.h"
#include "temporal/tempo.h"
#include "temporal/types_convert.h"
#include "pbd/i18n.h"
using namespace PBD;
using namespace Temporal;
using std::cerr;
using std::cout;
using std::endl;
using Temporal::superclock_t;
std::string Tempo::xml_node_name = X_("Tempo");
std::string Meter::xml_node_name = X_("Meter");
SerializedRCUManager<TempoMap> TempoMap::_map_mgr (0);
thread_local TempoMap::SharedPtr TempoMap::_tempo_map_p;
PBD::Signal0<void> TempoMap::MapChanged;
#ifndef NDEBUG
#define TEMPO_MAP_ASSERT(expr) TempoMap::map_assert(expr, #expr, __FILE__, __LINE__)
#else
#define TEMPO_MAP_ASSERT(expr)
#endif
void
Point::add_state (XMLNode & node) const
{
node.set_property (X_("sclock"), _sclock);
node.set_property (X_("quarters"), _quarters);
node.set_property (X_("bbt"), _bbt);
}
Point::Point (TempoMap const & map, XMLNode const & node)
: MapOwned (map)
{
if (!node.get_property (X_("sclock"), _sclock)) {
throw failed_constructor();
}
if (!node.get_property (X_("quarters"), _quarters)) {
throw failed_constructor();
}
if (!node.get_property (X_("bbt"), _bbt)) {
throw failed_constructor();
}
}
#if 0
samplepos_t
Point::sample() const
{
return superclock_to_samples (_sclock, _map->sample_rate());
}
#endif
Tempo::Tempo (XMLNode const & node)
{
TEMPO_MAP_ASSERT (node.name() == xml_node_name);
node.get_property (X_("npm"), _npm);
node.get_property (X_("enpm"), _enpm);
_superclocks_per_note_type = double_npm_to_scpn (_npm);
_end_superclocks_per_note_type = double_npm_to_scpn (_enpm);
if (!node.get_property (X_("note-type"), _note_type)) {
throw failed_constructor ();
}
if (!node.get_property (X_("locked-to-meter"), _locked_to_meter)) {
_locked_to_meter = true;
}
/* older versions used "clamped" as the property name here */
if (!node.get_property (X_("continuing"), _continuing) && !node.get_property (X_("clamped"), _continuing)) {
_continuing = false;
}
}
void
Tempo::set_note_types_per_minute (double npm)
{
_npm = npm;
_superclocks_per_note_type = double_npm_to_scpn (_npm);
}
void
Tempo::set_end_npm (double npm)
{
_enpm = npm;
_end_superclocks_per_note_type = double_npm_to_scpn (_enpm);
}
void
Tempo::set_continuing (bool yn)
{
_continuing = yn;
}
XMLNode&
Tempo::get_state () const
{
XMLNode* node = new XMLNode (xml_node_name);
node->set_property (X_("npm"), note_types_per_minute());
node->set_property (X_("enpm"), end_note_types_per_minute());
node->set_property (X_("note-type"), note_type());
node->set_property (X_("type"), type());
node->set_property (X_("locked-to-meter"), _locked_to_meter);
node->set_property (X_("continuing"), _continuing);
/* We don't have an _active property any more, but earlier versions of
Ardour will crash during session loading if this property is not
provided. For the 7.5 - 8.0 transition, there was theoretically no
file format change, so leave this in place till at least the next
format version change.
*/
node->set_property (X_("active"), true);
return *node;
}
int
Tempo::set_state (XMLNode const & node, int /*version*/)
{
if (node.name() != xml_node_name) {
return -1;
}
node.get_property (X_("npm"), _npm);
node.get_property (X_("enpm"), _enpm);
_superclocks_per_note_type = double_npm_to_scpn (_npm);
_end_superclocks_per_note_type = double_npm_to_scpn (_enpm);
node.get_property (X_("note-type"), _note_type);
if (!node.get_property (X_("locked-to-meter"), _locked_to_meter)) {
_locked_to_meter = true;
}
/* older versions used "clamped" as the property name here */
if (!node.get_property (X_("continuing"), _continuing) && !node.get_property (X_("continuing"), _continuing)) {
_continuing = false;
}
return 0;
}
Meter::Meter (XMLNode const & node)
{
TEMPO_MAP_ASSERT (node.name() == xml_node_name);
if (!node.get_property (X_("note-value"), _note_value)) {
throw failed_constructor ();
}
if (!node.get_property (X_("divisions-per-bar"), _divisions_per_bar)) {
throw failed_constructor ();
}
}
XMLNode&
Meter::get_state () const
{
XMLNode* node = new XMLNode (xml_node_name);
node->set_property (X_("note-value"), note_value());
node->set_property (X_("divisions-per-bar"), divisions_per_bar());
return *node;
}
int
Meter::set_state (XMLNode const & node, int /* version */)
{
if (node.name() != xml_node_name) {
return -1;
}
node.get_property (X_("note-value"), _note_value);
node.get_property (X_("divisions-per-bar"), _divisions_per_bar);
return 0;
}
Temporal::BBT_Time
Meter::bbt_add (Temporal::BBT_Time const & bbt, Temporal::BBT_Offset const & add) const
{
int32_t bars = bbt.bars;
int32_t beats = bbt.beats;
int32_t ticks = bbt.ticks;
if ((bars ^ add.bars) < 0) {
/* signed-ness varies */
if (abs(add.bars) >= abs(bars)) {
/* addition will change which side of "zero" the answer is on;
adjust bbt.bars towards zero to deal with "unusual" BBT math
*/
if (bars < 0) {
bars++;
} else {
bars--;
}
}
}
if ((beats ^ add.beats) < 0) {
/* signed-ness varies */
if (abs (add.beats) >= abs (beats)) {
/* adjust bbt.beats towards zero to deal with "unusual" BBT math */
if (beats < 0) {
beats++;
} else {
beats--;
}
}
}
Temporal::BBT_Offset r (bars + add.bars, beats + add.beats, ticks + add.ticks);
/* ticks-per-bar-division; PPQN is ticks-per-quarter note */
const int32_t tpg = ticks_per_grid ();
if (r.ticks >= tpg) {
/* ticks per bar */
const int32_t tpB = tpg * _divisions_per_bar;
if (r.ticks >= tpB) {
r.bars += r.ticks / tpB;
r.ticks %= tpB;
}
if (r.ticks >= tpg) {
r.beats += r.ticks / tpg;
r.ticks %= tpg;
}
}
if (r.beats > _divisions_per_bar) {
/* adjust to zero-based math, since that's what C++ operators expect */
r.beats -= 1;
r.bars += r.beats / _divisions_per_bar;
r.beats %= _divisions_per_bar;
/* adjust back */
r.beats += 1;
}
if (r.bars == 0) {
r.bars = 1;
}
return Temporal::BBT_Time (r.bars, r.beats, r.ticks);
}
Temporal::BBT_Time
Meter::bbt_subtract (Temporal::BBT_Time const & bbt, Temporal::BBT_Offset const & sub) const
{
int32_t bars = bbt.bars;
int32_t beats = bbt.beats;
int32_t ticks = bbt.ticks;
if ((bars ^ sub.bars) < 0) {
/* signed-ness varies */
if (abs (sub.bars) >= abs (bars)) {
/* adjust bbt.bars towards zero to deal with "unusual" BBT math */
if (bars < 0) {
bars++;
} else {
bars--;
}
}
}
if ((beats ^ sub.beats) < 0) {
/* signed-ness varies */
if (abs (sub.beats) >= abs (beats)) {
/* adjust bbt.beats towards zero to deal with "unusual" BBT math */
if (beats < 0) {
beats++;
} else {
beats--;
}
}
}
Temporal::BBT_Offset r (bars - sub.bars, beats - sub.beats, ticks - sub.ticks);
/* ticks-per-bar-division; PPQN is ticks-per-quarter note */
const int32_t tpg = ticks_per_grid ();
if (r.ticks < 0) {
r.beats += floor ((double) r.ticks / tpg);
r.ticks = tpg + (r.ticks % Temporal::Beats::PPQN);
}
if (r.beats <= 0) {
r.bars += floor ((r.beats - 1.0) / _divisions_per_bar);
r.beats = _divisions_per_bar + (r.beats % _divisions_per_bar);
}
if (r.bars <= 0) {
r.bars -= 1;
}
return Temporal::BBT_Time (r.bars, r.beats, r.ticks);
}
Temporal::BBT_Time
Meter::round_to_bar (Temporal::BBT_Time const & bbt) const
{
Beats b (bbt.beats, bbt.ticks);
Beats half (Beats::ticks (Beats::PPQN + ((_divisions_per_bar * Beats::PPQN / 2))));
if (b >= half) {
return BBT_Time (bbt.bars+1, 1, 0);
}
return BBT_Time (bbt.bars, 1, 0);
}
Temporal::BBT_Time
Meter::round_up_to_beat_div (Temporal::BBT_Time const & bbt, int beat_div) const
{
Temporal::BBT_Time b = bbt.round_up_to_beat_div (beat_div);
if (b.beats > _divisions_per_bar) {
b.bars++;
b.beats = 1;
}
return b;
}
Temporal::BBT_Time
Meter::round_to_beat (Temporal::BBT_Time const & bbt) const
{
Temporal::BBT_Time b = bbt.round_to_beat ();
if (b.beats > _divisions_per_bar) {
b.bars++;
b.beats = 1;
}
return b;
}
Temporal::Beats
Meter::to_quarters (Temporal::BBT_Offset const & offset) const
{
int64_t ticks = 0;
ticks += (Beats::PPQN * offset.bars * _divisions_per_bar * 4) / _note_value;
ticks += (Beats::PPQN * offset.beats * 4) / _note_value;
/* "parts per bar division" */
const int tpg = ticks_per_grid ();
if (offset.ticks > tpg) {
ticks += Beats::PPQN * offset.ticks / tpg;
ticks += offset.ticks % tpg;
} else {
ticks += offset.ticks;
}
return Beats (ticks/Beats::PPQN, ticks%Beats::PPQN);
}
int
TempoPoint::set_state (XMLNode const & node, int version)
{
int ret;
if ((ret = Tempo::set_state (node, version)) == 0) {
if (node.get_property (X_("omega"), _omega)) {
if (node.get_property (X_("omega"), _omega)) {
/* Older versions only defined a single omega value */
/* ???? */
}
}
}
return ret;
}
XMLNode&
TempoPoint::get_state () const
{
XMLNode& base (Tempo::get_state());
Point::add_state (base);
base.set_property (X_("omega"), _omega);
return base;
}
TempoPoint::TempoPoint (TempoMap const & map, XMLNode const & node)
: Point (map, node)
, Tempo (node)
, _omega (0.)
{
if (node.get_property (X_("omega"), _omega)) {
/* Older versions only defined a single omega value */
if (node.get_property (X_("omega"), _omega)) {
/* ???? */
}
}
}
void
TempoPoint::set_omega (double ob)
{
_omega = ob;
}
/* To understand the math(s) behind ramping, see the file doc/tempo.{pdf,tex}
*/
void
TempoPoint::compute_omega_from_next_tempo (TempoPoint const & next)
{
compute_omega_from_distance_and_next_tempo (next.beats() - beats(), next);
}
void
TempoPoint::compute_omega_from_distance_and_next_tempo (Beats const & quarter_duration, TempoPoint const & next)
{
superclock_t end_scpqn;
if (!_continuing) {
/* tempo is defined by our own start and end */
end_scpqn = end_superclocks_per_quarter_note();
} else {
/* tempo is defined by our own start the start of the next tempo */
end_scpqn = next.superclocks_per_quarter_note ();
}
if (superclocks_per_quarter_note () == end_scpqn) {
_omega = 0.0;
return;
}
compute_omega_from_quarter_duration (quarter_duration, end_scpqn);
}
void
TempoPoint::compute_omega_from_quarter_duration (Beats const & quarter_duration, superclock_t end_scpqn)
{
const double old = _omega;
if (!std::isfinite (_omega = ((1.0/end_scpqn) - (1.0/superclocks_per_quarter_note())) / DoubleableBeats (quarter_duration).to_double())) {
DEBUG_TRACE (DEBUG::TemporalMap, "quarter-computed omega out of bounds\n");
_omega = old;
}
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("quarter-computed omega from qtr duration = %1 dur was %2 start speed %3 end speed [%4]\n", _omega, quarter_duration.str(), superclocks_per_quarter_note(), end_scpqn));
}
superclock_t
TempoPoint::superclock_at (Temporal::Beats const & qn) const
{
if (qn == _quarters) {
return _sclock;
}
if (qn < Beats()) {
/* negative */
TEMPO_MAP_ASSERT (_quarters == Beats());
} else {
/* positive */
TEMPO_MAP_ASSERT (qn >= _quarters);
}
if (!actually_ramped()) {
/* not ramped, use linear */
const Beats delta = qn - _quarters;
const superclock_t spqn = superclocks_per_quarter_note ();
return _sclock + (spqn * delta.get_beats()) + muldiv_round (spqn, delta.get_ticks(), superclock_t (Temporal::ticks_per_beat));
}
superclock_t r;
const double log_expr = superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double();
// std::cerr << "logexpr " << log_expr << " from " << superclocks_per_quarter_note() << " * " << _omega << " * " << (qn - _quarters) << std::endl;
if (log_expr < -1) {
r = _sclock + llrint (log (-log_expr - 1.0) / -_omega);
if (r < 0) {
std::cerr << "CASE 1: " << *this << endl << " scpqn = " << superclocks_per_quarter_note() << std::endl;
std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log (" << log_expr << ") "
<< log (-log_expr - 1.0)
<< " - omega = " << -_omega
<< " => "
<< r << std::endl;
abort ();
}
} else {
r = _sclock + llrint (log1p (log_expr) / _omega);
// std::cerr << "r = " << _sclock << " + " << log1p (log_expr) / _omega << " => " << r << std::endl;
if (r < 0) {
std::cerr << "CASE 2: scpqn = " << superclocks_per_quarter_note() << std::endl;
std::cerr << " for " << qn << " @ " << _quarters << " | " << _sclock << " + log1p (" << superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double() << " = "
<< log1p (superclocks_per_quarter_note() * _omega * DoubleableBeats (qn - _quarters).to_double())
<< " => "
<< r << std::endl;
_map->dump (std::cerr);
abort ();
}
}
return r;
}
superclock_t
TempoPoint::superclocks_per_note_type_at (timepos_t const &pos) const
{
if (!actually_ramped()) {
return _superclocks_per_note_type;
}
return _superclocks_per_note_type * exp (-_omega * (pos.superclocks() - sclock()));
}
Temporal::Beats
TempoPoint::quarters_at_superclock (superclock_t sc) const
{
/* catch a special case. The maximum superclock_t value cannot be
converted into a 64 bit tick value for common tempos.
Obviously, values less than this can also cause overflow, but are
unlikely to be encountered.
A longer term/big picture solution for this is likely required in
order to deal with longer sessions. Still, even at 300bpm, a 32 bit
integer should cover 165 days. The problem is that a 62 bit (int62_t)
superclock counter can cover 105064 days, so the theoretical
potential for errors here is real.
*/
if (sc >= int62_t::max) {
return std::numeric_limits<Beats>::max();
}
if (!actually_ramped()) {
/* The simple expression of the math we're computing here is
* (if we could use floating point):
*
* double sc_delta = sc - _sclock; // how far in superclocks from this point's superclock position?
* double seconds = sc_delta / superclock_ticks_per_second; // how long is that in seconds?
* double note_types_per_second = npm / 60.; // how many note types in 1 second ?
* double quarter_delta = note_types_per_second * (4. / * _note_type); // how many quarters is that?
* return _quarters + Beats (floor (quarter_delta), fmod (quarter_delta, 1.)); // add to this point's position in quarters
*
* But we can't use doubles, so this gets quite a bit more complex.
*/
// TEMPO_MAP_ASSERT (sc >= _sclock);
superclock_t sc_delta = sc - _sclock;
/* convert sc into superbeats, given that sc represents some number of seconds */
const superclock_t whole_seconds = sc_delta / superclock_ticks_per_second();
const superclock_t remainder = sc_delta - (whole_seconds * superclock_ticks_per_second());
/* big number to allow most (fractional) BPMs to be represented as an integer "super note type per second"
*
* 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.
*/
const superclock_t big_numerator = 508032000; // 2^10 * 3^4 * 5^3 * 7^2
uint64_t _super_note_type_per_second = (uint64_t) llround (_npm * big_numerator / 60);
const int64_t supernotes = ((_super_note_type_per_second) * whole_seconds) + muldiv_round (superclock_t (_super_note_type_per_second), remainder, superclock_ticks_per_second());
const int64_t superbeats = muldiv_round (supernotes, 4, (superclock_t) _note_type);
/* convert superbeats to beats:ticks */
int32_t b = superbeats / big_numerator;
int64_t remain = superbeats - (b * big_numerator);
int32_t t = PBD::muldiv_round (Temporal::ticks_per_beat, remain, big_numerator);
const Beats ret = _quarters + Beats (b, t);
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("%8 => \nsc %1 delta %9 = %2 secs rem = %3 rem snotes %4 sbeats = %5 => %6 : %7 + %10 = %11\n", sc, whole_seconds, remainder, supernotes, superbeats, b , t, *this, sc_delta, _quarters, ret));
/* positive superclock can never generate negative beats unless
* it is too large. If that happens, handle it the same way as
* the opening special case in this method.
*/
if (sc >= 0 && ret < Beats()) {
return std::numeric_limits<Beats>::max();
}
return ret;
}
const double b = (exp (_omega * (sc - _sclock)) - 1) / (superclocks_per_quarter_note() * _omega);
return _quarters + Beats::from_double (b);
}
MeterPoint::MeterPoint (TempoMap const & map, XMLNode const & node)
: Point (map, node)
, Meter (node)
{
}
/* Given a time in BBT_Time, compute the equivalent Beat Time.
*
* Computation assumes that the Meter is in effect at the time specified as
* BBT_Time (i.e. there is no other MeterPoint between this one and the specified
* time.
*/
Temporal::Beats
MeterPoint::quarters_at (Temporal::BBT_Time const & bbt) const
{
Temporal::BBT_Offset offset = bbt_delta (bbt, _bbt);
return _quarters + to_quarters (offset);
}
/* Given a time in Beats, compute the equivalent BBT Time.
*
* Computation assumes that the Meter is in effect at the time specified in
* Beats (i.e. there is no other MeterPoint between this one and the specified
* time.
*/
Temporal::BBT_Time
MeterPoint::bbt_at (Temporal::Beats const & qn) const
{
return bbt_add (_bbt, Temporal::BBT_Offset (0, 0, (qn - _quarters).to_ticks()));
}
XMLNode&
MeterPoint::get_state () const
{
XMLNode& base (Meter::get_state());
Point::add_state (base);
return base;
}
TempoMetric::TempoMetric (TempoPoint const & t, MeterPoint const & m)
: _tempo (&t)
, _meter (&m)
{
_reftime = _tempo->map().reftime (t, m);
}
superclock_t
TempoMap::reftime (TempoPoint const & t, MeterPoint const & m) const
{
Points::const_iterator pi;
if (m.sclock() < t.sclock()) {
pi = _points.s_iterator_to (*(static_cast<const Point*> (&m)));
} else {
pi = _points.s_iterator_to (*(static_cast<const Point*> (&t)));
}
/* Walk backwards through points to find a BBT markers, or the start */
while (pi != _points.begin()) {
if (dynamic_cast<const MusicTimePoint*> (&*pi)) {
break;
}
--pi;
}
return pi->sclock();
}
Temporal::BBT_Argument
TempoMetric::bbt_at (timepos_t const & pos) const
{
if (pos.is_beats()) {
return bbt_at (pos.beats());
}
superclock_t sc = pos.superclocks();
/* Use the later of the tempo or meter as the reference point to
* compute the BBT distance. All map points are fully defined by all 3
* time types, but we need the latest one to avoid incorrect
* computations of quarter duration.
*/
const Point* reference_point;
if (_tempo->beats() < _meter->beats()) {
reference_point = _meter;
} else {
reference_point = _tempo;
}
const Beats dq = _tempo->quarters_at_superclock (sc) - reference_point->beats();
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("qn @ %1 = %2, meter @ %3 , delta %4\n", sc, _tempo->quarters_at_superclock (sc), _meter->beats(), dq));
/* dq is delta in quarters (beats). Convert to delta in note types of
the current meter, which we'll call "grid"
*/
const int64_t note_value_count = muldiv_round (dq.get_beats(), _meter->note_value(), int64_t (4));
/* now construct a BBT_Offset using the count in grid units */
const BBT_Offset bbt_offset (0, note_value_count, dq.get_ticks());
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("BBT offset from %3 @ %1: %2\n", (_tempo->beats() < _meter->beats() ? _meter->bbt() : _tempo->bbt()), bbt_offset,
(_tempo->beats() < _meter->beats() ? "meter" : "tempo")));
superclock_t ref (std::min (_meter->sclock(), _tempo->sclock()));
return BBT_Argument (ref, _meter->bbt_add (reference_point->bbt(), bbt_offset));
}
superclock_t
TempoMetric::superclock_at (BBT_Time const & bbt) const
{
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("get quarters for %1 = %2 using %3\n", bbt, _meter->quarters_at (bbt), *this));
return _tempo->superclock_at (_meter->quarters_at (bbt));
}
MusicTimePoint::MusicTimePoint (TempoMap const & map, XMLNode const & node)
: Point (map, node)
, TempoPoint (map, *node.child (Tempo::xml_node_name.c_str()))
, MeterPoint (map, *node.child (Meter::xml_node_name.c_str()))
{
node.get_property (X_("name"), _name); /* may fail, leaves name empty */
}
XMLNode&
MusicTimePoint::get_state () const
{
XMLNode* node = new XMLNode (X_("MusicTime"));
Point::add_state (*node);
node->add_child_nocopy (Tempo::get_state());
node->add_child_nocopy (Meter::get_state());
node->set_property (X_("name"), _name); /* failure is OK */
return *node;
}
void
MusicTimePoint::set_name (std::string const & str)
{
_name = str;
/* XXX need a signal or something to announce change */
}
bool
GridIterator::valid_for (TempoMap const & m, superclock_t start, uint32_t bmod, uint32_t bdiv) const
{
if (!valid || start != end || map != &m || bar_mod != bmod || beat_div != bdiv) {
return false;
}
return true;
}
/* TEMPOMAP */
TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter)
{
TempoPoint* tp = new TempoPoint (*this, initial_tempo, 0, Beats(), BBT_Time());
MeterPoint* mp = new MeterPoint (*this, initial_meter, 0, Beats(), BBT_Time());
_tempos.push_back (*tp);
_meters.push_back (*mp);
_points.push_back (*tp);
_points.push_back (*mp);
}
TempoMap::~TempoMap()
{
}
TempoMap::TempoMap (XMLNode const & node, int version)
{
set_state (node, version);
}
TempoMap::TempoMap (TempoMap const & other)
{
copy_points (other);
}
TempoMap&
TempoMap::operator= (TempoMap const & other)
{
copy_points (other);
return *this;
}
void
TempoMap::copy_points (TempoMap const & other)
{
MusicTimePoint const * mt;
TempoPoint const * tp;
MeterPoint const * mp;
for (auto const & point : other._points) {
if ((mt = dynamic_cast<MusicTimePoint const *> (&point))) {
MusicTimePoint* mtp = new MusicTimePoint (*mt);
_bartimes.push_back (*mtp);
_meters.push_back (*mtp);
_tempos.push_back (*mtp);
_points.push_back (*mtp);
mtp->set_map (*this);
} else if ((mp = dynamic_cast<MeterPoint const *> (&point))) {
MeterPoint* mpp = new MeterPoint (*mp);
_meters.push_back (*mpp);
_points.push_back (*mpp);
mpp->set_map (*this);
} else if ((tp = dynamic_cast<TempoPoint const *> (&point))) {
TempoPoint* tpp = new TempoPoint (*tp);
_tempos.push_back (*tpp);
_points.push_back (*tpp);
tpp->set_map (*this);
}
}
#ifndef NDEBUG
for (auto & p : _points) {
assert (&p.map () == this);
}
for (auto & t : _tempos) {
assert (&t.map () == this);
}
for (auto & m : _meters) {
assert (&m.map () == this);
}
#endif
}
TempoMapCutBuffer*
TempoMap::cut (timepos_t const & start, timepos_t const & end, bool ripple)
{
return cut_copy (start, end, false, ripple);
}
TempoMapCutBuffer*
TempoMap::copy ( timepos_t const & start, timepos_t const & end)
{
return cut_copy (start, end, true, false);
}
TempoMapCutBuffer*
TempoMap::cut_copy (timepos_t const & start, timepos_t const & end, bool copy, bool ripple)
{
if (n_tempos() == 1 && n_meters() == 1) {
return nullptr;
}
TempoMetric sm (metric_at (start));
TempoMetric em (metric_at (end));
timecnt_t dur = start.distance (end);
TempoMapCutBuffer* cb = new TempoMapCutBuffer (dur);
superclock_t start_sclock = start.superclocks();
superclock_t end_sclock = end.superclocks();
bool removed = false;
Tempo start_tempo (tempo_at (start));
Tempo end_tempo (tempo_at (end));
Meter start_meter (meter_at (start));
Meter end_meter (meter_at (end));
MusicTimePoint* mtp;
BBT_Time bbt (bbt_at (start));
Beats b (quarters_at (start));
if (!copy) {
mtp = new MusicTimePoint (*this, start_sclock, b, bbt, em.tempo(), em.meter(), _("cut"));
} else {
mtp = nullptr;
}
for (Points::iterator p = _points.begin(); p != _points.end(); ) {
/* XXX might to check time domain of start/end, and use beat
* time here.
*/
if (p->sclock() >= end_sclock) {
break;
}
if (p->sclock() < start_sclock) {
++p;
continue;
}
Points::iterator nxt (p);
++nxt;
TempoPoint const * tp;
MeterPoint const * mp;
MusicTimePoint const * mtp;
if ((mtp = dynamic_cast<MusicTimePoint const *> (&*p))) {
cb->add (*mtp);
if (!copy && mtp->sclock() != 0) {
core_remove_bartime (*mtp);
remove_point (*mtp);
removed = true;
}
} else {
if ((tp = dynamic_cast<TempoPoint const *> (&*p))) {
cb->add (*tp);
if (!copy && tp->sclock() != 0) {
core_remove_tempo (*tp);
remove_point (*tp);
removed = true;
}
} else if ((mp = dynamic_cast<MeterPoint const *> (&*p))) {
cb->add (*mp);
if (!copy && mp->sclock() != 0) {
core_remove_meter (*mp);
remove_point (*mp);
removed = true;
}
}
}
p = nxt;
}
if (!copy && ripple) {
shift (start, -start.distance (end));
}
if (mtp) {
// add_or_replace_bartime (mtp);
}
if (!copy && removed) {
reset_starting_at (start_sclock);
}
return cb;
}
void
TempoMap::paste (TempoMapCutBuffer const & cb, timepos_t const & position, bool ripple, std::string suggested_name)
{
if (cb.empty()) {
return;
}
if (ripple) {
shift (position, cb.duration());
}
/* We need to look these up first, before we change the map */
const timepos_t end_position = position + cb.duration();
const Tempo end_tempo = tempo_at (end_position);
const Meter end_meter = meter_at (end_position);
/* iterate over _points since they are already in sclock order, and we
* won't need to post-sort the way we would if we handled tempos,
* meters, bartimes separately.
*/
BBT_Time pos_bbt = bbt_at (position);
Beats pos_beats = quarters_at (position);
bool ignored;
bool replaced;
MusicTimePoint* mtp;
superclock_t s;
std::string name;
/* Do not try to put a BBT marker at absolute zero or anywhere on a bar */
if (!position.is_zero() && (pos_bbt.ticks != 0 || pos_bbt.beats != 1)) {
if (suggested_name.empty()) {
name = _("paste>");
} else {
name = string_compose (X_("%1>"), suggested_name);
}
mtp = new MusicTimePoint (*this, position.superclocks(), pos_beats, pos_bbt, tempo_at (position), meter_at (position), name);
core_add_bartime (mtp, replaced);
if (!replaced) {
core_add_tempo (mtp, ignored);
core_add_meter (mtp, ignored);
core_add_point (mtp);
}
}
for (auto const & p : cb.points()) {
TempoPoint const * tp;
MeterPoint const * mp;
MusicTimePoint const * mtp;
Beats b;
BBT_Time bb;
s = p.sclock() + position.superclocks();
b = quarters_at_superclock (s);
bb = bbt_at (s);
if ((mtp = dynamic_cast<MusicTimePoint const *> (&p))) {
tp = dynamic_cast<TempoPoint const *> (&p);
mp = dynamic_cast<MeterPoint const *> (&p);
MusicTimePoint *ntp = new MusicTimePoint (*this, s, b, bb, *tp, *mp, mtp->name());
core_add_bartime (ntp, replaced);
if (!replaced) {
core_add_tempo (ntp, ignored);
core_add_meter (ntp, ignored);
core_add_point (ntp);
}
} else {
if ((tp = dynamic_cast<TempoPoint const *> (&p))) {
TempoPoint *ntp = new TempoPoint (*this, *tp, s, b, bb);
core_add_tempo (ntp, replaced);
if (!replaced) {
core_add_point (ntp);
}
} else if ((mp = dynamic_cast<MeterPoint const *> (&p))) {
MeterPoint *ntp = new MeterPoint (*this, *mp, s, b, bb);
core_add_meter (ntp, replaced);
if (!replaced) {
core_add_point (ntp);
}
}
}
}
pos_bbt = bbt_at (end_position);
pos_beats = quarters_at (end_position);
if (pos_bbt.ticks != 0 || pos_bbt.beats != 1) {
if (suggested_name.empty()) {
name = _("<paste");
} else {
name = string_compose (X_("<%1"), suggested_name);
}
mtp = new MusicTimePoint (*this, end_position.superclocks(), pos_beats, pos_bbt, end_tempo, end_meter, name);
core_add_bartime (mtp, replaced);
if (!replaced) {
core_add_tempo (mtp, ignored);
core_add_meter (mtp, ignored);
core_add_point (mtp);
}
}
reset_starting_at (s);
}
void
TempoMap::shift (timepos_t const & at, timecnt_t const & by)
{
if (at == std::numeric_limits<timepos_t>::min()) {
/* can't insert time at the front of the map: those entries are fixed */
return;
}
timecnt_t abs_by (by.abs());
superclock_t distance = abs_by.superclocks ();
superclock_t at_superclocks = abs_by.superclocks ();
if (distance == 0) {
return;
}
for (auto & p : _points) {
if (p.sclock() >= at_superclocks) {
if (distance >= 0 || p.sclock() > distance) {
if (dynamic_cast<MusicTimePoint*> (&p)) {
continue;
}
superclock_t s = p.sclock() + distance;
BBT_Time bb = bbt_at (s);
Beats b = quarters_at_superclock (s);
p.set (s, b, bb);
}
}
}
reset_starting_at (at_superclocks + distance);
}
void
TempoMap::shift (timepos_t const & at, BBT_Offset const & offset)
{
/* for now we require BBT-based shifts to be in units of whole bars */
if (std::abs (offset.bars) < 1) {
return;
}
if (offset.beats || offset.ticks) {
return;
}
const superclock_t at_superclocks = at.superclocks();
for (Points::iterator p = _points.begin(); p != _points.end(); ) {
Points::iterator nxt = p;
++nxt;
if (p->sclock() >= at_superclocks) {
if (offset.bars > p->bbt().bars) {
TempoPoint* tp;
MeterPoint* mp;
if (dynamic_cast<MusicTimePoint*> (&*p)) {
break;
} else if ((mp = dynamic_cast<MeterPoint*> (&*p))) {
core_remove_meter (*mp);
} else if ((tp = dynamic_cast<TempoPoint*> (&*p))) {
core_remove_tempo (*tp);
}
} else {
BBT_Time new_bbt (p->bbt().bars + offset.bars, p->bbt().beats, p->bbt().ticks);
p->set (p->sclock(), p->beats(), new_bbt);
}
}
p = nxt;
}
reset_starting_at (at_superclocks);
}
MeterPoint*
TempoMap::add_meter (MeterPoint* mp)
{
bool replaced;
MeterPoint* ret = core_add_meter (mp, replaced);
if (!replaced) {
core_add_point (mp);
} else {
delete mp;
}
reset_starting_at (ret->sclock());
return ret;
}
bool
TempoMap::clear_tempos_before (timepos_t const & t, bool stop_at_music_time)
{
if (_tempos.size() < 2) {
return false;
}
bool removed = false;
superclock_t sc = t.superclocks();
Tempos::iterator tp = _tempos.end();
--tp;
MusicTimePoint* mtp (nullptr);
while (tp != _tempos.begin()) {
if (tp->sclock() > sc) {
--tp;
mtp = nullptr;
continue;
}
if ((mtp = dynamic_cast<MusicTimePoint*> (&*tp)) && stop_at_music_time) {
break;
}
Tempos::iterator nxt = tp;
--nxt;
if (mtp) {
Meters::iterator mpi = _meters.s_iterator_to (*(static_cast<MeterPoint*> (&*mtp)));
_meters.erase (mpi);
MusicTimes::iterator mtpi = _bartimes.s_iterator_to (*mtp);
_bartimes.erase (mtpi);
}
Points::iterator pi = _points.s_iterator_to (*(static_cast<Point*> (&*tp)));
if (pi != _points.end()) {
_points.erase (pi);
}
_tempos.erase (tp);
removed = true;
tp = nxt;
mtp = nullptr;
}
if (removed) {
reset_starting_at (sc);
}
return removed;
}
bool
TempoMap::clear_tempos_after (timepos_t const & t, bool stop_at_music_time)
{
if (_tempos.size() < 2) {
return false;
}
bool removed = false;
superclock_t sc = t.superclocks();
Tempos::iterator tp = _tempos.begin();
MusicTimePoint* mtp (nullptr);
++tp;
while (tp != _tempos.end()) {
if (tp->sclock() < sc) {
++tp;
mtp = nullptr;
continue;
}
if ((mtp = dynamic_cast<MusicTimePoint*> (&*tp)) && stop_at_music_time) {
break;
}
Tempos::iterator nxt = tp;
++nxt;
if (mtp) {
Meters::iterator mpi = _meters.s_iterator_to (*(static_cast<MeterPoint*> (&*mtp)));
_meters.erase (mpi);
MusicTimes::iterator mtpi = _bartimes.s_iterator_to (*mtp);
_bartimes.erase (mtpi);
}
Points::iterator pi = _points.s_iterator_to (*(static_cast<Point*> (&*tp)));
if (pi != _points.end()) {
_points.erase (pi);
}
_tempos.erase (tp);
removed = true;
tp = nxt;
mtp = nullptr;
}
if (removed) {
reset_starting_at (sc);
}
return removed;
}
void
TempoMap::change_tempo (TempoPoint & p, Tempo const & t)
{
*((Tempo*)&p) = t;
reset_starting_at (p.sclock());
}
void
TempoMap::replace_tempo (TempoPoint const & old, Tempo const & t, timepos_t const & time)
{
if (old.sclock() == 0) {
_tempos.front() = t;
reset_starting_at (0);
return;
}
remove_tempo (old, false);
set_tempo (t, time);
}
TempoPoint &
TempoMap::set_tempo (Tempo const & t, BBT_Argument const & bbt)
{
return set_tempo (t, timepos_t (quarters_at (bbt)));
}
TempoPoint &
TempoMap::set_tempo (Tempo const & t, timepos_t const & time)
{
TempoPoint * ret;
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", time, t));
if (time.is_beats()) {
/* tempo changes are required to be on-beat */
Beats on_beat = time.beats().round_to_beat();
superclock_t sc;
BBT_Time bbt;
TempoMetric metric (metric_at (on_beat, false));
bbt = metric.bbt_at (on_beat);
sc = metric.superclock_at (on_beat);
TempoPoint* tp = new TempoPoint (*this, t, sc, on_beat, bbt);
ret = add_tempo (tp);
} else {
Beats beats;
BBT_Time bbt;
superclock_t sc = time.superclocks();
TempoMetric tm (metric_at (sc, false));
/* tempo changes must be on beat */
beats = tm.quarters_at_superclock (sc).round_to_beat ();
bbt = tm.bbt_at (beats);
/* recompute superclock position of rounded beat */
sc = tm.superclock_at (beats);
TempoPoint* tp = new TempoPoint (*this, t, sc, beats, bbt);
ret = add_tempo (tp);
}
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::TemporalMap)) {
dump (cerr);
}
#endif
return *ret;
}
TempoPoint &
TempoMap::set_tempo (Tempo const & t, timepos_t const & time, Beats const & beats)
{
assert (!time.is_beats());
BBT_Time bbt;
TempoMetric metric (metric_at (beats, false));
bbt = metric.bbt_at (beats);
TempoPoint* tp = new TempoPoint (*this, t, time.superclocks(), beats, bbt);
TempoPoint* ret = add_tempo (tp);
return *ret;
}
void
TempoMap::core_add_point (Point* pp)
{
Points::iterator p;
const Beats beats_limit = pp->beats();
for (p = _points.begin(); p != _points.end() && p->beats() < beats_limit; ++p);
_points.insert (p, *pp);
}
TempoPoint*
TempoMap::core_add_tempo (TempoPoint* tp, bool& replaced)
{
Tempos::iterator t;
const superclock_t sclock_limit = tp->sclock();
const Beats beats_limit = tp->beats ();
for (t = _tempos.begin(); t != _tempos.end() && t->beats() < beats_limit; ++t);
if (t != _tempos.end()) {
if (t->sclock() == sclock_limit) {
/* overwrite Tempo part of this point */
*((Tempo*)&(*t)) = *tp;
/* caller must delete tp when replaced is true */
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old tempo with %1\n", *tp));
replaced = true;
return &(*t);
}
}
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted tempo %1\n", *tp));
replaced = false;
return &(* _tempos.insert (t, *tp));
}
MeterPoint*
TempoMap::core_add_meter (MeterPoint* mp, bool& replaced)
{
Meters::iterator m;
const superclock_t sclock_limit = mp->sclock();
const Beats beats_limit = mp->beats ();
for (m = _meters.begin(); m != _meters.end() && m->beats() < beats_limit; ++m);
if (m != _meters.end()) {
if (m->sclock() == sclock_limit) {
/* overwrite Meter part of this point */
*((Meter*)&(*m)) = *mp;
/* caller must delete mp when replaced is true */
replaced = true;
return &(*m);
}
}
replaced = false;
return &(*(_meters.insert (m, *mp)));
}
MusicTimePoint*
TempoMap::core_add_bartime (MusicTimePoint* mtp, bool& replaced)
{
MusicTimes::iterator m;
const superclock_t sclock_limit = mtp->sclock();
for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < sclock_limit; ++m);
if (m != _bartimes.end()) {
if (m->sclock() == sclock_limit) {
/* overwrite Tempo part of this point */
*m = *mtp;
/* caller must delete mtp when replaced is true */
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old bartime with %1\n", mtp));
replaced = true;
return &(*m);
}
}
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted bartime %1\n", mtp));
replaced = false;
return &(* _bartimes.insert (m, *mtp));
}
TempoPoint*
TempoMap::add_tempo (TempoPoint * tp)
{
bool replaced;
TempoPoint* ret = core_add_tempo (tp, replaced);
if (!replaced) {
core_add_point (tp);
} else {
delete tp;
}
TempoPoint* prev = const_cast<TempoPoint*> (previous_tempo (*ret));
if (prev) {
reset_starting_at (prev->sclock());
} else {
reset_starting_at (ret->sclock());
}
return ret;
}
void
TempoMap::remove_tempo (TempoPoint const & tp, bool with_reset)
{
if (_tempos.size() < 2) {
return;
}
if (!core_remove_tempo (tp)) {
return;
}
superclock_t sc (tp.sclock());
remove_point (tp);
if (with_reset) {
reset_starting_at (sc);
}
}
bool
TempoMap::core_remove_tempo (TempoPoint const & tp)
{
Tempos::iterator t;
/* the argument is likely to be a Point-derived object that doesn't
* actually exist in this TempoMap, since the caller called
* TempoMap::write_copy() in order to perform an RCU operation, but
* will be removing an element known from the original map.
*
* However, since we do not allow points of the same type (Tempo,
* Meter, BarTime) at the same time, we can effectively search here
* using what is effectively a duple of (type,time) for the
* comparison.
*
* Once/if found, we will have a pointer to the actual Point-derived
* object in this TempoMap, and we can then remove that from the
* _points list.
*/
for (t = _tempos.begin(); t != _tempos.end() && t->sclock() < tp.sclock(); ++t);
if (t == _tempos.end()) {
/* not found */
return false;
}
if (t->sclock() != tp.sclock()) {
/* error ... no tempo point at the time of tp */
std::cerr << "not point at time\n";
return false;
}
Tempos::iterator nxt = _tempos.begin();
Tempos::iterator prev = _tempos.end();
if (t != _tempos.end()) {
nxt = t;
++nxt;
}
if (t != _tempos.begin()) {
prev = t;
--prev;
}
const bool was_end = (nxt == _tempos.end());
_tempos.erase (t);
if (prev != _tempos.end() && was_end) {
prev->set_end_npm (prev->note_types_per_minute()); /* remove any ramp */
}
return true;
}
void
TempoMap::set_bartime (BBT_Time const & bbt, timepos_t const & pos, std::string name)
{
TEMPO_MAP_ASSERT (pos.time_domain() == AudioTime);
superclock_t sc (pos.superclocks());
TempoMetric metric (metric_at (sc));
/* MusicTimePoints define a beat position (even if it is not predicted
* by the prior tempo map elements.
*/
Beats b = metric.quarters_at_superclock (sc).round_up_to_beat ();;
MusicTimePoint* tp = new MusicTimePoint (*this, sc, b, bbt, metric.tempo(), metric.meter(), name);
add_or_replace_bartime (tp);
}
void
TempoMap::replace_bartime (MusicTimePoint & mtp, bool with_reset)
{
bool ignored;
core_add_bartime (&mtp, ignored);
if (with_reset) {
reset_starting_at (mtp.sclock());
}
}
MusicTimePoint*
TempoMap::add_or_replace_bartime (MusicTimePoint* mtp)
{
bool replaced;
/* A MusicTimePoint by definition defines a beat position. It's not
* zero, but it must be "on beat". So ensure that this is true.
*/
mtp->set (mtp->sclock(), mtp->beats().round_up_to_beat(), mtp->bbt());
MusicTimePoint* ret = core_add_bartime (mtp, replaced);
if (!replaced) {
bool ignore;
(void) core_add_tempo (mtp, ignore);
(void) core_add_meter (mtp, ignore);
core_add_point (mtp);
} else {
delete mtp;
}
reset_starting_at (ret->sclock());
return ret;
}
bool
TempoMap::core_remove_bartime (MusicTimePoint const & mtp)
{
MusicTimes::iterator m;
/* the argument is likely to be a Point-derived object that doesn't
* actually exist in this TempoMap, since the caller called
* TempoMap::write_copy() in order to perform an RCU operation, but
* will be removing an element known from the original map.
*
* However, since we do not allow points of the same type (Tempo,
* Meter, BarTime) at the same time, we can effectively search here
* using what is effectively a duple of (type,time) for the
* comparison.
*
* Once/if found, we will have a pointer to the actual Point-derived
* object in this TempoMap, and we can then remove that from the
* _points list.
*/
for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < mtp.sclock(); ++m);
if (m == _bartimes.end()) {
/* error ... not found */
return false;
}
if (m->sclock() != mtp.sclock()) {
/* error ... no music time point at the time of tp */
return false;
}
remove_point (mtp);
core_remove_tempo (mtp);
core_remove_meter (mtp);
_bartimes.erase (m);
return true;
}
void
TempoMap::remove_bartime (MusicTimePoint const & mtp, bool with_reset)
{
superclock_t sc (mtp.sclock());
core_remove_bartime (mtp);
if (with_reset) {
reset_starting_at (sc);
}
}
void
TempoMap::remove_point (Point const & point)
{
Points::iterator p;
/* Again, we do not allow multiple MusicTimePoints at the same
* location, so if sclock() matches, @param point matches
* the point in the list.
*/
for (p = _points.begin(); p != _points.end(); ++p) {
if (p->sclock() == point.sclock()) {
// XXX need to fix this leak delete tpp;
_points.erase (p);
break;
}
}
}
void
TempoMap::reset_starting_at (superclock_t sc)
{
DEBUG_TRACE (DEBUG::MapReset, string_compose ("reset starting at %1\n", sc));
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::MapReset)) {
dump (std::cerr);
}
#endif
TEMPO_MAP_ASSERT (!_tempos.empty());
TEMPO_MAP_ASSERT (!_meters.empty());
TempoPoint* tp;
MeterPoint* mp;
MusicTimePoint* mtp;
TempoMetric metric (_tempos.front(), _meters.front());
Points::iterator p;
bool need_initial_ramp_reset = false;
DEBUG_TRACE (DEBUG::MapReset, string_compose ("we begin at %1 with metric %2\n", sc, metric));
/* Setup the metric that is in effect at the starting point */
for (p = _points.begin(); p != _points.end(); ++p) {
DEBUG_TRACE (DEBUG::MapReset, string_compose ("Now looking at %1 => %2 \n", &(*p), *p));
if (p->sclock() > sc) {
break;
}
mtp = 0;
tp = 0;
mp = 0;
if ((mtp = dynamic_cast<MusicTimePoint*> (&*p)) != 0) {
metric = TempoMetric (*mtp, *mtp);
DEBUG_TRACE (DEBUG::MapReset, string_compose ("Bartime!, used tempo @ %1\n", (TempoPoint*) mtp));
need_initial_ramp_reset = false;
} else if ((tp = dynamic_cast<TempoPoint*> (&*p)) != 0) {
metric = TempoMetric (*tp, metric.meter());
if (tp->ramped()) {
need_initial_ramp_reset = true;
} else {
need_initial_ramp_reset = true;
}
DEBUG_TRACE (DEBUG::MapReset, string_compose ("Tempo! @ %1, metric's tempo is %2\n", tp, &metric.tempo()));
} else if ((mp = dynamic_cast<MeterPoint*> (&*p)) != 0) {
metric = TempoMetric (metric.tempo(), *mp);
DEBUG_TRACE (DEBUG::MapReset, "Meter!\n");
}
}
/* if the tempo point the defines our starting metric for position
* @param sc is ramped, recompute its omega value based on the beat
* time of the following tempo point. If we do not do this before we
* start, then ::superclock_at() for subsequent points will be
* incorrect.
*/
if (need_initial_ramp_reset) {
const TempoPoint *nxt = next_tempo (metric.tempo());
if (nxt) {
const_cast<TempoPoint*> (&metric.tempo())->compute_omega_from_next_tempo (*nxt);
}
need_initial_ramp_reset = false;
}
MusicTimes::iterator next_mtp = _bartimes.begin();
superclock_t current_section_limit;
while (next_mtp != _bartimes.end() && (next_mtp->sclock() <= sc)) {
++next_mtp;
}
if (next_mtp != _bartimes.end()) {
DEBUG_TRACE (DEBUG::MapReset, string_compose ("start rset with section defined by MTP @ %1 %2\n", &*next_mtp, *next_mtp));
current_section_limit = next_mtp->sclock();
} else {
current_section_limit = std::numeric_limits<superclock_t>::max();
DEBUG_TRACE (DEBUG::MapReset, "start rset with no next MTP (run to end)\n");
}
/* Now iterate over remaining points and recompute their audio time
* and beat time positions.
*/
while (p != _points.end()) {
if (next_mtp != _bartimes.end()) {
current_section_limit = next_mtp->sclock();
} else {
current_section_limit = std::numeric_limits<superclock_t>::max();
}
Points::iterator section_start = p;
DEBUG_TRACE (DEBUG::MapReset, string_compose ("start section at %1 with limit at %2\n", *p, current_section_limit));;
while (p != _points.end() && (p->sclock() < current_section_limit)) {
++p;
}
reset_section (section_start, p, current_section_limit, metric);
if (next_mtp != _bartimes.end()) {
DEBUG_TRACE (DEBUG::MapReset, string_compose ("reset MTP %1 using %2 to %3\n", *next_mtp, metric, metric.tempo().quarters_at_superclock (next_mtp->sclock())));
next_mtp->set (next_mtp->sclock(), metric.tempo().quarters_at_superclock (next_mtp->sclock()), next_mtp->bbt());
}
if (next_mtp != _bartimes.end()) {
++next_mtp;
}
}
DEBUG_TRACE (DEBUG::MapReset, "RESET DONE\n");
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::MapReset)) {
dump (std::cerr);
}
#endif
}
void
TempoMap::reset_section (Points::iterator& begin, Points::iterator& end, superclock_t section_limit, TempoMetric& metric)
{
TempoPoint* tp;
TempoPoint* nxt_tempo = 0;
MeterPoint* mp;
MusicTimePoint* mtp;
DEBUG_TRACE (DEBUG::MapReset, string_compose ("reset a section of %1 points, ending at %2\n", std::distance (begin, end), section_limit));
for (Points::iterator p = begin; p != end; ) {
mtp = 0;
tp = 0;
mp = 0;
if ((mtp = dynamic_cast<MusicTimePoint*> (&*p)) == 0) {
if ((tp = dynamic_cast<TempoPoint*> (&*p)) == 0) {
mp = dynamic_cast<MeterPoint*> (&*p);
}
}
DEBUG_TRACE (DEBUG::MapReset, string_compose ("workong on it! tp = %1 mp %2 mtp %3\n", tp, mp, mtp));
if (tp) {
Points::iterator pp = p;
nxt_tempo = 0;
++pp;
while (pp != _points.end()) {
TempoPoint* nt = dynamic_cast<TempoPoint*> (&*pp);
if (nt) {
nxt_tempo = nt;
break;
}
++pp;
}
DEBUG_TRACE (DEBUG::MapReset, string_compose ("considering omega comp for %1 with nxt = %2\n", *tp, nxt_tempo));
if (tp->ramped() && nxt_tempo) {
tp->compute_omega_from_next_tempo (*nxt_tempo);
}
}
Points::iterator nxt = p;
++nxt;
if (!mtp) {
DEBUG_TRACE (DEBUG::MapReset, string_compose ("recompute %1 using %2\n", p->bbt(), metric));
superclock_t sc = metric.superclock_at (p->bbt());
if (sc >= section_limit) {
if (tp) {
core_remove_tempo (*tp);
} else {
core_remove_meter (*mp);
}
} else {
if (mp) {
/* Meter markers must be on-bar */
BBT_Time rounded = metric.meter().round_to_bar (p->bbt());
p->set (sc, metric.meter().quarters_at (rounded), rounded);
DEBUG_TRACE (DEBUG::MapReset, string_compose ("\tbased on %1 move meter point to %2,%3\n", p->bbt(), sc, p->beats()));
} else {
/* Tempo markers must be on-beat */
BBT_Time rounded = metric.meter().round_to_beat (p->bbt());
p->set (sc, metric.meter().quarters_at (rounded), rounded);
DEBUG_TRACE (DEBUG::MapReset, string_compose ("\tbased on %1 move tempo point to %2,%3\n", p->bbt(), sc, p->beats()));
}
}
} else {
DEBUG_TRACE (DEBUG::MapReset, "\tnot recomputing this one\n");
}
/* Now ensure that metric is correct moving forward */
if ((mtp = dynamic_cast<MusicTimePoint*> (&*p)) != 0) {
metric = TempoMetric (*mtp, *mtp);
} else if ((tp = dynamic_cast<TempoPoint*> (&*p)) != 0) {
metric = TempoMetric (*tp, metric.meter());
} else if ((mp = dynamic_cast<MeterPoint*> (&*p)) != 0) {
metric = TempoMetric (metric.tempo(), *mp);
}
p = nxt;
}
}
bool
TempoMap::move_meter (MeterPoint const & mp, timepos_t const & when, bool push)
{
TEMPO_MAP_ASSERT (!_tempos.empty());
TEMPO_MAP_ASSERT (!_meters.empty());
if (_meters.size() < 2 || mp == _meters.front()) {
/* not movable */
return false;
}
superclock_t sc;
Beats beats;
BBT_Time bbt;
beats = when.beats ();
/* Do not allow moving a meter marker to the same position as
* an existing one.
*/
Tempos::iterator t, prev_t;
Meters::iterator m, prev_m;
/* meter changes must be on bar */
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->beats() < beats; ++t) { prev_t = t; }
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->beats() < beats && *m != mp; ++m) { prev_m = m; }
if (prev_m == _meters.end()) {
return false;
}
if (prev_t == _tempos.end()) {
prev_t = _tempos.begin();
}
TempoMetric metric (*prev_t, *prev_m);
bbt = metric.bbt_at (beats);
bbt = metric.round_to_bar (bbt);
/* Now find the correct TempoMetric for the new BBT position (which may
* differ from the one we determined earlier.
*
* The search for the correct meter will be limited by the meter we're
* dragging. But the search for the correct tempo needs to bounded by
* both the BBT *and* the beat position, in case there is an upcoming
* BBT marker.
*/
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->bbt() < bbt && t->beats() < beats; ++t) { prev_t = t; }
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->bbt() < bbt && *m != mp; ++m) { prev_m = m; }
if (prev_m == _meters.end()) {
return false;
}
if (prev_t == _tempos.end()) {
prev_t = _tempos.begin();
}
if (dynamic_cast<MusicTimePoint*> (&(*prev_t)) || dynamic_cast<MusicTimePoint*> (&(*prev_m))) {
/* game over ... cannot drag meter through a BBT Marker */
return false;
}
metric = TempoMetric (*prev_t, *prev_m);
beats = metric.quarters_at (bbt);
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end(); ++m) {
if (&*m != &mp) {
if (m->beats() == beats) {
return false;
}
}
}
sc = metric.superclock_at (bbt);
if (mp.sclock() == sc && mp.beats() == beats && mp.bbt() == bbt) {
return false;
}
const superclock_t old_sc = mp.sclock();
/* reset position of this meter */
const_cast<MeterPoint*> (&mp)->set (sc, beats, bbt);
{
Meters::iterator current = _meters.end();
Meters::iterator insert_before = _meters.end();
for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) {
if (*m == mp) {
current = m;
}
if (insert_before == _meters.end() && (m->sclock() > sc)) {
insert_before = m;
}
}
/* existing meter must have been found */
TEMPO_MAP_ASSERT (current != _meters.end());
/* reposition in list */
_meters.splice (insert_before, _meters, current);
}
{
Points::iterator current = _points.end();
Points::iterator insert_before = _points.end();
for (Points::iterator m = _points.begin(); m != _points.end(); ++m) {
if (*m == mp) {
current = m;
}
if (insert_before == _points.end() && (m->sclock() > sc)) {
insert_before = m;
}
}
/* existing meter must have been found */
TEMPO_MAP_ASSERT (current != _points.end());
/* reposition in list */
_points.splice (insert_before, _points, current);
}
/* recompute 3 domain positions for everything after this */
reset_starting_at (std::min (sc, old_sc));
return true;
}
bool
TempoMap::move_tempo (TempoPoint const & tp, timepos_t const & when, bool push)
{
TEMPO_MAP_ASSERT (!_tempos.empty());
TEMPO_MAP_ASSERT (!_meters.empty());
if (_tempos.size() < 2 || tp == _tempos.front()) {
/* not movable */
return false;
}
superclock_t sc;
Beats beats;
BBT_Time bbt;
/* tempo changes must be on beat */
beats = when.beats();
/* XXX need to TEMPO_MAP_ASSERT that meter note value is >= 4 */
MeterPoint const & mm (meter_at (beats));
beats.round_to_subdivision (mm.note_value() / 4, RoundNearest);
/* Do not allow moving a tempo marker to the same position as
* an existing one.
*/
Tempos::iterator t, prev_t;
Meters::iterator m, prev_m;
/* find tempo & meter in effect at the new target location */
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->beats() <= beats && *t != tp; ++t) { prev_t = t; }
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->beats() <= beats; ++m) { prev_m = m; }
if (prev_t == _tempos.end()) {
/* moved earlier than first, no movement */
return false;
}
if (prev_m == _meters.end()) {
/* moved earlier than first, no movement */
return false;
}
/* If the previous tempo is ramped, we need to recompute its omega
* constant to cover the (new) duration of the ramp.
*/
if (prev_t->actually_ramped()) {
prev_t->compute_omega_from_distance_and_next_tempo (beats - prev_t->beats(), tp);
}
TempoMetric metric (*prev_t, *prev_m);
const Beats delta ((beats - tp.beats()).abs());
if (delta < Beats::ticks (metric.meter().ticks_per_grid())) {
return false;
}
sc = metric.superclock_at (beats);
bbt = metric.bbt_at (beats);
if (tp.sclock() == sc && tp.beats() == beats && tp.bbt() == bbt) {
return false;
}
const superclock_t old_sc = tp.sclock();
/* reset position of this tempo */
const_cast<TempoPoint*> (&tp)->set (sc, beats, bbt);
/* move to correct position in tempo list */
{
Tempos::iterator current = _tempos.end();
Tempos::iterator insert_before = _tempos.end();
for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
if (*t == tp) {
current = t;
}
if (insert_before == _tempos.end() && (t->sclock() > sc)) {
insert_before = t;
}
}
/* existing tempo must have been found */
TEMPO_MAP_ASSERT (current != _tempos.end());
/* reposition in list */
_tempos.splice (insert_before, _tempos, current);
}
/* move to correct position in points list */
{
Points::iterator current = _points.end();
Points::iterator insert_before = _points.end();
for (Points::iterator t = _points.begin(); t != _points.end(); ++t) {
if (*t == tp) {
current = t;
}
if (insert_before == _points.end() && (t->sclock() > sc)) {
insert_before = t;
}
}
/* existing tempo must have been found */
TEMPO_MAP_ASSERT (current != _points.end());
/* reposition in list */
_points.splice (insert_before, _points, current);
}
/* recompute 3 domain positions for everything after this */
reset_starting_at (std::min (sc, old_sc));
return true;
}
MeterPoint &
TempoMap::set_meter (Meter const & m, timepos_t const & time)
{
MeterPoint * ret = 0;
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", time, m));
if (time.is_beats()) {
Beats beats (time.beats());
TempoMetric metric (metric_at (beats));
/* meter changes are required to be on-bar */
BBT_Time rounded_bbt = metric.bbt_at (beats);
rounded_bbt = metric.round_to_bar (rounded_bbt);
const Beats rounded_beats = metric.quarters_at (rounded_bbt);
const superclock_t sc = metric.superclock_at (rounded_beats);
MeterPoint* mp = new MeterPoint (*this, m, sc, rounded_beats, rounded_bbt);
ret = add_meter (mp);
} else {
superclock_t sc (time.superclocks());
Beats beats;
BBT_Time bbt;
TempoMetric metric (metric_at (sc));
/* meter changes must be on bar */
bbt = metric.bbt_at (time);
bbt = metric.round_to_bar (bbt);
/* compute beat position */
beats = metric.quarters_at (bbt);
/* recompute superclock position of bar-rounded position */
sc = metric.superclock_at (beats);
MeterPoint* mp = new MeterPoint (*this, m, sc, beats, bbt);
ret = add_meter (mp);
}
return *ret;
}
MeterPoint &
TempoMap::set_meter (Meter const & t, BBT_Argument const & bbt)
{
return set_meter (t, timepos_t (quarters_at (bbt)));
}
void
TempoMap::remove_meter (MeterPoint const & mp, bool with_reset)
{
if (_meters.size() < 2) {
return;
}
if (!core_remove_meter (mp)) {
return;
}
superclock_t sc = mp.sclock();
remove_point (mp);
if (with_reset) {
reset_starting_at (sc);
}
}
bool
TempoMap::core_remove_meter (MeterPoint const & mp)
{
Meters::iterator m;
/* the argument is likely to be a Point-derived object that doesn't
* actually exist in this TempoMap, since the caller called
* TempoMap::write_copy() in order to perform an RCU operation, but
* will be removing an element known from the original map.
*
* However, since we do not allow points of the same type (Tempo,
* Meter, BarTime) at the same time, we can effectively search here
* using what is effectively a duple of (type,time) for the
* comparison.
*
* Once/if found, we will have a pointer to the actual Point-derived
* object in this TempoMap, and we can then remove that from the
* _points list.
*/
for (m = _meters.begin(); m != _meters.end() && m->sclock() < mp.sclock(); ++m);
if (m == _meters.end()) {
/* not found */
return false;
}
if (m->sclock() != mp.sclock()) {
/* error ... no meter point at the time of mp */
return false;
}
_meters.erase (m);
return true;
}
Temporal::BBT_Argument
TempoMap::bbt_at (timepos_t const & pos) const
{
if (pos.is_beats()) {
return bbt_at (pos.beats());
}
return bbt_at (pos.superclocks());
}
Temporal::BBT_Argument
TempoMap::bbt_at (superclock_t s) const
{
TempoMetric metric (metric_at (s));
superclock_t ref (std::min (metric.tempo().sclock(), metric.meter().sclock()));
return BBT_Argument (ref, metric.bbt_at (timepos_t::from_superclock (s)));
}
Temporal::BBT_Argument
TempoMap::bbt_at (Temporal::Beats const & qn) const
{
TempoMetric metric (metric_at (qn));
return BBT_Argument (metric.reftime(), metric.bbt_at (qn));
}
superclock_t
TempoMap::superclock_at (Temporal::Beats const & qn) const
{
return metric_at (qn).superclock_at (qn);
}
superclock_t
TempoMap::superclock_at (Temporal::BBT_Argument const & bbt) const
{
return metric_at (bbt).superclock_at (bbt);
}
superclock_t
TempoMap::superclock_at (timepos_t const & pos) const
{
if (pos.is_beats()) {
return superclock_at (pos.beats ());
}
/* somewhat nonsensical to call this under these conditions but ... */
return pos.superclocks();
}
#define S2Sc(s) (samples_to_superclock ((s), TEMPORAL_SAMPLE_RATE))
#define Sc2S(s) (superclock_to_samples ((s), TEMPORAL_SAMPLE_RATE))
/** Count the number of beats that are equivalent to distance when going forward,
starting at pos.
*/
Temporal::Beats
TempoMap::scwalk_to_quarters (superclock_t pos, superclock_t distance) const
{
TempoMetric first (metric_at (pos));
TempoMetric last (metric_at (pos+distance));
Temporal::Beats a = first.quarters_at_superclock (pos);
Temporal::Beats b = last.quarters_at_superclock (pos+distance);
return b - a;
}
Temporal::Beats
TempoMap::scwalk_to_quarters (Temporal::Beats const & pos, superclock_t distance) const
{
/* XXX this converts from beats to superclock and back to beats... which is OK (reversible) */
superclock_t s = metric_at (pos).superclock_at (pos);
s += distance;
return metric_at (s).quarters_at_superclock (s);
}
Temporal::Beats
TempoMap::bbtwalk_to_quarters (Beats const & pos, BBT_Offset const & distance) const
{
return quarters_at (bbt_walk (bbt_at (pos), distance)) - pos;
}
Temporal::Beats
TempoMap::bbtwalk_to_quarters (BBT_Argument const & pos, BBT_Offset const & distance) const
{
return quarters_at (bbt_walk (pos, distance)) - quarters_at (pos);
}
void
TempoMap::sample_rate_changed (samplecnt_t new_sr)
{
const double ratio = new_sr / (double) TEMPORAL_SAMPLE_RATE;
for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
t->map_reset_set_sclock_for_sr_change (llrint (ratio * t->sclock()));
}
for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) {
m->map_reset_set_sclock_for_sr_change (llrint (ratio * m->sclock()));
}
for (MusicTimes::iterator p = _bartimes.begin(); p != _bartimes.end(); ++p) {
p->map_reset_set_sclock_for_sr_change (llrint (ratio * p->sclock()));
}
}
void
TempoMap::dump (std::ostream& ostr) const
{
ostr << "\n\nTEMPO MAP @ " << this << ":\n" << std::dec;
ostr << "... tempos...\n";
for (Tempos::const_iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
ostr << &*t << ' ' << *t << endl;
}
ostr << "... meters...\n";
for (Meters::const_iterator m = _meters.begin(); m != _meters.end(); ++m) {
ostr << &*m << ' ' << *m << endl;
}
ostr << "... bartimes...\n";
for (MusicTimes::const_iterator m = _bartimes.begin(); m != _bartimes.end(); ++m) {
ostr << &*m << ' ' << *m << endl;
}
ostr << "... all points ...\n";
for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) {
ostr << &*p << ' ' << *p;
if (dynamic_cast<MusicTimePoint const *> (&(*p))) {
ostr << " BarTime";
}
if (dynamic_cast<TempoPoint const *> (&(*p))) {
ostr << " Tempo";
}
if (dynamic_cast<MeterPoint const *> (&(*p))) {
ostr << " Meter";
}
ostr << endl;
}
ostr << "------------\n\n\n";
}
template<class const_traits_t> typename const_traits_t::iterator_type
TempoMap::_get_tempo_and_meter (typename const_traits_t::tempo_point_type & tp,
typename const_traits_t::meter_point_type & mp,
typename const_traits_t::time_reference (Point::*method)() const,
typename const_traits_t::time_type arg,
typename const_traits_t::iterator_type begini,
typename const_traits_t::iterator_type endi,
typename const_traits_t::tempo_point_type tstart,
typename const_traits_t::meter_point_type mstart,
bool can_match, bool ret_iterator_after_not_at) const
{
typename const_traits_t::iterator_type p;
typename const_traits_t::iterator_type last_used = endi;
bool tempo_done = false;
bool meter_done = false;
TEMPO_MAP_ASSERT (!_tempos.empty());
TEMPO_MAP_ASSERT (!_meters.empty());
TEMPO_MAP_ASSERT (!_points.empty());
/* If the starting position is the beginning of the timeline (indicated
* by the default constructor value for the time_type (superclock_t,
* Beats, BBT_Time), then we are always allowed to use the tempo &
* meter at that position.
*
* Without this, it would be necessary to special case "can_match" in
* the caller if the start is "zero". Instead we do that here, since
* common cases (e.g. ::get_grid()) will use can_match = false, but may
* pass in a zero start point.
*/
can_match = (can_match || arg == typename const_traits_t::time_type ());
/* Set return tempo and meter points by value using the starting tempo
* and meter passed in.
*
* Then advance through all points, resetting either tempo and/or meter
* until we find a point beyond (or equal to, if @p can_match is
* true) the @p arg (end time)
*/
for (tp = tstart, mp = mstart, p = begini; p != endi; ++p) {
typename const_traits_t::tempo_point_type tpp;
typename const_traits_t::meter_point_type mpp;
if (!tempo_done && (tpp = dynamic_cast<typename const_traits_t::tempo_point_type> (&(*p))) != 0) {
if ((can_match && (((*p).*method)() > arg)) || (!can_match && (((*p).*method)() >= arg))) {
tempo_done = true;
} else {
tp = tpp;
last_used = p;
}
}
if (!meter_done && (mpp = dynamic_cast<typename const_traits_t::meter_point_type> (&(*p))) != 0) {
if ((can_match && (((*p).*method)() > arg)) || (!can_match && (((*p).*method)() >= arg))) {
meter_done = true;
} else {
mp = mpp;
last_used = p;
}
}
if (meter_done && tempo_done) {
break;
}
}
if (!tp || !mp) {
return endi;
}
if (ret_iterator_after_not_at) {
p = last_used;
if (can_match) {
while ((p != endi) && ((*p).*method)() <= arg) ++p;
} else {
while ((p != endi) && ((*p).*method)() < arg) ++p;
}
return p;
}
return last_used;
}
Points::const_iterator
TempoMap::get_grid (TempoMapPoints& ret, superclock_t rstart, superclock_t end, uint32_t bar_mod, uint32_t beat_div) const
{
if (rstart == end) {
return _points.end();
}
/* note: @p bar_mod is "bar modulo", and describes the N in "give
me every Nth bar". If the caller wants every 4th bar, bar_mod ==
4. If we want every point defined by the tempo note type (e.g. every
quarter not, then bar_mod is zero.
*/
TEMPO_MAP_ASSERT (!_tempos.empty());
TEMPO_MAP_ASSERT (!_meters.empty());
TEMPO_MAP_ASSERT (!_points.empty());
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::Grid)) {
dump (std::cout);
}
#endif
DEBUG_TRACE (DEBUG::Grid, string_compose (">>> GRID START %1 .. %2 (barmod = %3)\n", rstart, end, bar_mod));
/* The fast path: one tempo, one meter, just do (relatively) simple math */
if (_tempos.size() == 1 && _meters.size() == 1) {
TempoMetric metric (_tempos.front(), _meters.front());
/* Figure out the beat preceding rstart */
superclock_t spdiv;
if (bar_mod == 1) {
spdiv = llrintf (metric.superclocks_per_note_type() * (metric.meter().divisions_per_bar() * (4. / metric.meter().note_value())));
} else {
spdiv = metric.superclocks_per_note_type() / beat_div;
}
superclock_t start = (rstart / spdiv) * spdiv; /* div (bar/beat) preceding rstart */
/* determine BBT and beats at the position. Note that we know
* that the tempo and meter must be at zero
*/
BBT_Time bbt (metric.bbt_at (timepos_t::from_superclock (start)));
/* Now round to bar mod or beat_div to keep the grid aligned
* with what has been asked for.
*/
BBT_Time on_bar;
if (rstart != 0) {
if (bar_mod == 1) {
on_bar = bbt.round_up_to_bar ();
} else {
on_bar = metric.meter().round_up_to_beat_div (bbt, beat_div);
}
BBT_Offset delta = Temporal::bbt_delta (on_bar, bbt);
if (delta != BBT_Offset ()) {
bbt = on_bar;
Beats beats_delta = _meters.front().to_quarters (delta);
DEBUG_TRACE (DEBUG::Grid, string_compose ("simple reset start using bbt %1 via %2 (rounded by %3 beats %4) sc %5\n", bbt, on_bar, delta, beats_delta, start));
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("bbt %1 was already on-bar or on-beat %2 start is %3\n", bbt, on_bar, start));
}
}
Beats beats (metric.quarters_at_superclock (start));
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1 with beats %2 for %3\n", start, beats, bbt));
fill_grid_with_final_metric (ret, metric, start, rstart, end, bar_mod, beat_div, beats, bbt);
return _points.end();
}
TempoPoint const * tp = 0;
MeterPoint const * mp = 0;
Points::const_iterator p = _points.begin();
Beats beats;
/* Find relevant meter for nominal start point */
p = get_tempo_and_meter (tp, mp, rstart, true, true);
/* p now points to either the point *after* start, or the end of the
* _points list.
*
* metric is the TempoMetric that is in effect at start
*/
TempoMetric metric = TempoMetric (*tp, *mp);
DEBUG_TRACE (DEBUG::Grid, string_compose ("metric in effect at %1 = %2\n", rstart, metric));
/* determine the BBT at start. We can discard the reftime of a
* BBT_Argument, because it is @var metric that defines it */
BBT_Argument bba = metric.bbt_at (timepos_t::from_superclock (rstart));
BBT_Time bbt (bba.bars, bba.beats, bba.ticks);
/* We know that both the tempo point and meter point that make up @var
* metric are beat and bar aligned respectively (note: if they are a
* MusicTimePoint, they *define* a beat/bar alignment, even if they are
* arbitrarily placed with respect to the earlier elements of the tempo
* map.
*
* So we can just start at the later of the two of them,
*/
superclock_t start;
if (tp->sclock() > mp->sclock()) {
bbt = tp->bbt();
start = tp->sclock();
} else {
bbt = mp->bbt();
start = mp->sclock();
}
/* at this point:
*
* - metric is a TempoMetric that describes the situation at the start time
* - p is an iterator pointin to either the end of the _points list, or
* the next point in the list after start.
*/
fill_grid_by_walking (ret, p, metric, start, rstart, end, bar_mod, beat_div, beats, bbt);
/* reached the end or no more points to consider, so just
* finish by filling the grid to the end, if necessary.
*/
if (start < end) {
fill_grid_with_final_metric (ret, metric, start, rstart, end, bar_mod, beat_div, beats, bbt);
} else {
if (p == _points.end()) {
DEBUG_TRACE (DEBUG::Grid, string_compose ("ended loop with start %1 end %2, p @ END\n", start, end));
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("ended loop with start %1 end %2, p=> %3\n", start, end, *p));
}
}
DEBUG_TRACE (DEBUG::Grid, "<<< GRID DONE\n");
return p;
}
void
TempoMap::get_grid (GridIterator& iter, TempoMapPoints& ret, superclock_t rstart, superclock_t end, uint32_t bar_mod, uint32_t beat_div) const
{
DEBUG_TRACE (DEBUG::Grid, string_compose (">>> GRID-I START %1 .. %2 (barmod = %3) iter valid ? %4 iter for %5\n", rstart, end, bar_mod, iter.valid_for (*this, rstart, bar_mod, beat_div), iter.end));
if (!iter.valid_for (*this, rstart, bar_mod, beat_div)) {
Points::const_iterator p = get_grid (ret, rstart, end, bar_mod, beat_div);
if (!ret.empty()) {
TempoMapPoint& tmp (ret.back());
iter.set (*this, &tmp.tempo(), &tmp.meter(), tmp.sclock(), tmp.beats(), tmp.bbt(), p, end, bar_mod, beat_div);
} else {
iter.catch_up_to (end);
}
return;
}
TempoMetric metric (*iter.tempo, *iter.meter);
superclock_t start = iter.sclock;
Beats beats = iter.beats;
BBT_Time bbt = iter.bbt;
Points::const_iterator p = iter.points_iterator;
fill_grid_by_walking (ret, p, metric, start, rstart, end, bar_mod, beat_div, beats, bbt);
if (start < end) {
fill_grid_with_final_metric (ret, metric, start, rstart, end, bar_mod, beat_div, beats, bbt);
}
if (!ret.empty()) {
TempoMapPoint& tmp (ret.back());
iter.set (*this, &metric.tempo(), &metric.meter(), tmp.sclock(), tmp.beats(), tmp.bbt(), p, end, bar_mod, beat_div);
} else {
iter.catch_up_to (end);
}
DEBUG_TRACE (DEBUG::Grid, "<<< GRID-I DONE\n");
}
void
TempoMap::fill_grid_by_walking (TempoMapPoints& ret, Points::const_iterator& p_i, TempoMetric& metric_i, superclock_t& start, superclock_t rstart, superclock_t end,
int bar_mod, int beat_div, Beats& beats, BBT_Time& bbt) const
{
TempoMetric metric (metric_i);
Points::const_iterator p (p_i);
while (p != _points.end() && start < end) {
MusicTimePoint const *mtp = dynamic_cast<MusicTimePoint const *> (&*p);
/* Generate grid points (either actual meter-defined
* beats, or bars based on bar_mod) up until the next point
* in the map
*/
if (bar_mod != 0) {
if (start >= rstart) {
if (bbt.is_bar() && (bar_mod == 1 || ((bbt.bars % bar_mod == 1)))) {
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
DEBUG_TRACE (DEBUG::Grid, string_compose ("Ga %1\t [%2]\n", metric, ret.back()));
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("-- skip %1 not on bar_mod %2\n", bbt, bar_mod));
}
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("skip-a point @ %1, too early for %2\n", start, rstart));
}
/* Advance by the number of bars specified by bar_mod */
bbt.bars += bar_mod;
} else {
if (start >= rstart) {
if (beat_div == 1) {
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
DEBUG_TRACE (DEBUG::Grid, string_compose ("Gb %1\t [%2]\n", metric, ret.back()));
} else {
int ticks = (bbt.beats * metric.meter().ticks_per_grid()) + bbt.ticks;
int mod = Temporal::ticks_per_beat / beat_div;
if ((ticks % mod) == 0) {
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
DEBUG_TRACE (DEBUG::Grid, string_compose ("Gd %1\t [%2]\n", metric, ret.back()));
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("-- skip %1 not on beat_div_mod %2\n", ticks, mod));
}
}
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("skip-b point @ %1, too early for %2\n", start, rstart));
}
/* Note that in a BBT time, the "beats" count is
* meter-dependent. So if we're in 4/4 time, the beats
* are quarters. If we're in 7/8 time, the beats are in
* 1/8 time, etc.
*/
if (beat_div == 1) {
/* Advance beats by 1 meter-defined "beat */
bbt = metric.bbt_add (bbt, BBT_Offset (0, 1, 0));
} else {
/* Advance beats by a fraction of the * meter-defined "beat" */
bbt = metric.bbt_add (bbt, BBT_Offset (0, 0, Temporal::ticks_per_beat / beat_div));
}
}
DEBUG_TRACE (DEBUG::Grid, string_compose ("pre-check overrun of next point with bbt @ %1 audio %2 point %3\n", bbt, start, *p));
bool reset = false;
if (!mtp) {
if (bbt == p->bbt()) {
// DEBUG_TRACE (DEBUG::Grid, string_compose ("Gc %1\t [%2]\n", metric, ret.back()));
DEBUG_TRACE (DEBUG::Grid, string_compose ("we've reached the next point via BBT, BBT %1 audio %2 point %3\n", bbt, start, *p));
reset = true;
} else if (bbt > p->bbt()) {
DEBUG_TRACE (DEBUG::Grid, string_compose ("we've passed the next point via BBT, BBT %1 audio %2 point %3\n", bbt, start, *p));
reset = true;
}
} else {
/* What does the current metric say about the audio
time position of @v bbt ?
*/
start = metric.superclock_at (bbt);
if (start >= p->sclock()) {
/* Yep, too far. So we need to reset and take
the next (music time point) into account.
*/
DEBUG_TRACE (DEBUG::Grid, string_compose ("we've reached/passed the next point via sclock, BBT %1 audio %2 point %3, using metric %4\n", bbt, start, *p, metric));
reset = true;
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("confirmed that BBT %1 has audio time %2 before next point %3\n", bbt, start, *p));
}
}
DEBUG_TRACE (DEBUG::Grid, string_compose ("check overrun of next point, reset required ? %4 with bbt @ %1 audio %2 point %3\n", bbt, start, *p, (reset ? "YES" : "NO")));
if (reset) {
/* bbt is position for the next grid-line.
*/
TempoPoint const * tp = &metric.tempo();
MeterPoint const * mp = &metric.meter();
if (mtp) {
/* BBT Markers/MusicTimePoints give the user a
* chance to "reset" the BBT ruler. We should
* do the same, unconditionally.
*/
tp = dynamic_cast<TempoPoint const *> (&*p);
mp = dynamic_cast<MeterPoint const *> (&*p);
TEMPO_MAP_ASSERT (tp);
TEMPO_MAP_ASSERT (mp);
metric = TempoMetric (*tp, *mp);
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset metric from music-time point %1, now %2\n", *mtp, metric));
if (p->bbt().ticks != 0) {
/* We do not want an arbitrary off-beat
* BBT marker to interrupt the grid. So
* round up from the marker's BBT time
* to the nearest appropriate beat/bar
* unit, and then reset from there.
*/
BBT_Time on_bar;
if (bar_mod == 1) {
on_bar = p->bbt().round_up_to_bar ();
} else {
on_bar = mp->round_up_to_beat_div (p->bbt(), beat_div);
}
bbt = BBT_Argument (metric.reftime(), on_bar);
BBT_Offset delta = Temporal::bbt_delta (on_bar, p->bbt());
if (delta != BBT_Offset ()) {
Beats beats_delta = mp->to_quarters (delta);
start = tp->superclock_at (tp->beats() + beats_delta);
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start using bbt %1 as %2 via %3 (rounded by %4 beats %5)\n", p->bbt(), bbt, on_bar, delta, beats_delta));
} else {
start = p->sclock();
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start using bbt %1 as %2 via %3 (rounded by %4)\n", p->bbt(), bbt, on_bar, delta));
}
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1\n", start));
} else {
bbt = BBT_Argument (metric.reftime(), p->bbt());
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start using bbt %1 as %2\n", p->bbt(), bbt));
start = p->sclock();
DEBUG_TRACE (DEBUG::Grid, string_compose ("reset start to %1\n", start));
}
/* Advance p to the next point */
++p;
} else {
bool rebuild_metric = false;
DEBUG_TRACE (DEBUG::Grid, string_compose ("iterating over points to find next, terminal is %1\n", bbt));
if (p != _points.end()) {
DEBUG_TRACE (DEBUG::Grid, string_compose ("\tstarting point is %1\n", *p));
} else {
DEBUG_TRACE (DEBUG::Grid, "\treached end already\n");
}
/* Find all points at this BBT time (the next
* grid), then rebuild the TempoMetric with whatever
* we find, so that we will use that going forward.
*/
superclock_t sc = p->sclock();
while (p != _points.end() && p->bbt() <= bbt && p->sclock() == sc) {
TempoPoint const * tpp;
MeterPoint const * mpp;
if ((tpp = dynamic_cast<TempoPoint const *> (&(*p))) != 0) {
rebuild_metric = true;
tp = tpp;
}
if ((mpp = dynamic_cast<MeterPoint const *> (&(*p))) != 0) {
rebuild_metric = true;
mp = mpp;
}
++p;
if (p != _points.end()) {
DEBUG_TRACE (DEBUG::Grid, string_compose ("next point is %1\n", *p));
} else {
DEBUG_TRACE (DEBUG::Grid, "\tthat was that\n");
}
}
/* reset the metric to use the most recent tempo & meter */
if (rebuild_metric) {
metric = TempoMetric (*tp, *mp);
bbt = BBT_Argument (metric.reftime(), bbt);
DEBUG_TRACE (DEBUG::Grid, string_compose ("second| with start = %1 aka %2 rebuilt metric from points, now %3\n", start, bbt, metric));
} else {
DEBUG_TRACE (DEBUG::Grid, string_compose ("not rebuilding metric, continuing with %1\n", metric));
}
}
}
start = metric.superclock_at (bbt);
/* Update the quarter-note time value to match the BBT and
* audio time positions
*/
beats = metric.quarters_at (bbt);
/* we have a candidate grid point (start,beats,bbt). It might
* not be within the range we're generating, but if it is, the iterator
* will need to know where the points_iterator and metric
* parameters ended up before we return..
*/
if (start >= rstart && start < end) {
p_i = p;
metric_i = metric;
}
DEBUG_TRACE (DEBUG::Grid, string_compose ("moved to %1 qn %2 sc %3) using metric %4 p at end %5\n", bbt, beats, start, metric, p == _points.end()));
}
}
void
TempoMap::fill_grid_with_final_metric (TempoMapPoints& ret, TempoMetric metric, superclock_t start, superclock_t rstart, superclock_t end, int bar_mod, int beat_div, Beats beats, BBT_Time bbt) const
{
DEBUG_TRACE (DEBUG::Grid, string_compose ("reached end, no more map points, use %5 to finish between %1 .. %2 initial bbt %3, beats %4\n", start, end, bbt, beats.str(), metric));
while (start < end) {
DEBUG_TRACE (DEBUG::Grid, string_compose ("bar mod %1 moved to %2 qn %3 sc %4)\n", bar_mod, bbt, beats, start));
/* It is possible we already added the current BBT
* point, so check to avoid doubling up
*/
if (bar_mod != 0) {
if ((start >= rstart) && bbt.is_bar() && (bar_mod == 1 || ((bbt.bars % bar_mod == 1)))) {
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
DEBUG_TRACE (DEBUG::Grid, string_compose ("GendA %1\t %2\n", metric, ret.back()));
}
/* Advance by the number of bars specified by
bar_mod, then recompute the beats and
superclock position corresponding to that
BBT time.
*/
bbt.bars += bar_mod;
} else {
if (start >= rstart) {
if (beat_div == 1) {
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
DEBUG_TRACE (DEBUG::Grid, string_compose ("Gendb %1\t [%2]\n", metric, ret.back()));
} else {
int ticks = (bbt.beats * metric.meter().ticks_per_grid()) + bbt.ticks;
int mod = Temporal::ticks_per_beat / beat_div;
if ((ticks % mod) == 0) {
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
DEBUG_TRACE (DEBUG::Grid, string_compose ("Gendd %1\t [%2]\n", metric, ret.back()));
}
}
}
/* move on by 1 meter-defined "beat" */
if (beat_div == 1) {
bbt = metric.bbt_add (bbt, BBT_Offset (0, 1, 0));
} else {
bbt = metric.bbt_add (bbt, BBT_Offset (0, 0, Temporal::ticks_per_beat / beat_div));
}
}
/* compute audio and quarter-note time from the new BBT position */
start = metric.superclock_at (bbt);
beats = metric.quarters_at (bbt);
}
}
std::ostream&
std::operator<<(std::ostream& str, Meter const & m)
{
return str << m.divisions_per_bar() << '/' << m.note_value();
}
std::ostream&
std::operator<<(std::ostream& str, Tempo const & t)
{
if (t.ramped()) {
return str << t.note_types_per_minute() << " .. " << t.end_note_types_per_minute() << " 1/" << t.note_type() << " RAMPED notes per minute (" << t.superclocks_per_note_type() << " .. " << t.end_superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')';
} else {
return str << t.note_types_per_minute() << " 1/" << t.note_type() << " notes per minute (" << t.superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')';
}
}
std::ostream&
std::operator<<(std::ostream& str, Point const & p)
{
return str << "P@" << p.sclock() << '/' << p.beats() << '/' << p.bbt();
}
std::ostream&
std::operator<<(std::ostream& str, MeterPoint const & m)
{
return str << *((Meter const *) &m) << ' ' << *((Point const *) &m);
}
std::ostream&
std::operator<<(std::ostream& str, TempoPoint const & t)
{
str << *((Tempo const *) &t) << ' ' << *((Point const *) &t);
if (t.ramped()) {
if (t.actually_ramped()) {
str << ' ' << " ramp to " << t.end_note_types_per_minute();
} else {
str << ' ' << " !ramp to " << t.end_note_types_per_minute();
}
str << " omega = " << std::setprecision(12) << t.omega();
}
return str;
}
std::ostream&
std::operator<<(std::ostream& str, MusicTimePoint const & p)
{
str << "MP @ "
<< *((Point const *) &p) << ' '
<< *((Tempo const *) &p) << ' '
<< *((Meter const *) &p);
return str;
}
std::ostream&
std::operator<<(std::ostream& str, TempoMetric const & tm)
{
return str << tm.tempo() << ' ' << tm.meter();
}
std::ostream&
std::operator<<(std::ostream& str, TempoMapPoint const & tmp)
{
str << '@' << std::setw (12) << tmp.sclock() << ' ' << tmp.sclock() / (double) superclock_ticks_per_second()
<< " secs " << tmp.sample (TEMPORAL_SAMPLE_RATE) << " samples"
<< (tmp.is_explicit_tempo() ? " EXP-T" : " imp-t")
<< (tmp.is_explicit_meter() ? " EXP-M" : " imp-m")
<< (tmp.is_explicit_position() ? " EXP-P" : " imp-p")
<< " qn " << tmp.beats ()
<< " bbt " << tmp.bbt()
;
if (tmp.is_explicit_tempo()) {
str << " tempo " << tmp.tempo();
}
if (tmp.is_explicit_meter()) {
str << " meter " << tmp.meter();
}
if (tmp.is_explicit_tempo() && tmp.tempo().ramped()) {
str << " ramp omega(beats) = " << tmp.tempo().omega();
}
return str;
}
BBT_Argument
TempoMap::bbt_walk (BBT_Argument const & bbt, BBT_Offset const & o) const
{
BBT_Offset offset (o);
BBT_Time start (bbt);
Tempos::const_iterator t, prev_t, next_t;
Meters::const_iterator m, prev_m, next_m;
TEMPO_MAP_ASSERT (!_tempos.empty());
TEMPO_MAP_ASSERT (!_meters.empty());
/* trivial (and common) case: single tempo, single meter */
if (_tempos.size() == 1 && _meters.size() == 1) {
return BBT_Argument (_meters.front().bbt_add (bbt, o));
}
/* Find tempo,meter pair for bbt, and also for the next tempo and meter
* after each (if any)
*/
/* Yes, linear search because the typical size of _tempos and _meters
* is 1, and extreme sizes are on the order of 10
*/
next_t = _tempos.end();
next_m = _meters.end();
for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->bbt() < bbt;) {
prev_t = t;
++t;
if (t != _tempos.end()) {
next_t = t;
++next_t;
}
}
for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->bbt() < bbt;) {
prev_m = m;
++m;
if (m != _meters.end()) {
next_m = m;
++next_m;
}
}
/* may have found tempo and/or meter precisely at the time given */
if (t != _tempos.end() && t->bbt() == bbt) {
prev_t = t;
}
if (m != _meters.end() && m->bbt() == bbt) {
prev_m = m;
}
/* see ::metric_at() for comments about the use of const_cast here
*/
TempoMetric metric (*const_cast<TempoPoint*>(&*prev_t), *const_cast<MeterPoint*>(&*prev_m));
/* normalize possibly too-large ticks count */
const int32_t tpg = metric.meter().ticks_per_grid ();
if (offset.ticks > tpg) {
/* normalize */
offset.beats += offset.ticks / tpg;
offset.ticks %= tpg;
}
/* add each beat, 1 by 1, rechecking to see if there's a new
* TempoMetric in effect after each addition
*/
#define TEMPO_CHECK_FOR_NEW_METRIC \
{ \
/* need new metric */ \
bool advance_t = false; \
bool advance_m = false; \
if (next_t != _tempos.end() && (start >= next_t->bbt())) { \
advance_t = true; \
}\
if (next_m != _meters.end() && (start >= next_m->bbt())) { \
advance_m = true; \
} \
if (advance_t && advance_m) { \
metric = TempoMetric (*const_cast<TempoPoint*>(&*next_t), *const_cast<MeterPoint*>(&*next_m)); \
++next_t; \
++next_m; \
} else if (advance_t && !advance_m) { \
metric = TempoMetric (*const_cast<TempoPoint*>(&*next_t), metric.meter()); \
++next_t; \
} else if (advance_m && !advance_t) { \
metric = TempoMetric (metric.tempo(), *const_cast<MeterPoint*>(&*next_m)); \
++next_m; \
} \
}
for (int32_t b = 0; b < offset.bars; ++b) {
TEMPO_CHECK_FOR_NEW_METRIC;
start.bars += 1;
}
for (int32_t b = 0; b < offset.beats; ++b) {
TEMPO_CHECK_FOR_NEW_METRIC;
start.beats += 1;
if (start.beats > metric.divisions_per_bar()) {
start.bars += 1;
start.beats = 1;
}
}
start.ticks += offset.ticks;
if (start.ticks >= ticks_per_beat) {
start.beats += 1;
start.ticks %= ticks_per_beat;
}
return BBT_Argument (metric.reftime(), start);
}
Temporal::Beats
TempoMap::quarters_at (timepos_t const & pos) const
{
if (pos.is_beats()) {
/* a bit redundant */
return pos.beats();
}
return quarters_at_superclock (pos.superclocks());
}
Temporal::Beats
TempoMap::quarters_at (Temporal::BBT_Argument const & bbt) const
{
return metric_at (bbt).quarters_at (bbt);
}
Temporal::Beats
TempoMap::quarters_at_superclock (superclock_t pos) const
{
return metric_at (pos).quarters_at_superclock (pos);
}
XMLNode&
TempoMap::get_state () const
{
XMLNode* node = new XMLNode (X_("TempoMap"));
node->set_property (X_("superclocks-per-second"), superclock_ticks_per_second());
XMLNode* children;
children = new XMLNode (X_("Tempos"));
node->add_child_nocopy (*children);
for (Tempos::const_iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
if (!dynamic_cast<MusicTimePoint const *> (&(*t))) {
children->add_child_nocopy (t->get_state());
}
}
children = new XMLNode (X_("Meters"));
node->add_child_nocopy (*children);
for (Meters::const_iterator m = _meters.begin(); m != _meters.end(); ++m) {
if (!dynamic_cast<MusicTimePoint const *> (& (*m))) {
children->add_child_nocopy (m->get_state());
}
}
children = new XMLNode (X_("MusicTimes"));
node->add_child_nocopy (*children);
for (MusicTimes::const_iterator b = _bartimes.begin(); b != _bartimes.end(); ++b) {
children->add_child_nocopy (b->get_state());
}
return *node;
}
int
TempoMap::set_state (XMLNode const & node, int version)
{
if (version <= 6000) {
return set_state_3x (node);
}
/* global map properties */
/* XXX this should probably be at the global level in the session file
* because it is the time unit for anything in the audio time domain,
* and affects a lot more than just the tempo map
*/
superclock_t sc;
if (node.get_property (X_("superclocks-per-second"), sc)) {
set_superclock_ticks_per_second (sc);
}
XMLNodeList const & children (node.children());
/* XXX might be good to have a recovery mechanism in case setting
* things from XML fails. Not very likely, however.
*/
_tempos.clear ();
_meters.clear ();
_bartimes.clear ();
_points.clear ();
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
if ((*c)->name() == X_("Tempos")) {
if (set_tempos_from_state (**c)) {
return -1;
}
}
if ((*c)->name() == X_("Meters")) {
if (set_meters_from_state (**c)) {
return -1;
}
}
if ((*c)->name() == X_("MusicTimes")) {
if (set_music_times_from_state (**c)) {
return -1;
}
}
}
return 0;
}
int
TempoMap::set_music_times_from_state (XMLNode const& mt_node)
{
XMLNodeList const & children (mt_node.children());
try {
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
MusicTimePoint* mp = new MusicTimePoint (*this, **c);
add_or_replace_bartime (mp);
}
} catch (...) {
_bartimes.clear (); /* remove any that were created */
return -1;
}
return 0;
}
int
TempoMap::set_tempos_from_state (XMLNode const& tempos_node)
{
XMLNodeList const & children (tempos_node.children());
bool ignore;
try {
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
TempoPoint* tp = new TempoPoint (*this, **c);
core_add_tempo (tp, ignore);
core_add_point (tp);
}
} catch (...) {
_tempos.clear (); /* remove any that were created */
return -1;
}
return 0;
}
int
TempoMap::set_meters_from_state (XMLNode const& meters_node)
{
XMLNodeList const & children (meters_node.children());
bool ignore;
try {
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
MeterPoint* mp = new MeterPoint (*this, **c);
core_add_meter (mp, ignore);
core_add_point (mp);
}
} catch (...) {
_meters.clear (); /* remove any that were created */
return -1;
}
return 0;
}
bool
TempoMap::can_remove (TempoPoint const & t) const
{
return !is_initial (t);
}
bool
TempoMap::is_initial (TempoPoint const & t) const
{
return t.sclock() == 0;
}
bool
TempoMap::is_initial (MeterPoint const & m) const
{
return m.sclock() == 0;
}
bool
TempoMap::can_remove (MeterPoint const & m) const
{
return !is_initial (m);
}
/** returns the duration (using the domain of @p pos) of the supplied BBT time at a specified sample position in the tempo map.
* @param pos the frame position in the tempo map.
* @param bbt the distance in BBT time from pos to calculate.
* @param dir the rounding direction..
* @return the timecnt_t that @p bbt represents when starting at @p pos, in
* the time domain of @p pos
*/
timecnt_t
TempoMap::bbt_duration_at (timepos_t const & pos, BBT_Offset const & dur) const
{
if (pos.time_domain() == AudioTime) {
return timecnt_t::from_superclock (superclock_at (bbt_walk (bbt_at (pos), dur)) - pos.superclocks(), pos);
}
return timecnt_t (bbtwalk_to_quarters (pos.beats(), dur), pos);
}
/** Takes a position and distance (both in any time domain), and returns a timecnt_t
* that describes that distance from that position in a specified time domain.
*
* This method is used when converting a (distance,pos) pair (a timecnt_t) into
* (distance,pos') in a different time domain. For an english example: "given a
* distance of N beats from position P, what is that same distance measured in
* superclocks from P' ?"
*
* A different example: "given a distance N superclocks from position P, what
* is that same distance measured in beats from P' ?"
*
* There is a trivial case in which the requested return domain is the same as
* the domain for the distance. In english: "given a distance of N beats from
* P, what is the same distance measured in beats from P' ?" In this case, we
* can simply construct a new timecnt_t that uses P' instead of P, since the
* distance component must necessarily be same. Notice that this true no matter
* what domain is used.
*/
timecnt_t
TempoMap::convert_duration (timecnt_t const & duration, timepos_t const & new_position, TimeDomain return_domain) const
{
timepos_t p (return_domain);
Beats b;
superclock_t s;
if (return_domain == duration.time_domain()) {
/* new timecnt_t: same distance, but new position */
return timecnt_t (duration.distance(), new_position);
}
switch (return_domain) {
case AudioTime:
switch (duration.time_domain()) {
case AudioTime:
/*NOTREACHED*/
break;
case BeatTime:
/* duration is in beats but we're asked to return superclocks */
switch (new_position.time_domain()) {
case BeatTime:
/* new_position is already in beats */
p = new_position;
break;
case AudioTime:
/* Determine beats at sc pos, so that we can add beats */
p = timepos_t (metric_at (new_position).quarters_at_superclock (new_position.superclocks()));
break;
}
/* add beats */
p += duration;
/* determine superclocks */
s = metric_at (p).superclock_at (p.beats());
/* return duration in sc */
return timecnt_t::from_superclock (s - new_position.superclocks(), new_position);
break;
}
break;
case BeatTime:
switch (duration.time_domain()) {
case AudioTime:
/* duration is in superclocks but we're asked to return beats */
switch (new_position.time_domain ()) {
case AudioTime:
/* pos is already in superclocks */
p = new_position;
break;
case BeatTime:
/* determined sc at beat position so we can add superclocks */
p = timepos_t (metric_at (new_position).sample_at (new_position.beats()));
break;
}
/* add superclocks */
p += duration;
/* determine beats */
b = metric_at (p).quarters_at_superclock (p.superclocks());
/* return duration in beats */
return timecnt_t (b - new_position.beats(), new_position);
break;
case BeatTime:
/*NOTREACHED*/
break;
}
break;
}
/*NOTREACHED*/
abort ();
/*NOTREACHED*/
return timecnt_t::from_superclock (0);
}
uint32_t
TempoMap::n_meters () const
{
return _meters.size();
}
uint32_t
TempoMap::n_tempos () const
{
return _tempos.size();
}
bool
TempoMap::remove_time (timepos_t const & pos, timecnt_t const & duration)
{
superclock_t start (pos.superclocks());
superclock_t end ((pos + duration).superclocks());
superclock_t shift (duration.superclocks());
TempoPoint* last_tempo = 0;
MeterPoint* last_meter = 0;
TempoPoint* tempo_after = 0;
MeterPoint* meter_after = 0;
bool moved = false;
for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ) {
if (t->sclock() >= start && t->sclock() < end) {
last_tempo = &*t;
t = _tempos.erase (t);
moved = true;
} else if (t->sclock() >= start) {
t->set (t->sclock() - shift, t->beats(), t->bbt());
moved = true;
if (t->sclock() == start) {
tempo_after = &*t;
}
++t;
}
}
for (Meters::iterator m = _meters.begin(); m != _meters.end(); ) {
if (m->sclock() >= start && m->sclock() < end) {
last_meter = &*m;
m = _meters.erase (m);
moved = true;
} else if (m->sclock() >= start) {
m->set (m->sclock() - shift, m->beats(), m->bbt());
moved = true;
if (m->sclock() == start) {
meter_after = &*m;
}
++m;
}
}
if (last_tempo && !tempo_after) {
last_tempo->set (start, last_tempo->beats(), last_tempo->bbt());
moved = true;
}
if (last_meter && !meter_after) {
last_tempo->set (start, last_meter->beats(), last_meter->bbt());
moved = true;
}
if (moved) {
reset_starting_at (start);
}
return moved;
}
TempoPoint const *
TempoMap::next_tempo (TempoPoint const & t) const
{
Tempos::const_iterator i = _tempos.iterator_to (t);
++i;
if (i != _tempos.end()) {
return &(*i);
}
return 0;
}
TempoPoint const *
TempoMap::previous_tempo (TempoPoint const & point) const
{
Tempos::const_iterator i = _tempos.iterator_to (point);
if (i == _tempos.begin()) {
return 0;
}
--i;
return &(*i);
}
MeterPoint const *
TempoMap::next_meter (MeterPoint const & t) const
{
Meters::const_iterator i = _meters.iterator_to (t);
++i;
if (i != _meters.end()) {
return &(*i);
}
return 0;
}
MeterPoint const *
TempoMap::previous_meter (MeterPoint const & point) const
{
Meters::const_iterator i = _meters.iterator_to (point);
if (i == _meters.begin()) {
return 0;
}
--i;
return &(*i);
}
double
TempoMap::quarters_per_minute_at (timepos_t const & pos) const
{
TempoPoint const & tp (tempo_at (pos));
const double val = tp.note_types_per_minute_at_DOUBLE (pos) * (4.0 / tp.note_type());
return val;
}
TempoPoint const &
TempoMap::tempo_at (timepos_t const & pos) const
{
return pos.is_beats() ? tempo_at (pos.beats()) : tempo_at (pos.superclocks());
}
MeterPoint const &
TempoMap::meter_at (timepos_t const & pos) const
{
return pos.is_beats() ? meter_at (pos.beats()) : meter_at (pos.superclocks());
}
TempoMetric
TempoMap::metric_at (timepos_t const & pos) const
{
if (pos.is_beats()) {
return metric_at (pos.beats());
}
return metric_at (pos.superclocks());
}
TempoMetric
TempoMap::metric_at (superclock_t sc, bool can_match) const
{
TempoPoint const * tp = 0;
MeterPoint const * mp = 0;
(void) get_tempo_and_meter (tp, mp, sc, can_match, false);
return TempoMetric (*tp,* mp);
}
TempoMetric
TempoMap::metric_at (Beats const & b, bool can_match) const
{
TempoPoint const * tp = 0;
MeterPoint const * mp = 0;
(void) get_tempo_and_meter (tp, mp, b, can_match, false);
return TempoMetric (*tp, *mp);
}
TempoMetric
TempoMap::metric_at (BBT_Argument const & bbt, bool can_match) const
{
TempoPoint const * tp = 0;
MeterPoint const * mp = 0;
/* Since the reference time of a BBT_Argument is the time of the
* latest tempo/meter marker before or at BBT, we can use the reference
* time to get the metric.
*/
(void) get_tempo_and_meter (tp, mp, bbt, can_match, false);
return TempoMetric (*tp, *mp);
}
bool
TempoMap::set_ramped (TempoPoint & tp, bool yn)
{
TEMPO_MAP_ASSERT (!_tempos.empty());
if (tp.ramped() == yn) {
return false;
}
Tempos::iterator nxt = _tempos.begin();
++nxt;
for (Tempos::iterator t = _tempos.begin(); nxt != _tempos.end(); ++t, ++nxt) {
if (tp == *t) {
break;
}
}
if (nxt == _tempos.end()) {
return false;
}
if (yn) {
tp.set_end_npm (nxt->end_note_types_per_minute());
} else {
tp.set_end_npm (tp.note_types_per_minute());
}
reset_starting_at (tp.sclock());
return true;
}
bool
TempoMap::set_continuing (TempoPoint& tp, bool yn)
{
if (!yn) {
tp.set_continuing (false);
return true; /* change made */
}
TempoPoint const * prev = previous_tempo (tp);
if (!prev) {
return false;
}
tp.set_note_types_per_minute (prev->note_types_per_minute());
return true;
}
#if 1
void
TempoMap::stretch_tempo (TempoPoint& focus, double tempo_value)
{
/* Our goal is to alter the outound tempo at @param focus and at the same
* time create & modify a ramp between the previous tempo and @param focus
* so that @param remains in the same location.
*
* The user has placed @param focus at the correct point, but wanfocus to
* adjust the (outbound) tempo without creating an obvious step change
* at @param focus. So we want to ramp from prev to focus
*/
TempoPoint* prev = const_cast<TempoPoint*> (previous_tempo (focus));
TempoPoint old_prev (*prev);
TempoPoint old_focus (focus);
focus.set_note_types_per_minute (tempo_value);
focus.set_end_npm (tempo_value);
prev->set_end_npm (tempo_value);
prev->compute_omega_from_next_tempo (focus);
superclock_t err = prev->superclock_at (focus.beats()) - focus.sclock();
const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE;
// const double end_scpqn = focus.superclocks_per_quarter_note();
double scpqn = focus.superclocks_per_quarter_note ();
double new_npm;
int cnt = 0;
reset_starting_at (prev->sclock());;
return;
while (std::abs(err) >= one_sample) {
if (err > 0) {
/* estimated > actual: speed end tempo up a little aka
reduce scpqn
*/
scpqn *= 0.99;
} else {
/* estimated < actual: reduce end tempo a little, aka
increase scpqn
*/
scpqn *= 1.01;
}
if (scpqn < 1.0) {
/* mathematically too small, bail out */
*prev = old_prev;
focus = old_focus;
return;
}
/* Convert scpqn to notes-per-minute */
new_npm = ((superclock_ticks_per_second() * 60.0) / scpqn) * (focus.note_type() / 4.0);
/* limit range of possible discovered tempo */
if (new_npm < 4.0 && new_npm > 400) {
/* too low of a tempo for our taste, bail out */
*prev = old_prev;
focus = old_focus;
return;
}
/* set the (initial) tempo, recompute omega and then compute
* the (new) error (distance between the predicted position of
* the next marker and its actual (fixed) position.
*/
focus.set_note_types_per_minute (new_npm);
focus.set_end_npm (new_npm);
prev->set_end_npm (new_npm);
prev->compute_omega_from_next_tempo (focus);
err = prev->superclock_at (focus.beats()) - focus.sclock();
++cnt;
}
// std::cerr << "that took " << cnt << " iterations to get to < 1 sample\n";
// std::cerr << "final focus: " << focus << std::endl;
// std::cerr << "final prev: " << *prev << std::endl;
reset_starting_at (prev->sclock());
// dump (std::cerr);
}
#else
/* Adjusts the outgoing tempo at @p ts so that the next Tempo point is at @p
* end_sample, while keeping the beat time positions of both the same.
*
* i.e. literally "stretches" out a tempo section (between two markers) by
* speeding or slowing the initial outbound tempo and ramping to the end
* tempo.
*/
void
TempoMap::stretch_tempo (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample, Beats const & start_qnote, Beats const & end_qnote)
{
/*
Ts (future prev_t) Tnext
| |
| [drag^] |
|----------|----------
e_f qn_beats(sample)
*/
if (!ts) {
return;
}
TempoPoint* next_t = const_cast<TempoPoint*> (next_tempo (*ts));
/* no stretching of the final tempo, where final includes "terminated
* by a BBT marker"
*/
if (!next_t || dynamic_cast<MusicTimePoint*> (next_t)) {
return;
}
superclock_t start_sclock = samples_to_superclock (sample, TEMPORAL_SAMPLE_RATE);
superclock_t end_sclock = samples_to_superclock (end_sample, TEMPORAL_SAMPLE_RATE);
/* minimum allowed measurement distance in samples */
const superclock_t min_delta_sclock = samples_to_superclock (2, TEMPORAL_SAMPLE_RATE);
double new_bpm;
if (ts->continuing()) {
/* this tempo point is required to start using the same bpm
* that the previous tempo ended with.
*/
TempoPoint* prev_to_ts = const_cast<TempoPoint*> (previous_tempo (*ts));
TEMPO_MAP_ASSERT (prev_to_ts);
/* the change in samples is the result of changing the slope of at most 2 previous tempo sections.
* constant to constant is straightforward, as the tempo prev to ts has constant slope.
*/
double contribution = 0.0;
if (next_t && prev_to_ts->ramped()) {
const DoubleableBeats delta_tp = ts->beats() - prev_to_ts->beats();
const DoubleableBeats delta_np = next_t->beats() - prev_to_ts->beats();
contribution = delta_tp.to_double() / delta_np.to_double();
}
samplepos_t const fr_off = end_sclock - start_sclock;
sampleoffset_t const ts_sample_contribution = fr_off - (contribution * (double) fr_off);
if (start_sclock > prev_to_ts->sclock() + min_delta_sclock && (start_sclock + ts_sample_contribution) > prev_to_ts->sclock() + min_delta_sclock) {
DoubleableBeats delta_sp = start_qnote - prev_to_ts->beats();
DoubleableBeats delta_ep = end_qnote - prev_to_ts->beats();
new_bpm = ts->note_types_per_minute() * (delta_sp.to_double() / delta_ep.to_double());
} else {
new_bpm = ts->note_types_per_minute();
}
} else {
/* ts is free to have it's bpm changed to any value (within limits) */
if (start_sclock > ts->sclock() + min_delta_sclock && end_sclock > ts->sclock() + min_delta_sclock) {
new_bpm = ts->note_types_per_minute() * ((start_sclock - ts->sclock()) / (double) (end_sclock - ts->sclock()));
} else {
new_bpm = ts->note_types_per_minute();
}
new_bpm = std::min (new_bpm, 1000.0);
}
/* don't clamp and proceed here.
testing has revealed that this can go negative,
which is an entirely different thing to just being too low.
*/
if (new_bpm < 0.5) {
return;
}
ts->set_note_types_per_minute (new_bpm);
if (ts->continuing()) {
TempoPoint* prev = 0;
if ((prev = const_cast<TempoPoint*> (previous_tempo (*ts))) != 0) {
prev->set_end_npm (ts->end_note_types_per_minute());
}
}
reset_starting_at (ts->sclock() + 1);
}
#endif
void
TempoMap::stretch_tempo_end (TempoPoint* ts, samplepos_t sample, samplepos_t end_sample)
{
/*
Ts (future prev_t) Tnext
| |
| [drag^] |
|----------|----------
e_f qn_beats(sample)
*/
if (!ts) {
return;
}
const superclock_t start_sclock = samples_to_superclock (sample, TEMPORAL_SAMPLE_RATE);
const superclock_t end_sclock = samples_to_superclock (end_sample, TEMPORAL_SAMPLE_RATE);
TempoPoint * prev_t = const_cast<TempoPoint*> (previous_tempo (*ts));
if (!prev_t) {
return;
}
/* minimum allowed measurement distance in superclocks */
const superclock_t min_delta_sclock = samples_to_superclock (2, TEMPORAL_SAMPLE_RATE);
double new_bpm;
if (start_sclock > prev_t->sclock() + min_delta_sclock && end_sclock > prev_t->sclock() + min_delta_sclock) {
new_bpm = prev_t->end_note_types_per_minute() * ((prev_t->sclock() - start_sclock) / (double) (prev_t->sclock() - end_sclock));
} else {
new_bpm = prev_t->end_note_types_per_minute();
}
new_bpm = std::min (new_bpm, (double) 1000.0);
if (new_bpm < 0.5) {
return;
}
prev_t->set_end_npm (new_bpm);
if (ts->continuing()) {
ts->set_note_types_per_minute (prev_t->note_types_per_minute());
}
reset_starting_at (prev_t->sclock());
}
bool
TempoMap::solve_ramped_twist (TempoPoint& earlier, TempoPoint& later)
{
superclock_t err = earlier.superclock_at (later.beats()) - later.sclock();
const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE;
double end_scpqn = earlier.end_superclocks_per_quarter_note();
double new_end_npm;
int cnt = 0;
while (std::abs(err) >= one_sample) {
if (err > 0) {
/* estimated > actual: speed end tempo up a little aka
reduce scpqn
*/
end_scpqn *= 0.99;
} else {
/* estimated < actual: reduce end tempo a little, aka
increase scpqn
*/
end_scpqn *= 1.01;
}
if (end_scpqn < 1.0) {
/* mathematically too small, bail out */
return false;
}
/* Convert scpqn to notes-per-minute */
new_end_npm = ((superclock_ticks_per_second() * 60.0) / end_scpqn) * (earlier.note_type() / 4.0);
/* limit range of possible discovered tempo */
if (new_end_npm < 4.0 && new_end_npm > 400) {
/* too low of a tempo for our taste, bail out */
return false;
}
/* set the (initial) tempo, recompute omega and then compute
* the (new) error (distance between the predicted position of
* the later marker and its actual (fixed) position.
*/
earlier.set_end_npm (new_end_npm);
earlier.compute_omega_from_next_tempo (later);
err = earlier.superclock_at (later.beats()) - later.sclock();
if (cnt > 20000) {
// std::cerr << "nn: " << new_end_npm << " err " << err << " @ " << cnt << "solve_ramped_twist FAILED\n";
return false;
}
++cnt;
}
// std::cerr << "that took " << cnt << " iterations to get to < 1 sample\n";
return true;
}
bool
TempoMap::solve_constant_twist (TempoPoint& earlier, TempoPoint& later)
{
superclock_t err = earlier.superclock_at (later.beats()) - later.sclock();
const superclock_t one_sample = superclock_ticks_per_second() / TEMPORAL_SAMPLE_RATE;
double start_npm = earlier.superclocks_per_quarter_note ();
int cnt = 0;
while (std::abs(err) >= one_sample) {
if (err > 0) {
/* estimated > actual: speed end tempo up a little aka
reduce scpqn
*/
start_npm *= 0.99;
} else {
/* estimated < actual: reduce end tempo a little, aka
increase scpqn
*/
start_npm *= 1.01;
}
/* Convert scpqn to notes-per-minute */
double new_npm = ((superclock_ticks_per_second() * 60.0) / start_npm) * (earlier.note_type() / 4.0);
/* limit range of possible discovered tempo */
if (new_npm < 4.0 && new_npm > 400) {
/* too low of a tempo for our taste, bail out */
return false;
}
/* set the (initial) tempo, and then compute
* the (new) error (distance between the predicted position of
* the later marker and its actual (fixed) position.
*/
earlier.set_note_types_per_minute (new_npm);
earlier.set_end_npm (new_npm);
err = earlier.superclock_at (later.beats()) - later.sclock();
if (cnt > 20000) {
// std::cerr << "nn: " << new_npm << " err " << err << " @ " << cnt << "solve_constant_twist FAILED\n";
return false;
}
++cnt;
}
// std::cerr << "that took " << cnt << " iterations to get to < 1 sample\n";
return true;
}
void
TempoMap::constant_twist_tempi (TempoPoint& prev, TempoPoint& focus, TempoPoint& next, double tempo_value)
{
/* Check if the new tempo value is within an acceptable range */
if (tempo_value < 4.0 || tempo_value > 400) {
std::cerr << "can't set tempo to " << tempo_value << " ....fail\n";
return;
}
TempoPoint old_prev (prev);
TempoPoint old_focus (focus);
/* Our job here is to reposition @param focus without altering the
* positions of @param prev and @param next. We do this by changing
* the tempo of prev (as opposed to ramped_twist_tempi, below )
*/
/* set a fixed tempo for the previous marker (this results in 'focus' moving a bit with the mouse) */
prev.set_note_types_per_minute (tempo_value);
prev.set_end_npm (tempo_value);
/* reposition focus, using prev to define audio time; leave beat time
* and BBT alone
*/
focus.set (prev.superclock_at (focus.beats()), focus.beats(), focus.bbt());
/* Now iteratively adjust focus.superclocks_per_quarter_note() (the
* section's starting tempo) so that next.sclock() remains within 1
* sample of its current position
*/
if (!solve_constant_twist (focus, next)) {
prev = old_prev;
focus = old_focus;
return;
}
}
void
TempoMap::ramped_twist_tempi (TempoPoint& unused, TempoPoint& focus, TempoPoint& next, double tempo_value)
{
/* Check if the new tempo value is within an acceptable range */
if (tempo_value < 4.0 || tempo_value > 400) {
return;
}
/* Our job here is to tweak the ramp of @param focus without
* altering the positions of @param focus and @param next.
* We are "twisting" the tempo section between those markers
* to enact a change but without moving the markers themselves
*
* Start by saving the current state of focus in case we need
* to bail out because change is impossible.
*/
TempoPoint old_focus (focus);
/* set start tempo of prev tempo marker; we will iteratively solve for the required ramp value */
focus.set_note_types_per_minute (tempo_value);
if (!solve_ramped_twist (focus, next)) {
focus = old_focus;
return;
}
}
void
TempoMap::init ()
{
WritableSharedPtr new_map (new TempoMap ());
_map_mgr.init (new_map);
fetch ();
}
TempoMap::WritableSharedPtr
TempoMap::write_copy()
{
return _map_mgr.write_copy();
}
int
TempoMap::update (TempoMap::WritableSharedPtr m)
{
if (!_map_mgr.update (m)) {
return -1;
}
/* update thread local map pointer in the calling thread */
update_thread_tempo_map ();
#ifndef NDEBUG
if (DEBUG_ENABLED (DEBUG::TemporalMap)) {
m->dump (std::cerr);
}
#endif
MapChanged (); /* EMIT SIGNAL */
return 0;
}
void
TempoMap::abort_update ()
{
/* drop lock taken by write_copy() */
_map_mgr.abort ();
/* update thread local map pointer in calling thread. Note that this
will reset _tempo_map_p, which is (almost guaranteed to be) the only
reference to the copy of the map made in ::write_copy(), so it will
be destroyed here.
*/
TempoMap::fetch ();
}
void
TempoMap::midi_clock_beat_at_or_after (samplepos_t const pos, samplepos_t& clk_pos, uint32_t& clk_beat) const
{
/* Sequences are always assumed to start on a MIDI Beat of 0 (ie, the downbeat).
*
* There are 24 MIDI clock per quarter note (1 Temporal::Beat)
*
* from http://midi.teragonaudio.com/tech/midispec/seq.htm
*/
superclock_t sc (samples_to_superclock (pos, TEMPORAL_SAMPLE_RATE));
TempoPoint const & tp (tempo_at (sc));
Temporal::Beats b = (tp.quarters_at_sample (pos)).round_up_to_beat ();
again:
/* We cannot use
* clk_pos = sample_at (b);
* because in this case we have to round up to the start
* of the next tick, not round to to the current tick.
* (compare to 14da117bc88)
*/
clk_pos = PBD::muldiv_round (superclock_at (b), TEMPORAL_SAMPLE_RATE, superclock_ticks_per_second ());
/* Each MIDI Beat spans 6 MIDI Clocks.
* In other words, each MIDI Beat is a 16th note (since there are 24 MIDI
* Clocks in a quarter note, therefore 4 MIDI Beats also fit in a quarter).
* So, a master can sync playback to a resolution of any particular 16th note.
*/
clk_beat = b.get_beats () * 4 ; // 4 = 24 / 6;
/* It can happen that the computed beat is actually slightly before
* pos. For example, if @p pos is 441002, this is less than 1 tick
* beyond beat 20 (at 120bpm, 44100Hz, 4/4). The call to
* quarters_at_sample(pos) will return 20:0, which does not (and
* cannot) be rounded up). But when we convert that back to a sample
* time, we get 441000, which is before the time (@p pos) that we are
* meant to be computing for. So .. advance to the next beat, and do it
* again.
*/
if (clk_pos < pos) {
b += Beats (1, 0);
goto again;
}
}
/******** OLD STATE LOADING CODE SECTION *************/
#if 0
static bool
bbt_time_to_string (const BBT_Time& bbt, std::string& str)
{
char buf[256];
int retval = snprintf (buf, sizeof(buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, bbt.bars, bbt.beats,
bbt.ticks);
if (retval <= 0 || retval >= (int)sizeof(buf)) {
return false;
}
str = buf;
return true;
}
#endif
static bool
string_to_bbt_time (const std::string& str, BBT_Time& bbt)
{
if (sscanf (str.c_str (), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &bbt.bars, &bbt.beats,
&bbt.ticks) == 3) {
return true;
}
return false;
}
int
TempoMap::parse_tempo_state_3x (const XMLNode& node, LegacyTempoState& lts)
{
BBT_Time bbt;
std::string start_bbt;
// _legacy_bbt.bars = 0; // legacy session check compars .bars != 0; default BBT_Time c'tor uses 1.
if (node.get_property ("start", start_bbt)) {
if (string_to_bbt_time (start_bbt, bbt)) {
/* legacy session - start used to be in bbt*/
// _legacy_bbt = bbt;
// set_pulse(-1.0);
info << _("Legacy session detected. TempoSection XML node will be altered.") << endmsg;
}
}
if (!node.get_property ("frame", lts.sample)) {
error << _("Legacy tempo section XML does not have a \"frame\" node - map will be ignored") << endmsg;
cerr << _("Legacy tempo section XML does not have a \"frame\" node - map will be ignored") << endl;
return -1;
}
if (!node.get_property ("pulse", lts.pulses)) {
error << _("Legacy tempo section XML does not have a \"pulse\" node - map will be ignored") << endmsg;
cerr << _("Legacy tempo section XML does not have a \"pulse\" node - map will be ignored") << endl;
return -1;
}
if (node.get_property ("beats-per-minute", lts.note_types_per_minute)) {
if (lts.note_types_per_minute < 0.0) {
error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg;
return -1;
}
}
if (!node.get_property ("note-type", lts.note_type)) {
if (lts.note_type < 1.0) {
error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg;
return -1;
}
} else {
/* older session, make note type be quarter by default */
lts.note_type = 4.0;
}
/* older versions used "clamped" as the property name here */
if (!node.get_property ("clamped", lts.continuing)) {
lts.continuing = false;
}
if (node.get_property ("end-beats-per-minute", lts.end_note_types_per_minute)) {
if (lts.end_note_types_per_minute < 0.0) {
info << _("TempoSection XML node has an illegal \"end-beats-per-minute\" value") << endmsg;
return -1;
}
}
Tempo::Type old_type;
if (node.get_property ("tempo-type", old_type)) {
/* sessions with a tempo-type node contain no end-beats-per-minute.
if the legacy node indicates a constant tempo, simply fill this in with the
start tempo. otherwise we need the next neighbour to know what it will be.
*/
if (old_type == Tempo::Constant) {
lts.end_note_types_per_minute = lts.note_types_per_minute;
} else {
lts.end_note_types_per_minute = -1.0;
}
}
return 0;
}
int
TempoMap::parse_meter_state_3x (const XMLNode& node, LegacyMeterState& lms)
{
std::string bbt_str;
if (node.get_property ("start", bbt_str)) {
if (string_to_bbt_time (bbt_str, lms.bbt)) {
/* legacy session - start used to be in bbt*/
info << _("Legacy session detected - MeterSection XML node will be altered.") << endmsg;
// set_pulse (-1.0);
} else {
error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
}
}
/* position is the only data we extract from older XML */
if (!node.get_property ("frame", lms.sample)) {
error << _("Legacy tempo section XML does not have a \"frame\" node - map will be ignored") << endmsg;
return -1;
}
if (!node.get_property ("pulse", lms.pulses)) {
error << _("Legacy meter section XML does not have a \"pulse\" node - map will be ignored") << endmsg;
cerr << _("Legacy meter section XML does not have a \"pulse\" node - map will be ignored") << endl;
return -1;
}
if (!node.get_property ("beat", lms.beat)) {
lms.beat = 0.0;
}
if (node.get_property ("bbt", bbt_str)) {
if (!string_to_bbt_time (bbt_str, lms.bbt)) {
error << _("MeterSection XML node has an illegal \"bbt\" value") << endmsg;
return -1;
}
} else {
warning << _("MeterSection XML node has no \"bbt\" property") << endmsg;
}
/* beats-per-bar is old; divisions-per-bar is new */
if (!node.get_property ("divisions-per-bar", lms.divisions_per_bar)) {
if (!node.get_property ("beats-per-bar", lms.divisions_per_bar)) {
error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg;
return -1;
}
}
if (lms.divisions_per_bar < 0.0) {
error << _("MeterSection XML node has an illegal \"divisions-per-bar\" value") << endmsg;
return -1;
}
if (!node.get_property ("note-type", lms.note_type)) {
error << _("MeterSection XML node has no \"note-type\" property") << endmsg;
return -1;
}
if (lms.note_type < 0.0) {
error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg;
return -1;
}
return 0;
}
int
TempoMap::set_state_3x (const XMLNode& node)
{
XMLNodeList nlist;
XMLNodeConstIterator niter;
nlist = node.children();
/* Need initial tempo & meter points, because subsequent ones will use
* set_tempo() and set_meter() which require pre-existing data
*/
int32_t initial_tempo_index = -1;
int32_t initial_meter_index = -1;
int32_t index;
bool need_points_clear = true;
bool initial_tempo_at_zero = true;
bool initial_meter_at_zero = true;
for (niter = nlist.begin(), index = 0; niter != nlist.end(); ++niter, ++index) {
XMLNode* child = *niter;
if ((initial_tempo_index < 0) && (child->name() == Tempo::xml_node_name)) {
LegacyTempoState lts;
if (parse_tempo_state_3x (*child, lts)) {
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
break;
}
if (lts.sample != 0) {
initial_tempo_at_zero = false;
}
Tempo t (lts.note_types_per_minute,
lts.end_note_types_per_minute,
lts.note_type);
TempoPoint* tp = new TempoPoint (*this, t, samples_to_superclock (0, TEMPORAL_SAMPLE_RATE), Beats::from_double (lts.pulses * 4.0), BBT_Time());
tp->set_continuing (lts.continuing);
_tempos.clear ();
if (need_points_clear) {
_points.clear ();
need_points_clear = false;
}
_tempos.push_back (*tp);
_points.push_back (*tp);
initial_tempo_index = index;
}
if ((initial_meter_index < 0) && (child->name() == Meter::xml_node_name)) {
LegacyMeterState lms;
if (parse_meter_state_3x (*child, lms)) {
error << _("Tempo map: could not use old meter state, restoring old one.") << endmsg;
break;
}
if (lms.sample != 0) {
initial_meter_at_zero = false;
}
Meter m (lms.divisions_per_bar, lms.note_type);
MeterPoint *mp = new MeterPoint (*this, m, 0, Beats(), BBT_Time());
_meters.clear();
if (need_points_clear) {
_points.clear ();
need_points_clear = false;
}
_meters.push_back (*mp);
_points.push_back (*mp);
initial_meter_index = index;
}
if (initial_tempo_index >= 0 && initial_meter_index >= 0) {
break;
}
}
if (initial_tempo_index < 0 || initial_meter_index < 0) {
error << _("Old tempo map information is missing either tempo or meter information - ignored") << endmsg;
return -1;
}
for (niter = nlist.begin(), index = 0; niter != nlist.end(); ++niter, ++index) {
XMLNode* child = *niter;
if (child->name() == Tempo::xml_node_name) {
LegacyTempoState lts;
if (parse_tempo_state_3x (*child, lts)) {
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
break;
}
if (index == initial_tempo_index) {
/* already added */
continue;
}
if (index == initial_tempo_index) {
if (!initial_tempo_at_zero) {
/* already added */
continue;
}
}
Tempo t (lts.note_types_per_minute,
lts.end_note_types_per_minute,
lts.note_type);
set_tempo (t, timepos_t (lts.sample), Beats::from_double (lts.pulses * 4.0));
} else if (child->name() == Meter::xml_node_name) {
LegacyMeterState lms;
if (parse_meter_state_3x (*child, lms)) {
error << _("Tempo map: could not use old meter state, restoring old one.") << endmsg;
break;
}
if (index == initial_meter_index) {
if (!initial_meter_at_zero) {
/* Add a BBT point to fix the meter location */
set_bartime (lms.bbt, timepos_t (lms.sample));
} else {
continue;
}
}
Meter m (lms.divisions_per_bar, lms.note_type);
set_meter (m, timepos_t (lms.sample));
}
}
#if 0
/* check for legacy sessions where bbt was the base musical unit for tempo */
for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
TempoSection* t;
if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
if (t->legacy_bbt().bars != 0) {
fix_legacy_session();
break;
}
if (t->end_note_types_per_minute() < 0.0) {
fix_legacy_end_session();
break;
}
}
}
if (niter == nlist.end()) {
MetricSectionSorter cmp;
_metrics.sort (cmp);
}
#endif
#if 0
/* check for multiple tempo/meters at the same location, which
ardour2 somehow allowed.
*/
{
Tempos::iterator prev = _tempos.end();
for (Tempos::iterator i = _tempos.begin(); i != _tempos.end(); ++i) {
if (prev != _tempos.end()) {
MeterSection* ms;
MeterSection* prev_m;
TempoSection* ts;
TempoSection* prev_t;
if ((prev_m = dynamic_cast<MeterSection*>(*prev)) != 0 && (ms = dynamic_cast<MeterSection*>(*i)) != 0) {
if (prev_m->beat() == ms->beat()) {
error << string_compose (_("Multiple meter definitions found at %1"), prev_m->beat()) << endmsg;
return -1;
}
} else if ((prev_t = dynamic_cast<TempoSection*>(*prev)) != 0 && (ts = dynamic_cast<TempoSection*>(*i)) != 0) {
if (prev_t->pulse() == ts->pulse()) {
error << string_compose (_("Multiple tempo definitions found at %1"), prev_t->pulse()) << endmsg;
return -1;
}
}
}
prev = i;
}
}
#endif
reset_starting_at (0);
return 0;
}
void
TempoMap::map_assert (bool expr, char const * exprstr, char const * file, int line)
{
if (!expr) {
TempoMap::SharedPtr map = TempoMap::use();
std::cerr << "TEMPO MAP LOGIC FAILURE: [" << exprstr << "] at " << file << ':' << line << std::endl;
map->dump (std::cerr);
abort ();
}
}
double
TempoMap::max_notes_per_minute() const
{
double npm = 0;
for (auto const & t : _tempos) {
if (t.note_types_per_minute() > npm) {
npm = t.note_types_per_minute();
}
if (t.end_note_types_per_minute() > npm) {
npm = t.end_note_types_per_minute();
}
}
return npm;
}
double
TempoMap::min_notes_per_minute() const
{
double npm = std::numeric_limits<double>::max();
for (auto const & t : _tempos) {
if (t.note_types_per_minute() < npm) {
npm = t.note_types_per_minute();
}
if (t.end_note_types_per_minute() < npm) {
npm = t.end_note_types_per_minute();
}
}
return npm;
}
#if 0
void
TempoMap::fix_legacy_session ()
{
MeterSection* prev_m = 0;
TempoSection* prev_t = 0;
bool have_initial_t = false;
for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
MeterSection* m;
TempoSection* t;
if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
if (m->initial()) {
pair<double, BBT_Time> bbt = make_pair (0.0, BBT_Time (1, 1, 0));
m->set_beat (bbt);
m->set_pulse (0.0);
m->set_minute (0.0);
m->set_position_lock_style (AudioTime);
prev_m = m;
continue;
}
if (prev_m) {
pair<double, BBT_Time> start = make_pair (((m->bbt().bars - 1) * prev_m->note_divisor())
+ (m->bbt().beats - 1)
+ (m->bbt().ticks / BBT_Time::ticks_per_beat)
, m->bbt());
m->set_beat (start);
const double start_beat = ((m->bbt().bars - 1) * prev_m->note_divisor())
+ (m->bbt().beats - 1)
+ (m->bbt().ticks / BBT_Time::ticks_per_beat);
m->set_pulse (start_beat / prev_m->note_divisor());
}
prev_m = m;
} else if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
if (!t->active()) {
continue;
}
/* Ramp type never existed in the era of this tempo section */
t->set_end_npm (t->note_types_per_minute());
if (t->initial()) {
t->set_pulse (0.0);
t->set_minute (0.0);
t->set_position_lock_style (AudioTime);
prev_t = t;
have_initial_t = true;
continue;
}
if (prev_t) {
/* some 4.x sessions have no initial (non-movable) tempo. */
if (!have_initial_t) {
prev_t->set_pulse (0.0);
prev_t->set_minute (0.0);
prev_t->set_position_lock_style (AudioTime);
prev_t->set_initial (true);
prev_t->set_locked_to_meter (true);
have_initial_t = true;
}
const double beat = ((t->legacy_bbt().bars - 1) * ((prev_m) ? prev_m->note_divisor() : 4.0))
+ (t->legacy_bbt().beats - 1)
+ (t->legacy_bbt().ticks / BBT_Time::ticks_per_beat);
if (prev_m) {
t->set_pulse (beat / prev_m->note_divisor());
} else {
/* really shouldn't happen but.. */
t->set_pulse (beat / 4.0);
}
}
prev_t = t;
}
}
}
void
TempoMap::fix_legacy_end_session ()
{
TempoSection* prev_t = 0;
for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
TempoSection* t;
if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
if (!t->active()) {
continue;
}
if (prev_t) {
if (prev_t->end_note_types_per_minute() < 0.0) {
prev_t->set_end_npm (t->note_types_per_minute());
}
}
prev_t = t;
}
}
if (prev_t) {
prev_t->set_end_npm (prev_t->note_types_per_minute());
}
}
#endif
TempoCommand::TempoCommand (XMLNode const & node)
: _before (0)
, _after (0)
{
if (!node.get_property (X_("name"), _name)) {
throw failed_constructor();
}
XMLNodeList const & children (node.children());
for (XMLNodeList::const_iterator n = children.begin(); n != children.end(); ++n) {
if ((*n)->name() == X_("before")) {
if ((*n)->children().empty()) {
throw failed_constructor();
}
_before = new XMLNode (*(*n)->children().front());
} else if ((*n)->name() == X_("after")) {
if ((*n)->children().empty()) {
throw failed_constructor();
}
_after = new XMLNode (*(*n)->children().front());
}
}
if (!_before || !_after) {
throw failed_constructor();
}
}
TempoCommand::TempoCommand (std::string const & str, XMLNode const * before, XMLNode const * after)
: _name (str)
, _before (before)
, _after (after)
{
}
TempoCommand::~TempoCommand ()
{
delete _before;
delete _after;
}
XMLNode&
TempoCommand::get_state() const
{
XMLNode* node = new XMLNode (X_("TempoCommand"));
node->set_property (X_("name"), _name);
if (_before) {
XMLNode* b = new XMLNode (X_("before"));
b->add_child_copy (*_before);
node->add_child_nocopy (*b);
}
if (_after) {
XMLNode* a = new XMLNode (X_("after"));
a->add_child_copy (*_after);
node->add_child_nocopy (*a);
}
return *node;
}
void
TempoCommand::undo ()
{
if (!_before) {
return;
}
TempoMap::WritableSharedPtr map (TempoMap::write_copy());
map->set_state (*_before, Stateful::current_state_version);
TempoMap::update (map);
}
void
TempoCommand::operator() ()
{
if (!_after) {
return;
}
TempoMap::WritableSharedPtr map (TempoMap::write_copy());
map->set_state (*_after, Stateful::current_state_version);
TempoMap::update (map);
}
TempoMapCutBuffer::TempoMapCutBuffer (timecnt_t const & dur)
: _start_tempo (nullptr)
, _end_tempo (nullptr)
, _start_meter (nullptr)
, _end_meter (nullptr)
, _duration (dur)
{
}
TempoMapCutBuffer::~TempoMapCutBuffer ()
{
delete _start_tempo;
delete _end_tempo;
delete _start_meter;
delete _end_meter;
}
void
TempoMapCutBuffer::add_start_tempo (Tempo const & t)
{
delete _start_tempo;
_start_tempo = new Tempo (t);
}
void
TempoMapCutBuffer::add_end_tempo (Tempo const & t)
{
delete _end_tempo;
_end_tempo = new Tempo (t);
}
void
TempoMapCutBuffer::add_start_meter (Meter const & t)
{
delete _start_meter;
_start_meter = new Meter (t);
}
void
TempoMapCutBuffer::add_end_meter (Meter const & t)
{
delete _end_meter;
_end_meter = new Meter (t);
}
void
TempoMapCutBuffer::dump (std::ostream& ostr)
{
ostr << "TempoMapCutBuffer @ " << this << std::endl;
if (_start_tempo) {
ostr << "Start Tempo: " << *_start_tempo << std::endl;
}
if (_end_tempo) {
ostr << "End Tempo: " << *_end_tempo << std::endl;
}
if (_start_meter) {
ostr << "Start Meter: " << *_start_meter << std::endl;
}
if (_end_meter) {
ostr << "End Meter: " << *_end_meter << std::endl;
}
ostr << "Tempos:\n";
for (auto const & t : _tempos) {
ostr << '\t' << &t << ' ' << t << std::endl;
}
ostr << "Meters:\n";
for (auto const & m : _meters) {
ostr << '\t' << &m << ' ' << m << std::endl;
}
}
void
TempoMapCutBuffer::add (TempoPoint const & tp)
{
TempoPoint* ntp = new TempoPoint (tp);
/* We must reset the audio and beat time position, but we can't do
* anything useful with the BBT time designation.
*/
ntp->set (ntp->sclock() - _duration.position().superclocks(),
ntp->beats() - _duration.position().beats(),
ntp->bbt());
_tempos.push_back (*ntp);
_points.push_back (*ntp);
}
void
TempoMapCutBuffer::add (MeterPoint const & mp)
{
MeterPoint* ntp = new MeterPoint (mp);
/* We must reset the audio and beat time position, but we can't do
* anything useful with the BBT time designation.
*/
ntp->set (ntp->sclock() - _duration.position().superclocks(),
ntp->beats() - _duration.position().beats(),
ntp->bbt());
_meters.push_back (*ntp);
_points.push_back (*ntp);
}
void
TempoMapCutBuffer::add (MusicTimePoint const & mtp)
{
MusicTimePoint* ntp = new MusicTimePoint (mtp);
/* We must reset the audio and beat time position, but we can't do
* anything useful with the BBT time designation.
*/
ntp->set (ntp->sclock() - _duration.position().superclocks(),
ntp->beats() - _duration.position().beats(),
ntp->bbt());
_bartimes.push_back (*ntp);
_tempos.push_back (*ntp);
_meters.push_back (*ntp);
_points.push_back (*ntp);
}
void
TempoMapCutBuffer::clear ()
{
_tempos.clear ();
_meters.clear ();
_bartimes.clear ();
_points.clear ();
}