3132 lines
78 KiB
C++
3132 lines
78 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 "pbd/error.h"
|
|
#include "pbd/i18n.h"
|
|
#include "pbd/compose.h"
|
|
#include "pbd/enumwriter.h"
|
|
#include "pbd/failed_constructor.h"
|
|
#include "pbd/stacktrace.h"
|
|
|
|
#include "temporal/debug.h"
|
|
#include "temporal/tempo.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");
|
|
|
|
superclock_t Temporal::superclock_ticks_per_second = 508032000; // 2^10 * 3^4 * 5^3 * 7^2
|
|
|
|
SerializedRCUManager<TempoMap> TempoMap::_map_mgr (0);
|
|
thread_local TempoMap::SharedPtr TempoMap::_tempo_map_p;
|
|
|
|
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)
|
|
: _map (&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
|
|
|
|
timepos_t
|
|
Point::time() const
|
|
{
|
|
switch (_map->time_domain()) {
|
|
case AudioTime:
|
|
return timepos_t::from_superclock (sclock());
|
|
case BeatTime:
|
|
return timepos_t (beats());
|
|
case BarTime:
|
|
/*NOTREACHED*/
|
|
break;
|
|
}
|
|
/*NOTREACHED*/
|
|
abort();
|
|
/*NOTREACHED*/
|
|
return timepos_t (AudioTime);
|
|
}
|
|
|
|
Tempo::Tempo (XMLNode const & node)
|
|
{
|
|
assert (node.name() == xml_node_name);
|
|
if (!node.get_property (X_("scpnt-start"), _superclocks_per_note_type)) {
|
|
throw failed_constructor ();
|
|
}
|
|
if (!node.get_property (X_("scpnt-end"), _end_superclocks_per_note_type)) {
|
|
throw failed_constructor ();
|
|
}
|
|
if (!node.get_property (X_("note-type"), _note_type)) {
|
|
throw failed_constructor ();
|
|
}
|
|
if (!node.get_property (X_("type"), _type)) {
|
|
throw failed_constructor ();
|
|
}
|
|
if (!node.get_property (X_("active"), _active)) {
|
|
throw failed_constructor ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
Tempo::set_ramped (bool yn)
|
|
{
|
|
_type = (yn ? Ramped : Constant);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Tempo::set_clamped (bool)
|
|
{
|
|
#warning implement Tempo::set_clamped
|
|
return true;
|
|
}
|
|
|
|
XMLNode&
|
|
Tempo::get_state () const
|
|
{
|
|
XMLNode* node = new XMLNode (xml_node_name);
|
|
|
|
node->set_property (X_("scpnt-start"), superclocks_per_note_type());
|
|
node->set_property (X_("scpnt-end"), end_superclocks_per_note_type());
|
|
node->set_property (X_("note-type"), note_type());
|
|
node->set_property (X_("type"), type());
|
|
node->set_property (X_("active"), active());
|
|
|
|
return *node;
|
|
}
|
|
|
|
int
|
|
Tempo::set_state (XMLNode const & node, int /*version*/)
|
|
{
|
|
if (node.name() != xml_node_name) {
|
|
return -1;
|
|
}
|
|
|
|
node.get_property (X_("scpnt-start"), _superclocks_per_note_type);
|
|
node.get_property (X_("scpnt-end"), _end_superclocks_per_note_type);
|
|
node.get_property (X_("note-type"), _note_type);
|
|
node.get_property (X_("type"), _type);
|
|
node.get_property (X_("active"), _active);
|
|
|
|
return 0;
|
|
}
|
|
|
|
Meter::Meter (XMLNode const & node)
|
|
{
|
|
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 -= (r.ticks / tpg);
|
|
r.ticks = tpg + (r.ticks % Temporal::Beats::PPQN);
|
|
}
|
|
|
|
if (r.beats < 0) {
|
|
|
|
r.beats += 1;
|
|
|
|
r.bars -= r.beats / _divisions_per_bar;
|
|
r.beats = r.beats % _divisions_per_bar;
|
|
|
|
r.beats -= 1;
|
|
}
|
|
|
|
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
|
|
{
|
|
Temporal::BBT_Time b = bbt.round_to_beat ();
|
|
if (b.beats > _divisions_per_bar/2) {
|
|
b.bars++;
|
|
}
|
|
b.beats = 1;
|
|
return b;
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
Meter::round_up_to_bar (Temporal::BBT_Time const & bbt) const
|
|
{
|
|
if (bbt.ticks == 0 && bbt.beats == 1) {
|
|
return bbt;
|
|
}
|
|
BBT_Time b = bbt.round_up_to_beat ();
|
|
if (b.beats > 1) {
|
|
b.bars += 1;
|
|
b.beats = 1;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
Meter::round_down_to_bar (Temporal::BBT_Time const & bbt) const
|
|
{
|
|
if (bbt.ticks == 0 && bbt.beats == 1) {
|
|
return bbt;
|
|
}
|
|
BBT_Time b = bbt.round_down_to_beat ();
|
|
if (b.beats > 1) {
|
|
b.beats = 1;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
Meter::round_up_to_beat (Temporal::BBT_Time const & bbt) const
|
|
{
|
|
Temporal::BBT_Time b = bbt.round_up_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) {
|
|
node.get_property (X_("omega"), _omega);
|
|
}
|
|
|
|
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)
|
|
: Tempo (node)
|
|
, Point (map, node)
|
|
, _omega (0)
|
|
{
|
|
}
|
|
|
|
/* To understand the math(s) behind ramping, see the file doc/tempo.{pdf,tex}
|
|
*/
|
|
|
|
void
|
|
TempoPoint::compute_omega (samplecnt_t sr, superclock_t end_scpqn, Temporal::Beats const & quarter_duration)
|
|
{
|
|
if ((superclocks_per_quarter_note () == end_scpqn) || (_type == Constant)) {
|
|
_omega = 0.0;
|
|
return;
|
|
}
|
|
|
|
_omega = ((1.0/end_scpqn) - (1.0/superclocks_per_quarter_note())) / quarter_duration.to_double();
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("computed omega = %1%2 dur was %3\n", std::setprecision(12),_omega, quarter_duration.to_double()));
|
|
}
|
|
|
|
superclock_t
|
|
TempoPoint::superclock_at (Temporal::Beats const & qn) const
|
|
{
|
|
if (qn == _quarters) {
|
|
return _sclock;
|
|
}
|
|
|
|
if (!actually_ramped()) {
|
|
/* not ramped, use linear */
|
|
const superclock_t spqn = superclocks_per_quarter_note ();
|
|
return (spqn * qn.get_beats()) + int_div_round ((spqn * qn.get_ticks()), superclock_t (Temporal::ticks_per_beat));
|
|
}
|
|
|
|
return _sclock + llrint (log1p (superclocks_per_quarter_note() * _omega * (qn - _quarters).to_double()) / _omega);
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
Temporal::Beats
|
|
TempoPoint::quarters_at (superclock_t sc) const
|
|
{
|
|
if (!actually_ramped()) {
|
|
/* convert sc into superbeats, given that sc represents some number of seconds */
|
|
const superclock_t whole_seconds = sc / superclock_ticks_per_second;
|
|
const superclock_t remainder = sc - (whole_seconds * superclock_ticks_per_second);
|
|
const superclock_t superbeats = ((_super_note_type_per_second/4) * whole_seconds) + int_div_round (superclock_t ((_super_note_type_per_second/4) * remainder), superclock_ticks_per_second);
|
|
|
|
/* convert superbeats to beats:ticks */
|
|
int32_t b;
|
|
int32_t t;
|
|
|
|
Tempo::superbeats_to_beats_ticks (superbeats, b, t);
|
|
|
|
return Beats (b, t);
|
|
}
|
|
|
|
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)
|
|
: Meter (node)
|
|
, Point (map, 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;
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
TempoMetric::bbt_at (superclock_t sc) const
|
|
{
|
|
const Beats dq = _tempo->quarters_at (sc) - _meter->beats();
|
|
const BBT_Offset bbt_offset (0, dq.get_beats(), dq.get_ticks());
|
|
return _meter->bbt_add (_meter->bbt(), bbt_offset);
|
|
}
|
|
|
|
superclock_t
|
|
TempoMetric::superclock_at (BBT_Time const & bbt) const
|
|
{
|
|
return _tempo->superclock_at (_meter->quarters_at (bbt));
|
|
}
|
|
|
|
MusicTimePoint::MusicTimePoint (TempoMap const & map, XMLNode const & node)
|
|
: Point (map, node)
|
|
{
|
|
}
|
|
|
|
XMLNode&
|
|
MusicTimePoint::get_state () const
|
|
{
|
|
XMLNode* node = new XMLNode (X_("MusicTime"));
|
|
Point::add_state (*node);
|
|
return *node;
|
|
}
|
|
|
|
void
|
|
TempoMapPoint::start_float ()
|
|
{
|
|
_floating = true;
|
|
}
|
|
|
|
void
|
|
TempoMapPoint::end_float ()
|
|
{
|
|
_floating = false;
|
|
}
|
|
|
|
/* TEMPOMAP */
|
|
|
|
struct TraceableWriterLock : public Glib::Threads::RWLock::WriterLock
|
|
{
|
|
TraceableWriterLock (Glib::Threads::RWLock & lock) : Glib::Threads::RWLock::WriterLock (lock) { }
|
|
~TraceableWriterLock() { }
|
|
};
|
|
|
|
TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, samplecnt_t sr)
|
|
: _sample_rate (sr)
|
|
, _dirty (false)
|
|
, _generation (0) // XXX needs to be reloaded from saved state??
|
|
, _initial_tempo (*this, initial_tempo, 0, Beats(), BBT_Time())
|
|
, _initial_meter (*this, initial_meter, 0, Beats(), BBT_Time())
|
|
, _initial_music_time (*this)
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
_tempos.push_back (_initial_tempo);
|
|
_meters.push_back (_initial_meter);
|
|
_bartimes.push_back (_initial_music_time);
|
|
|
|
_points.push_back (_initial_tempo);
|
|
_points.push_back (_initial_meter);
|
|
_points.push_back (_initial_music_time);
|
|
|
|
set_dirty (true);
|
|
}
|
|
|
|
TempoMap::~TempoMap()
|
|
{
|
|
}
|
|
|
|
TempoMap::TempoMap (TempoMap const & other)
|
|
: _sample_rate (other.sample_rate())
|
|
, _dirty (false)
|
|
, _generation (0)
|
|
, _time_domain (other.time_domain())
|
|
, _initial_tempo (other.metric_at (0).tempo())
|
|
, _initial_meter (other.metric_at (0).meter())
|
|
, _initial_music_time (*this)
|
|
{
|
|
#warning NUTEMPO since these lists are intrusive we must actually rebuild them
|
|
// _meters = other._meters;
|
|
// _bartimes = other._bartimes;
|
|
// _points = other._points;
|
|
// _tempos = other._tempos;
|
|
}
|
|
|
|
void
|
|
TempoMap::set_dirty (bool yn)
|
|
{
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("tempo map @ %1 dirty set to %2\n", this, yn));
|
|
_dirty = yn;
|
|
}
|
|
|
|
void
|
|
TempoMap::set_time_domain (TimeDomain td)
|
|
{
|
|
if (td == time_domain()) {
|
|
return;
|
|
}
|
|
|
|
#warning paul tempo_map::set_time_domain needs implementing
|
|
#if 0
|
|
switch (td) {
|
|
case AudioTime:
|
|
for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
|
|
t->set_sclock (t->superclock_at (t->beats ()));
|
|
}
|
|
for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) {
|
|
m->set_sclock (m->superclock_at (m->beats ()));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
for (Tempos::iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
|
|
t->set_beats (t->quarters_at (t->sclock()));
|
|
}
|
|
for (Meters::iterator m = _meters.begin(); m != _meters.end(); ++m) {
|
|
m->set_beats (m->quarters_at (m->sclock()));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
_time_domain = td;
|
|
set_dirty (true);
|
|
}
|
|
|
|
MeterPoint*
|
|
TempoMap::add_meter (MeterPoint & mp)
|
|
{
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
Meters::iterator m;
|
|
|
|
switch (time_domain()) {
|
|
case AudioTime:
|
|
for (m = _meters.begin(); m != _meters.end() && m->sclock() < mp.sclock(); ++m);
|
|
break;
|
|
case BeatTime:
|
|
for (m = _meters.begin(); m != _meters.end() && m->beats() < mp.beats(); ++m);
|
|
break;
|
|
case BarTime:
|
|
for (m = _meters.begin(); m != _meters.end() && m->bbt() < mp.bbt(); ++m);
|
|
break;
|
|
|
|
}
|
|
|
|
bool replaced = false;
|
|
MeterPoint* ret = 0;
|
|
|
|
if (m != _meters.end()) {
|
|
if (m->sclock() == mp.sclock()) {
|
|
/* overwrite Meter part of this point */
|
|
*((Meter*)&(*m)) = mp;
|
|
ret = &(*m);
|
|
replaced = true;
|
|
}
|
|
}
|
|
|
|
if (!replaced) {
|
|
ret = &(*(_meters.insert (m, mp)));
|
|
}
|
|
|
|
reset_starting_at (mp.sclock());
|
|
set_dirty (true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
TempoMap::change_tempo (TempoPoint & p, Tempo const & t)
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
*((Tempo*)&p) = t;
|
|
|
|
set_dirty (true);
|
|
}
|
|
|
|
TempoPoint &
|
|
TempoMap::set_tempo (Tempo const & t, superclock_t sc)
|
|
{
|
|
TempoPoint * ret;
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", sc, t));
|
|
|
|
Beats beats;
|
|
BBT_Time bbt;
|
|
|
|
TempoMetric tm (metric_at_locked (sc, false));
|
|
|
|
/* tempo changes must be on beat */
|
|
|
|
beats = tm.quarters_at (sc).round_to_beat ();
|
|
bbt = tm.bbt_at (beats);
|
|
|
|
/* recompute superclock position of rounded beat */
|
|
sc = tm.superclock_at (beats);
|
|
|
|
TempoPoint tp (*this, t, sc, beats, bbt);
|
|
ret = add_tempo (tp);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
TempoPoint &
|
|
TempoMap::set_tempo (Tempo const & t, BBT_Time const & bbt)
|
|
{
|
|
TempoPoint * ret = 0;
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", bbt, t));
|
|
|
|
/* tempo changes are required to be on-beat */
|
|
|
|
BBT_Time on_beat = bbt.round_to_beat();
|
|
superclock_t sc;
|
|
Beats beats;
|
|
|
|
TempoMetric metric (metric_at_locked (on_beat, false));
|
|
|
|
beats = metric.quarters_at (on_beat);
|
|
sc = metric.superclock_at (on_beat);
|
|
|
|
TempoPoint tp (*this, t, sc, beats, on_beat);
|
|
ret = add_tempo (tp);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
TempoPoint &
|
|
TempoMap::set_tempo (Tempo const & t, Beats const & beats)
|
|
{
|
|
TempoPoint * ret;
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set tempo @ %1 to %2\n", beats, t));
|
|
|
|
/* tempo changes are required to be on-beat */
|
|
|
|
Beats on_beat = beats.round_to_beat();
|
|
superclock_t sc;
|
|
BBT_Time bbt;
|
|
|
|
TempoMetric metric (metric_at_locked (on_beat, false));
|
|
|
|
bbt = metric.bbt_at (on_beat);
|
|
sc = metric.superclock_at (on_beat);
|
|
|
|
TempoPoint tp (*this, t, sc, on_beat, bbt);
|
|
ret = add_tempo (tp);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
TempoPoint &
|
|
TempoMap::set_tempo (Tempo const & t, timepos_t const & time)
|
|
{
|
|
if (time.is_beats()) {
|
|
return set_tempo (t, time.beats());
|
|
}
|
|
|
|
return set_tempo (t, time.superclocks());
|
|
}
|
|
|
|
TempoPoint*
|
|
TempoMap::add_tempo (TempoPoint & tp)
|
|
{
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
Tempos::iterator t;
|
|
|
|
switch (time_domain()) {
|
|
case AudioTime:
|
|
for (t = _tempos.begin(); t != _tempos.end() && t->sclock() < tp.sclock(); ++t);
|
|
break;
|
|
case BeatTime:
|
|
for (t = _tempos.begin(); t != _tempos.end() && t->beats() < tp.beats(); ++t);
|
|
break;
|
|
case BarTime:
|
|
for (t = _tempos.begin(); t != _tempos.end() && t->bbt() < tp.bbt(); ++t);
|
|
break;
|
|
}
|
|
|
|
bool replaced = false;
|
|
TempoPoint* ret = 0;
|
|
|
|
if (t != _tempos.end()) {
|
|
if (t->sclock() == tp.sclock()) {
|
|
/* overwrite Tempo part of this point */
|
|
*((Tempo*)&(*t)) = tp;
|
|
ret = &(*t);
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old tempo with %1\n", tp));
|
|
replaced = true;
|
|
}
|
|
}
|
|
|
|
if (!replaced) {
|
|
t = _tempos.insert (t, tp);
|
|
ret = &*t;
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted tempo %1\n", tp));
|
|
}
|
|
|
|
/* t is guaranteed not to be _tempos.end() : it was either the
|
|
* TempoPoint we overwrote, or its the one we inserted.
|
|
*/
|
|
|
|
assert (t != _tempos.end());
|
|
|
|
Tempos::iterator nxt = t;
|
|
++nxt;
|
|
|
|
if (t->ramped() && nxt != _tempos.end()) {
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("compute ramp over %1 .. %2 aka %3 .. %4\n", t->sclock(), nxt->sclock(), t->beats(), nxt->beats()));
|
|
t->compute_omega (_sample_rate, nxt->superclocks_per_quarter_note (), nxt->beats() - t->beats());
|
|
}
|
|
|
|
reset_starting_at (tp.sclock());
|
|
|
|
set_dirty (true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
TempoMap::remove_tempo (TempoPoint const & tp)
|
|
{
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
superclock_t sc (tp.sclock());
|
|
Tempos::iterator t;
|
|
for (t = _tempos.begin(); t != _tempos.end() && t->sclock() < tp.sclock(); ++t);
|
|
if (t->sclock() != tp.sclock()) {
|
|
/* error ... no tempo point at the time of tp */
|
|
return;
|
|
}
|
|
_tempos.erase (t);
|
|
reset_starting_at (sc);
|
|
set_dirty (true);
|
|
}
|
|
|
|
Changed ();
|
|
}
|
|
|
|
MusicTimePoint &
|
|
TempoMap::set_bartime (BBT_Time const & bbt, timepos_t const & pos)
|
|
{
|
|
MusicTimePoint * ret;
|
|
|
|
assert (pos.time_domain() == AudioTime);
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
superclock_t sc (pos.superclocks());
|
|
|
|
TempoMetric metric (metric_at_locked (sc));
|
|
|
|
MusicTimePoint tp (bbt, Point (*this, sc, metric.quarters_at (sc), bbt));
|
|
|
|
ret = add_or_replace_bartime (tp);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
MusicTimePoint*
|
|
TempoMap::add_or_replace_bartime (MusicTimePoint & tp)
|
|
{
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
MusicTimes::iterator m;
|
|
|
|
for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < tp.sclock(); ++m);
|
|
|
|
bool replaced = false;
|
|
MusicTimePoint* ret = 0;
|
|
|
|
if (m != _bartimes.end()) {
|
|
if (m->sclock() == tp.sclock()) {
|
|
/* overwrite the point with */
|
|
*m = tp;
|
|
ret = &(*m);
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("overwrote old bartime with %1\n", tp));
|
|
replaced = true;
|
|
}
|
|
}
|
|
|
|
if (!replaced) {
|
|
m = _bartimes.insert (m, tp);
|
|
ret = &*m;
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("inserted bartime %1\n", tp));
|
|
}
|
|
|
|
/* m is guaranteed not to be _bartimes.end() : it was either the
|
|
* TempoPoint we overwrote, or its the one we inserted.
|
|
*/
|
|
|
|
assert (m != _bartimes.end());
|
|
|
|
reset_starting_at (tp.sclock());
|
|
|
|
set_dirty (true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
TempoMap::remove_bartime (MusicTimePoint const & tp)
|
|
{
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
superclock_t sc (tp.sclock());
|
|
MusicTimes::iterator m;
|
|
for (m = _bartimes.begin(); m != _bartimes.end() && m->sclock() < tp.sclock(); ++m);
|
|
if (m->sclock() != tp.sclock()) {
|
|
/* error ... no tempo point at the time of tp */
|
|
return;
|
|
}
|
|
_bartimes.erase (m);
|
|
reset_starting_at (sc);
|
|
set_dirty (true);
|
|
}
|
|
|
|
Changed ();
|
|
}
|
|
|
|
void
|
|
TempoMap::reset_starting_at (superclock_t sc)
|
|
{
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
Tempos::iterator t;
|
|
Meters::iterator m;
|
|
MusicTimes::iterator b;
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("reset starting at %1\n", sc));
|
|
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
TempoPoint* current_tempo = 0;
|
|
MeterPoint* current_meter = 0;
|
|
|
|
/* our task:
|
|
|
|
1) set t, m and b to the iterators for the tempo, meter and bartime
|
|
markers (if any) closest to but after @param sc.
|
|
|
|
2) set current_tempo and current_meter to point to the tempo and
|
|
meter in effect at @param sc
|
|
*/
|
|
|
|
if (sc) {
|
|
for (t = _tempos.begin(); t != _tempos.end() && t->sclock() <= sc; ++t) {
|
|
current_tempo = &*t;
|
|
}
|
|
for (m = _meters.begin(); m != _meters.end() && m->sclock() <= sc; ++m) {
|
|
current_meter = &*m;
|
|
}
|
|
for (b = _bartimes.begin(); b != _bartimes.end() && b->sclock() <= sc; ++b);
|
|
} else {
|
|
t = _tempos.begin();
|
|
m = _meters.begin();
|
|
b = _bartimes.begin();
|
|
|
|
current_meter = &*m;
|
|
current_tempo = &*t;
|
|
}
|
|
|
|
Tempos::iterator nxt_tempo = _tempos.begin();
|
|
|
|
while ((t != _tempos.end()) || (m != _meters.end()) || (b != _bartimes.end())) {
|
|
|
|
/* UPDATE RAMP COEFFICIENTS WHEN NECESSARY */
|
|
|
|
if (t->ramped() && (nxt_tempo != _tempos.begin()) && (nxt_tempo != _tempos.end())) {
|
|
t->compute_omega (_sample_rate, nxt_tempo->superclocks_per_quarter_note (), nxt_tempo->beats() - t->beats());
|
|
}
|
|
|
|
/* figure out which of the 1, 2 or 3 possible iterators defines the next explicit point (we want the earliest on the timeline,
|
|
but there may be more than 1 at the same location).
|
|
*/
|
|
|
|
Point* first_of_three = 0;
|
|
superclock_t limit = INT64_MAX;
|
|
bool is_bartime = false;
|
|
|
|
if (m != _meters.end() && m->sclock() < limit) {
|
|
first_of_three = &*m;
|
|
limit = m->sclock();
|
|
}
|
|
|
|
if (t != _tempos.end() && t->sclock() < limit) {
|
|
first_of_three = &*t;
|
|
limit = t->sclock();
|
|
}
|
|
|
|
if (b != _bartimes.end() && b->sclock() < limit) {
|
|
first_of_three = &*b;
|
|
limit = b->sclock();
|
|
is_bartime = true;
|
|
}
|
|
|
|
assert (first_of_three);
|
|
|
|
/* Determine whether a tempo or meter or bartime point (or any combination thereof) is defining this new point */
|
|
|
|
bool advance_meter = false;
|
|
bool advance_tempo = false;
|
|
bool advance_bartime = false;
|
|
|
|
TempoMetric metric (*current_tempo, *current_meter);
|
|
|
|
if (m->sclock() == first_of_three->sclock()) {
|
|
advance_meter = true;
|
|
current_meter = &*m;
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tcurrent point defines meter %1\n", *current_meter));
|
|
}
|
|
|
|
if (t->sclock() == first_of_three->sclock()) {
|
|
advance_tempo = true;
|
|
current_tempo = &*t;
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tcurrent point defines tempo %1\n", *current_tempo));
|
|
}
|
|
|
|
if ((b != _bartimes.end()) && (b->sclock() == first_of_three->sclock())) {
|
|
advance_bartime = true;
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tcurrent point defines bartime %1\n", *b));
|
|
}
|
|
|
|
if (!is_bartime) {
|
|
superclock_t sc = metric.superclock_at (first_of_three->bbt());
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("\tbased on %1 move to %2,%3\n", first_of_three->bbt(), sc, first_of_three->beats()));
|
|
first_of_three->set (sc, first_of_three->beats(), first_of_three->bbt());
|
|
} else {
|
|
|
|
|
|
}
|
|
|
|
if (advance_meter && (m != _meters.end())) {
|
|
++m;
|
|
}
|
|
if (advance_tempo && (t != _tempos.end())) {
|
|
++t;
|
|
nxt_tempo = t;
|
|
++nxt_tempo;
|
|
}
|
|
if (advance_bartime && (b != _bartimes.end())) {
|
|
++b;
|
|
}
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, "reset done\n");
|
|
#ifndef NDEBUG
|
|
if (DEBUG_ENABLED (DEBUG::TemporalMap)) {
|
|
dump_locked (cerr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
TempoMap::move_meter (MeterPoint const & mp, timepos_t const & when, bool push)
|
|
{
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
assert (time_domain() != BarTime);
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
if (_meters.size() < 2 || mp == _meters.front()) {
|
|
/* not movable */
|
|
return false;
|
|
}
|
|
|
|
superclock_t sc;
|
|
Beats beats;
|
|
BBT_Time bbt;
|
|
TimeDomain td (time_domain());
|
|
bool round_up;
|
|
|
|
/* if time domains differ, then asking for the "when" value in
|
|
* the map domain may involve a tempo map lookup. This requires
|
|
* a lock, so we have to drop the lock while we do this.
|
|
*/
|
|
|
|
if (when.time_domain() != time_domain()) {
|
|
lm.release ();
|
|
}
|
|
|
|
switch (td) {
|
|
case AudioTime:
|
|
sc = when.superclocks();
|
|
if (sc > mp.sclock()) {
|
|
round_up = true;
|
|
} else {
|
|
round_up = false;
|
|
}
|
|
break;
|
|
case BeatTime:
|
|
beats = when.beats ();
|
|
if (beats > mp.beats ()) {
|
|
round_up = true;
|
|
} else {
|
|
round_up = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (when.time_domain() != time_domain()) {
|
|
lm.acquire ();
|
|
}
|
|
|
|
/* 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;
|
|
|
|
switch (time_domain()) {
|
|
case AudioTime: {
|
|
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->sclock() < sc; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->sclock() < sc && *m != mp; ++m) { prev_m = m; }
|
|
assert (prev_m != _meters.end());
|
|
if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); }
|
|
TempoMetric metric (*prev_t, *prev_m);
|
|
bbt = metric.bbt_at (sc);
|
|
bbt = metric.meter().round_to_bar (bbt);
|
|
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->bbt() < bbt && *m != mp; ++m) {prev_m = m; }
|
|
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->bbt() < bbt; ++t) { prev_t = t; }
|
|
assert (prev_m != _meters.end());
|
|
if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); }
|
|
metric = TempoMetric (*prev_t, *prev_m);
|
|
sc = metric.superclock_at (bbt);
|
|
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end(); ++m) {
|
|
if (&*m != &mp) {
|
|
if (m->sclock() == sc) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
beats = metric.quarters_at (bbt);
|
|
break;
|
|
}
|
|
|
|
case BeatTime: {
|
|
/* 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; }
|
|
assert (prev_m != _meters.end());
|
|
if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); }
|
|
TempoMetric metric (*prev_t, *prev_m);
|
|
bbt = metric.bbt_at (beats);
|
|
if (round_up) {
|
|
bbt = metric.meter().round_up_to_bar (bbt);
|
|
} else {
|
|
bbt = metric.meter().round_down_to_bar (bbt);
|
|
}
|
|
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->bbt() < bbt; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->bbt() < bbt && *m != mp; ++m) { prev_m = m; }
|
|
assert (prev_m != _meters.end());
|
|
if (prev_t == _tempos.end()) { prev_t = _tempos.begin(); }
|
|
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);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
/* NOTREACHED */
|
|
return false;
|
|
}
|
|
|
|
if (mp.sclock() == sc && mp.beats() == beats && mp.bbt() == bbt) {
|
|
return false;
|
|
}
|
|
|
|
const superclock_t old_sc = mp.sclock();
|
|
|
|
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 */
|
|
assert (current != _meters.end());
|
|
|
|
/* reset position of this meter */
|
|
current->set (sc, beats, bbt);
|
|
/* reposition in list */
|
|
_meters.splice (insert_before, _meters, current);
|
|
/* recompute 3 domain positions for everything after this */
|
|
reset_starting_at (std::min (sc, old_sc));
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TempoMap::move_tempo (TempoPoint const & tp, timepos_t const & when, bool push)
|
|
{
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
assert (time_domain() != BarTime);
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
if (_tempos.size() < 2 || tp == _tempos.front()) {
|
|
/* not movable */
|
|
return false;
|
|
}
|
|
|
|
superclock_t sc;
|
|
Beats beats;
|
|
BBT_Time bbt;
|
|
TimeDomain td (time_domain());
|
|
|
|
/* if time domains differ, then asking for the "when" value in
|
|
* the map domain may involve a tempo map lookup. This requires
|
|
* a lock, so we have to drop the lock while we do this.
|
|
*/
|
|
|
|
if (when.time_domain() != time_domain()) {
|
|
lm.release ();
|
|
}
|
|
|
|
switch (td) {
|
|
case AudioTime:
|
|
sc = when.superclocks();
|
|
break;
|
|
case BeatTime:
|
|
beats = when.beats ();
|
|
break;
|
|
}
|
|
|
|
if (when.time_domain() != time_domain()) {
|
|
lm.acquire ();
|
|
}
|
|
|
|
/* 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;
|
|
|
|
switch (time_domain()) {
|
|
case AudioTime: {
|
|
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->sclock() < sc && *t != tp; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->sclock() < sc; ++m) { prev_m = m; }
|
|
assert (prev_t != _tempos.end());
|
|
if (prev_m == _meters.end()) { prev_m = _meters.begin(); }
|
|
TempoMetric metric (*prev_t, *prev_m);
|
|
beats = metric.quarters_at (sc);
|
|
/* tempo changes must be on beat, so round and then
|
|
* recompute superclock and BBT with rounded result
|
|
*/
|
|
beats = beats.round_to_beat ();
|
|
for (t = _tempos.begin(), prev_t = _tempos.end(); t != _tempos.end() && t->sclock() < sc && *t != tp; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = _meters.end(); m != _meters.end() && m->sclock() < sc; ++m) { prev_m = m; }
|
|
assert (prev_t != _tempos.end());
|
|
if (prev_m == _meters.end()) { prev_m = _meters.begin(); }
|
|
metric = TempoMetric (*prev_t, *prev_m);
|
|
sc = metric.superclock_at (beats);
|
|
bbt = metric.bbt_at (beats);
|
|
break;
|
|
}
|
|
|
|
case BeatTime: {
|
|
/* tempo changes must be on beat */
|
|
beats = beats.round_to_beat ();
|
|
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; }
|
|
assert (prev_t != _tempos.end());
|
|
assert (prev_m != _meters.end());
|
|
TempoMetric metric (*prev_t, *prev_m);
|
|
sc = metric.superclock_at (beats);
|
|
bbt = metric.bbt_at (beats);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
/* NOTREACHED */
|
|
return false;
|
|
}
|
|
|
|
if (tp.sclock() == sc && tp.beats() == beats && tp.bbt() == bbt) {
|
|
return false;
|
|
}
|
|
|
|
const superclock_t old_sc = tp.sclock();
|
|
|
|
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 */
|
|
assert (current != _tempos.end());
|
|
|
|
/* reset position of this tempo */
|
|
current->set (sc, beats, bbt);
|
|
/* reposition in list */
|
|
_tempos.splice (insert_before, _tempos, current);
|
|
|
|
/* Update ramp coefficients when necessary */
|
|
|
|
if (current->ramped() && insert_before != _tempos.end()) {
|
|
current->compute_omega (_sample_rate, insert_before->superclocks_per_quarter_note (), insert_before->beats() - current->beats());
|
|
}
|
|
|
|
/* recompute 3 domain positions for everything after this */
|
|
reset_starting_at (std::min (sc, old_sc));
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return true;
|
|
}
|
|
|
|
MeterPoint &
|
|
TempoMap::set_meter (Meter const & m, timepos_t const & time)
|
|
{
|
|
if (time.is_beats()) {
|
|
return set_meter (m, time.beats());
|
|
}
|
|
|
|
return set_meter (m, time.superclocks());
|
|
}
|
|
|
|
|
|
MeterPoint &
|
|
TempoMap::set_meter (Meter const & t, BBT_Time const & bbt)
|
|
{
|
|
MeterPoint * ret = 0;
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", bbt, t));
|
|
|
|
TempoMetric metric (metric_at_locked (bbt));
|
|
superclock_t sc;
|
|
Beats beats;
|
|
BBT_Time rounded_bbt;
|
|
|
|
/* meter changes are required to be on-bar */
|
|
|
|
rounded_bbt = metric.round_to_bar (bbt);
|
|
beats = metric.quarters_at (rounded_bbt);
|
|
sc = metric.superclock_at (beats);
|
|
|
|
MeterPoint mp (*this, t, sc, beats, rounded_bbt);
|
|
|
|
ret = add_meter (mp);
|
|
|
|
reset_starting_at (mp.sclock());
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
MeterPoint &
|
|
TempoMap::set_meter (Meter const & t, Beats const & beats)
|
|
{
|
|
MeterPoint * ret = 0;
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", beats, t));
|
|
|
|
TempoMetric metric (metric_at_locked (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 (*this, t, sc, rounded_beats, rounded_bbt);
|
|
|
|
ret = add_meter (mp);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
MeterPoint &
|
|
TempoMap::set_meter (Meter const & m, superclock_t sc)
|
|
{
|
|
MeterPoint * ret = 0;
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("Set meter @ %1 to %2\n", sc, m));
|
|
|
|
Beats beats;
|
|
BBT_Time bbt;
|
|
|
|
TempoMetric metric (metric_at_locked (sc));
|
|
|
|
/* meter changes must be on bar */
|
|
|
|
bbt = metric.bbt_at (beats);
|
|
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 (*this, m, sc, beats, bbt);
|
|
ret = add_meter (mp);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return *ret;
|
|
}
|
|
|
|
void
|
|
TempoMap::remove_meter (MeterPoint const & mp)
|
|
{
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
superclock_t sc = mp.sclock();
|
|
Meters::iterator m = std::upper_bound (_meters.begin(), _meters.end(), mp, Point::sclock_comparator());
|
|
if (m->sclock() != mp.sclock()) {
|
|
/* error ... no meter point at the time of mp */
|
|
return;
|
|
}
|
|
_meters.erase (m);
|
|
reset_starting_at (sc);
|
|
set_dirty (true);
|
|
}
|
|
|
|
Changed ();
|
|
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
TempoMap::bbt_at (timepos_t const & pos) const
|
|
{
|
|
if (pos.is_beats()) {
|
|
return bbt_at (pos.beats());
|
|
}
|
|
return bbt_at (pos.superclocks());
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
TempoMap::bbt_at (superclock_t s) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (s).bbt_at (s);
|
|
}
|
|
|
|
Temporal::BBT_Time
|
|
TempoMap::bbt_at (Temporal::Beats const & qn) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (qn).bbt_at (qn);
|
|
}
|
|
|
|
#if 0
|
|
samplepos_t
|
|
TempoMap::sample_at (Temporal::Beats const & qn) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return superclock_to_samples (metric_at_locked (qn).superclock_at (qn), _sample_rate);
|
|
}
|
|
|
|
samplepos_t
|
|
TempoMap::sample_at (Temporal::BBT_Time const & bbt) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return samples_to_superclock (metric_at_locked (bbt).superclock_at (bbt), _sample_rate);
|
|
}
|
|
|
|
samplepos_t
|
|
TempoMap::sample_at (timepos_t const & pos) const
|
|
{
|
|
if (pos.is_beat()) {
|
|
return sample_at (pos.beats ());
|
|
}
|
|
|
|
/* somewhat nonsensical to call this under these conditions but ... */
|
|
|
|
return pos.superclocks();
|
|
}
|
|
#endif
|
|
|
|
superclock_t
|
|
TempoMap::superclock_at (Temporal::Beats const & qn) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (qn).superclock_at (qn);
|
|
}
|
|
|
|
superclock_t
|
|
TempoMap::superclock_at (Temporal::BBT_Time const & bbt) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (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();
|
|
}
|
|
|
|
superclock_t
|
|
TempoMap::superclock_plus_bbt (superclock_t pos, BBT_Time op) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
BBT_Time pos_bbt = bbt_at (pos);
|
|
|
|
pos_bbt.ticks += op.ticks;
|
|
if (pos_bbt.ticks >= ticks_per_beat) {
|
|
++pos_bbt.beats;
|
|
pos_bbt.ticks -= ticks_per_beat;
|
|
}
|
|
pos_bbt.beats += op.beats;
|
|
|
|
double divisions_per_bar = metric_at_locked (pos_bbt).divisions_per_bar();
|
|
while (pos_bbt.beats >= divisions_per_bar + 1) {
|
|
++pos_bbt.bars;
|
|
divisions_per_bar = metric_at_locked (pos_bbt).divisions_per_bar();
|
|
pos_bbt.beats -= divisions_per_bar;
|
|
}
|
|
pos_bbt.bars += op.bars;
|
|
|
|
return superclock_at (pos_bbt);
|
|
}
|
|
|
|
#define S2Sc(s) (samples_to_superclock ((s), _sample_rate))
|
|
#define Sc2S(s) (superclock_to_samples ((s), _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
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
TempoMetric first (metric_at (pos));
|
|
TempoMetric last (metric_at (pos+distance));
|
|
Temporal::Beats a = first.quarters_at (pos);
|
|
Temporal::Beats b = last.quarters_at (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) */
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
superclock_t s = metric_at_locked (pos).superclock_at (pos);
|
|
s += distance;
|
|
return metric_at_locked (s).quarters_at (s);
|
|
|
|
}
|
|
|
|
Temporal::Beats
|
|
TempoMap::bbtwalk_to_quarters (Beats const & pos, BBT_Offset const & distance) const
|
|
{
|
|
return quarter_note_at (bbt_walk (bbt_at (pos), distance)) - pos;
|
|
}
|
|
|
|
void
|
|
TempoMap::set_sample_rate (samplecnt_t new_sr)
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
const double ratio = new_sr / (double) _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
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
dump_locked (ostr);
|
|
}
|
|
|
|
void
|
|
TempoMap::dump_locked (std::ostream& ostr) const
|
|
{
|
|
for (Tempos::const_iterator t = _tempos.begin(); t != _tempos.end(); ++t) {
|
|
ostr << &*t << ' ' << *t << endl;
|
|
}
|
|
|
|
for (Meters::const_iterator m = _meters.begin(); m != _meters.end(); ++m) {
|
|
ostr << &*m << ' ' << *m << endl;
|
|
}
|
|
}
|
|
|
|
void
|
|
TempoMap::get_grid (TempoMapPoints& ret, superclock_t start, superclock_t end, uint32_t bar_mod)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose (">>> GRID START %1 .. %2 (barmod = %3)\n", start, end, bar_mod))
|
|
|
|
Tempos::iterator t (_tempos.begin());
|
|
Meters::iterator m (_meters.begin());
|
|
MusicTimes::iterator b (_bartimes.begin());
|
|
|
|
TempoMetric metric = metric_at_locked (start, false);
|
|
BBT_Time bbt = metric.bbt_at (start);
|
|
|
|
#ifndef NDEBUG
|
|
/* Sanity Check */
|
|
|
|
if (DEBUG_ENABLED(PBD::DEBUG::TemporalMap)) {
|
|
TempoMetric emetric = metric_at_locked (end, false);
|
|
BBT_Time ebbt = metric_at_locked (end).bbt_at (end);
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("get grid between %1..%2 [ %4 .. %5 ] { %6 .. %7 } at bar_mod = %3\n",
|
|
start, end, bar_mod, start, end, bbt, ebbt));
|
|
|
|
if (metric.quarters_at (bbt).diff (metric.quarters_at (start)) > Beats::ticks (1)) {
|
|
cerr << "MM1: " << start << " / " << metric.quarters_at (start) << " vs. "
|
|
<< metric.superclock_at (bbt) << " / " << metric.quarters_at (bbt)
|
|
<< " delta "
|
|
<< start - metric.superclock_at (bbt)
|
|
<< " dB "
|
|
<< metric.quarters_at (bbt).diff (metric.quarters_at (start))
|
|
<< "\n\tused " << metric
|
|
<< endl;
|
|
abort ();
|
|
}
|
|
|
|
if (emetric.quarters_at (ebbt).diff (emetric.quarters_at (end)) > Beats::ticks (1)) {
|
|
cerr << "MM2: " << end << " / " << emetric.quarters_at (end) << " vs. "
|
|
<< emetric.superclock_at (ebbt) << " / " << emetric.quarters_at (ebbt)
|
|
<< " delta "
|
|
<< end - emetric.superclock_at (ebbt)
|
|
<< " dB "
|
|
<< emetric.quarters_at (ebbt).diff (emetric.quarters_at (end))
|
|
<< "\n\tused " << emetric
|
|
<< endl;
|
|
abort ();
|
|
}
|
|
|
|
dump (cerr);
|
|
}
|
|
#endif
|
|
|
|
/* first task: get to the right starting point for the requested
|
|
* grid. if bar_mod is zero, then we'll start on the next beat after
|
|
* @param start. if bar_mod is non-zero, we'll start on the first bar
|
|
* after @param start. This bar position may or may not be a part of the
|
|
* grid, depending on whether or not it is a multiple of bar_mod.
|
|
*/
|
|
|
|
if (bar_mod == 0) {
|
|
|
|
/* round to next beat, then find the tempo/meter/bartime points
|
|
* in effect at that time.
|
|
*/
|
|
|
|
bbt = metric.meter().round_up_to_beat (bbt);
|
|
|
|
for (Tempos::iterator tt = _tempos.begin(); tt != _tempos.end() && tt->sclock() < start; ++tt) { t = tt; }
|
|
for (Meters::iterator mm = _meters.begin(); mm != _meters.end() && mm->sclock() < start; ++mm) { m = mm; }
|
|
for (MusicTimes::iterator bb = _bartimes.begin(); bb != _bartimes.end() && bb->sclock() < start; ++bb) { b = bb; }
|
|
|
|
/* reset metric */
|
|
|
|
metric = TempoMetric (*t, *m);
|
|
|
|
/* recompute superclock position */
|
|
|
|
superclock_t new_start = metric.superclock_at (bbt);
|
|
|
|
if (new_start < start) {
|
|
abort ();
|
|
}
|
|
|
|
start = new_start;
|
|
|
|
} else {
|
|
|
|
/* this rounding cannot change the meter in effect, because it
|
|
remains within the bar. But it could change the tempo (which
|
|
are only quantized to grid positions within a bar).
|
|
*/
|
|
|
|
BBT_Time bar = bbt.round_down_to_bar ();
|
|
if (bar_mod != 1) {
|
|
bar.bars -= bar.bars % bar_mod;
|
|
++bar.bars;
|
|
}
|
|
|
|
bbt = bar;
|
|
|
|
for (Tempos::iterator tt = _tempos.begin(); tt != _tempos.end() && tt->bbt() < bbt; ++tt) { t = tt; }
|
|
for (Meters::iterator mm = _meters.begin(); mm != _meters.end() && mm->bbt() < bbt; ++mm) { m = mm; }
|
|
for (MusicTimes::iterator bb = _bartimes.begin(); bb != _bartimes.end() && bb->bbt() < bbt; ++bb) { b = bb; }
|
|
|
|
/* t, m and b are now all iterators for the tempo, meter and
|
|
* position markers BEFORE pos. b may be _bartimes.end(), but
|
|
* the other two are guaranteed to be valid references into
|
|
* the tempos and meters
|
|
*/
|
|
|
|
metric = TempoMetric (*t, *m);
|
|
start = metric.superclock_at (bbt);
|
|
}
|
|
|
|
/* advance t, m and b so that the point to the *next*
|
|
* tempo/meter/position marker (if any)
|
|
*/
|
|
|
|
Tempos::iterator nxt_t = t; ++nxt_t;
|
|
Meters::iterator nxt_m = m; ++nxt_m;
|
|
MusicTimes::iterator nxt_b = b; ++nxt_b;
|
|
|
|
/* at this point:
|
|
*
|
|
* - metric is a TempoMetric that describes the situation at pos
|
|
* - t, m and b reference tempo, meter and position markers at or prior to pos (if any)
|
|
* - nxt_t, nxt_m, nxt_b reference the tempo, meter and position markers after pos (if any)
|
|
*
|
|
* t and m must be valid; b, nxt_t, nxt_m, nxt_b may all refer to ::end() of their respective containers.
|
|
*/
|
|
|
|
/* outer loop: compute next marker position, if any, and then set limit to the earlier of that position or @param e.
|
|
* Then run the inner loop to actually add grid points up until limit. Repeat till done.
|
|
*/
|
|
|
|
while (start < end) {
|
|
|
|
bool advance_tempo = false;
|
|
bool advance_meter = false;
|
|
bool advance_bartime = false;
|
|
Point* first_of_three = 0;
|
|
superclock_t limit = INT64_MAX;
|
|
|
|
if (nxt_t != _tempos.end() && limit >= nxt_t->sclock()) {
|
|
first_of_three = &*nxt_t;
|
|
limit = first_of_three->sclock();
|
|
}
|
|
|
|
if (nxt_m != _meters.end() && limit >= nxt_m->sclock()) {
|
|
first_of_three = &*nxt_m;
|
|
limit = first_of_three->sclock();
|
|
}
|
|
|
|
if (nxt_b != _bartimes.end() && limit >= nxt_b->sclock()) {
|
|
first_of_three = &*nxt_b;
|
|
limit = first_of_three->sclock();
|
|
}
|
|
|
|
if (first_of_three) {
|
|
if (nxt_m != _meters.end() && nxt_m->sclock() == first_of_three->sclock()) {
|
|
advance_meter = true;
|
|
}
|
|
|
|
if (nxt_t != _tempos.end() && nxt_t->sclock() == first_of_three->sclock()) {
|
|
advance_tempo = true;
|
|
}
|
|
|
|
if ((nxt_b != _bartimes.end()) && (b->sclock() == first_of_three->sclock())) {
|
|
advance_bartime = true;
|
|
}
|
|
|
|
limit = std::min (end, first_of_three->sclock());
|
|
} else {
|
|
limit = end;
|
|
}
|
|
|
|
if (start >= limit) {
|
|
break;
|
|
}
|
|
|
|
/* Inner loop: add grid points until we hit limit, which is defined by either @param e or the next marker of some kind */
|
|
|
|
do {
|
|
|
|
/* we already have the superclock and BBT time for the next point, either computed before the loop, or at the bottom of this one.
|
|
* So now complete the triplet of (superclock,quarters,bbt)
|
|
*/
|
|
|
|
const Temporal::Beats beats = metric.quarters_at (start);
|
|
|
|
/* add point to grid */
|
|
ret.push_back (TempoMapPoint (*this, metric, start, beats, bbt));
|
|
DEBUG_TRACE (DEBUG::TemporalMap, string_compose ("G %1\t %2\n", metric, ret.back()));
|
|
|
|
/* Advance by the meter note value size */
|
|
|
|
superclock_t step;
|
|
|
|
if (bar_mod == 0) {
|
|
|
|
step = metric.superclocks_per_note_type_at_superclock (start);
|
|
start += step;
|
|
|
|
} else {
|
|
|
|
bbt.bars += bar_mod;
|
|
|
|
/* could have invalidated the current metric */
|
|
|
|
if (first_of_three && (bbt > first_of_three->bbt())) {
|
|
start = first_of_three->sclock();
|
|
break;
|
|
}
|
|
|
|
/* move superclock time forward to next (included) bar. Note that we know that metric is still
|
|
valid because we just checked above if we crossed a marker.
|
|
*/
|
|
|
|
start = metric.superclock_at (bbt);
|
|
}
|
|
|
|
if (start >= limit) {
|
|
/* go back to outer loop to advance iterators and get a new metric */
|
|
break;
|
|
}
|
|
|
|
if (bar_mod == 0) {
|
|
bbt = metric.bbt_at (start);
|
|
}
|
|
|
|
} while (true);
|
|
|
|
/* back in outer loop. Check to see if we passed a marker */
|
|
|
|
if (first_of_three && (start >= first_of_three->sclock())) {
|
|
|
|
if (advance_tempo) {
|
|
if (nxt_t != _tempos.end()) {
|
|
t = nxt_t;
|
|
++nxt_t;
|
|
}
|
|
}
|
|
if (advance_meter) {
|
|
if (nxt_m != _meters.end()) {
|
|
m = nxt_m;
|
|
++nxt_m;
|
|
}
|
|
}
|
|
if (advance_bartime) {
|
|
b = nxt_b;
|
|
if (nxt_b != _bartimes.end()) {
|
|
++nxt_b;
|
|
}
|
|
}
|
|
|
|
if (advance_tempo || advance_meter || advance_bartime) {
|
|
|
|
/* we overstepped a marker
|
|
|
|
* if bar_mod is zero, then by definition any
|
|
* such marker qualifies as a grid point.
|
|
*
|
|
* if bar_mod != zero, then check to see if the new
|
|
* BBT position matches the interval we've been asked
|
|
* for. If so, use it, otherwise just continue around
|
|
* the loop, using the new position and metric.
|
|
*/
|
|
|
|
bbt = first_of_three->bbt ();
|
|
|
|
if (bar_mod != 0) {
|
|
|
|
/* check to see if it matches the interval */
|
|
|
|
if (!bbt.is_bar() || (bbt.bars % bar_mod != 0)) {
|
|
|
|
/* not usable */
|
|
|
|
bbt = bbt.round_up_to_bar ();
|
|
|
|
/* reset iterators for new position */
|
|
|
|
for (Tempos::iterator tt = t; tt != _tempos.end() && tt->bbt() < bbt; ++tt) { t = tt; }
|
|
for (Meters::iterator mm = m; mm != _meters.end() && mm->bbt() < bbt; ++mm) { m = mm; }
|
|
for (MusicTimes::iterator bb = b; bb != _bartimes.end() && bb->bbt() < bbt; ++bb) { b = bb; }
|
|
nxt_t = t; ++nxt_t;
|
|
nxt_m = m; ++nxt_m;
|
|
nxt_b = b; ++nxt_b;
|
|
}
|
|
}
|
|
|
|
metric = TempoMetric (*t, *m);
|
|
start = metric.superclock_at (bbt);
|
|
|
|
/* ready to loop because metric, start and bbt are all set correctly, as they were when entering the outer loop */
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::TemporalMap, "<<< GRID DONE\n");
|
|
}
|
|
|
|
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)
|
|
{
|
|
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, 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
|
|
<< (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 = " << tmp.tempo().omega();
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
superclock_t
|
|
TempoMap::superclock_plus_quarters_as_superclock (superclock_t start, Temporal::Beats const & distance) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
TempoMetric metric (metric_at_locked (start));
|
|
|
|
const Temporal::Beats start_qn = metric.quarters_at (start);
|
|
const Temporal::Beats end_qn = start_qn + distance;
|
|
|
|
TempoMetric end_metric (metric_at (end_qn));
|
|
|
|
return superclock_to_samples (end_metric.superclock_at (end_qn), _sample_rate);
|
|
}
|
|
|
|
Temporal::Beats
|
|
TempoMap::superclock_delta_as_quarters (superclock_t start, superclock_t distance) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return quarter_note_at (start + distance) - quarter_note_at (start);
|
|
}
|
|
|
|
Temporal::superclock_t
|
|
TempoMap::superclock_quarters_delta_as_superclock (superclock_t start, Temporal::Beats const & distance) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
Temporal::Beats start_qn = metric_at_locked (start).quarters_at (start);
|
|
start_qn += distance;
|
|
return metric_at_locked (start_qn).superclock_at (start_qn);
|
|
}
|
|
|
|
#if 0
|
|
void
|
|
TempoMap::update_one_domain_from_another (timepos_t const & src, void* dst, TimeDomain target_domain) const
|
|
{
|
|
switch (target_domain) {
|
|
case AudioTime:
|
|
*((samplepos_t*) dst) = sample_at (src);
|
|
break;
|
|
case BeatTime:
|
|
*((Beats*) dst) = quarter_note_at (src);
|
|
break;
|
|
case BarTime:
|
|
*((BBT_Time*) dst) = bbt_at (src);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int
|
|
TempoMap::update_music_times (int generation, samplepos_t pos, Temporal::Beats & b, Temporal::BBT_Time & bbt, bool force)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
if (force || (generation != _generation)) {
|
|
|
|
const superclock_t sc = S2Sc (pos);
|
|
TempoMetric tm (metric_at_locked (sc));
|
|
|
|
b = tm.quarters_at (sc);
|
|
bbt = tm.bbt_at (sc);
|
|
}
|
|
|
|
return _generation;
|
|
}
|
|
|
|
int
|
|
TempoMap::update_samples_and_bbt_times (int generation, Temporal::Beats const & b, samplepos_t & pos, Temporal::BBT_Time & bbt, bool force)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
if (force || (generation != _generation)) {
|
|
|
|
TempoMetric metric (metric_at_locked (b));
|
|
|
|
pos = samples_to_superclock (metric.superclock_at (b), _sample_rate);
|
|
bbt = metric.bbt_at (b);
|
|
}
|
|
|
|
return _generation;
|
|
}
|
|
|
|
int
|
|
TempoMap::update_samples_and_beat_times (int generation, Temporal::BBT_Time const & bbt, samplepos_t & pos, Temporal::Beats & b, bool force)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
if (force || (generation != _generation)) {
|
|
TempoMetric metric = metric_at_locked (bbt);
|
|
|
|
pos = superclock_to_samples (metric.superclock_at (bbt), _sample_rate);
|
|
b = metric.quarters_at (bbt);
|
|
}
|
|
|
|
return _generation;
|
|
}
|
|
#endif
|
|
|
|
superclock_t
|
|
TempoMap::superclock_per_quarter_note_at (superclock_t pos) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (pos).superclocks_per_quarter_note ();
|
|
}
|
|
|
|
BBT_Time
|
|
TempoMap::bbt_walk (BBT_Time const & bbt, BBT_Offset const & o) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
BBT_Offset offset (o);
|
|
Tempos::const_iterator t, prev_t, next_t;
|
|
Meters::const_iterator m, prev_m, next_m;
|
|
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
/* trivial (and common) case: single tempo, single meter */
|
|
|
|
if (_tempos.size() == 1 && _meters.size() == 1) {
|
|
return _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 tiem given */
|
|
|
|
if (t != _tempos.end() && t->bbt() == bbt) {
|
|
prev_t = t;
|
|
}
|
|
|
|
if (m != _meters.end() && m->bbt() == bbt) {
|
|
prev_m = m;
|
|
}
|
|
|
|
/* see ::metric_at_locked() for comments about the use of const_cast here
|
|
*/
|
|
|
|
TempoMetric metric (*const_cast<TempoPoint*>(&*prev_t), *const_cast<MeterPoint*>(&*prev_m));
|
|
superclock_t pos = metric.superclock_at (bbt);
|
|
|
|
/* 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 tick count, now guaranteed to be less than 1 grid unit */
|
|
|
|
if (offset.ticks) {
|
|
pos += metric.superclocks_per_ppqn () * offset.ticks;
|
|
}
|
|
|
|
/* 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 \
|
|
if (((next_t != _tempos.end()) && (pos >= next_t->sclock())) || \
|
|
((next_m != _meters.end()) && (pos >= next_m->sclock()))) { \
|
|
/* need new metric */ \
|
|
if (pos >= next_t->sclock()) { \
|
|
if (pos >= next_m->sclock()) { \
|
|
metric = TempoMetric (*const_cast<TempoPoint*>(&*next_t), *const_cast<MeterPoint*>(&*next_m)); \
|
|
++next_t; \
|
|
++next_m; \
|
|
} else { \
|
|
metric = TempoMetric (*const_cast<TempoPoint*>(&*next_t), metric.meter()); \
|
|
++next_t; \
|
|
} \
|
|
} else if (pos >= next_m->sclock()) { \
|
|
metric = TempoMetric (metric.tempo(), *const_cast<MeterPoint*>(&*next_m)); \
|
|
++next_m; \
|
|
} \
|
|
}
|
|
|
|
for (int32_t b = 0; b < offset.beats; ++b) {
|
|
|
|
TEMPO_CHECK_FOR_NEW_METRIC;
|
|
pos += metric.superclocks_per_grid (_sample_rate);
|
|
}
|
|
|
|
/* add each bar, 1 by 1, rechecking to see if there's a new
|
|
* TempoMetric in effect after each addition
|
|
*/
|
|
|
|
for (int32_t b = 0; b < offset.bars; ++b) {
|
|
|
|
TEMPO_CHECK_FOR_NEW_METRIC;
|
|
|
|
pos += metric.superclocks_per_bar (_sample_rate);
|
|
}
|
|
|
|
return metric.bbt_at (pos);
|
|
}
|
|
|
|
Temporal::Beats
|
|
TempoMap::quarter_note_at (timepos_t const & pos) const
|
|
{
|
|
if (pos.is_beats()) {
|
|
/* a bit redundant */
|
|
return pos.beats();
|
|
}
|
|
return quarter_note_at (pos);
|
|
}
|
|
|
|
Temporal::Beats
|
|
TempoMap::quarter_note_at (Temporal::BBT_Time const & bbt) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (bbt).quarters_at (bbt);
|
|
}
|
|
|
|
Temporal::Beats
|
|
TempoMap::quarter_note_at (superclock_t pos) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (pos).quarters_at (pos);
|
|
}
|
|
|
|
XMLNode&
|
|
TempoMap::get_state ()
|
|
{
|
|
XMLNode* node = new XMLNode (X_("TempoMap"));
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
node->set_property (X_("time-domain"), _time_domain);
|
|
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) {
|
|
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) {
|
|
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*/)
|
|
{
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
/* global map properties */
|
|
|
|
/* XXX this should probably be at the global level in the session file because it affects a lot more than just the tempo map, potentially */
|
|
node.get_property (X_("superclocks-per-second"), superclock_ticks_per_second);
|
|
|
|
node.get_property (X_("time-domain"), _time_domain);
|
|
|
|
XMLNodeList const & children (node.children());
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Changed ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
TempoMap::set_music_times_from_state (XMLNode const& tempos_node)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
TempoMap::set_tempos_from_state (XMLNode const& tempos_node)
|
|
{
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
XMLNodeList const & children (tempos_node.children());
|
|
|
|
try {
|
|
_tempos.clear ();
|
|
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
|
|
TempoPoint tp (*this, **c);
|
|
// _tempos.push_back (TempoPoint (*this, **c));
|
|
_tempos.push_back (tp);
|
|
}
|
|
} catch (...) {
|
|
_tempos.clear (); /* remove any that were created */
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
TempoMap::set_meters_from_state (XMLNode const& meters_node)
|
|
{
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
XMLNodeList const & children (meters_node.children());
|
|
|
|
try {
|
|
_meters.clear ();
|
|
for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {
|
|
MeterPoint mp (*this, **c);
|
|
_meters.push_back (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 sample duration 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 duration in superclocks between pos and bbt
|
|
*/
|
|
superclock_t
|
|
TempoMap::bbt_duration_at (superclock_t pos, const BBT_Time& bbt, int /* dir_ignored */ ) const
|
|
{
|
|
#warning paul maybe get rid of this
|
|
// return full_duration_at (pos, timecnt_t (bbt, pos), AudioTime).sclock();
|
|
return 0;
|
|
}
|
|
|
|
/** Takes a duration (in any time domain) and considers it as a distance from the given position.
|
|
* Returns a distance in the requested domain, taking tempo changes into account.
|
|
*
|
|
* Obviously, if the given distance is in the same time domain as the requested domain,
|
|
* the returned distance is identical to the given one.
|
|
*/
|
|
|
|
timecnt_t
|
|
TempoMap::full_duration_at (timepos_t const & pos, timecnt_t const & duration, TimeDomain return_domain) const
|
|
{
|
|
timepos_t p (return_domain);
|
|
Beats b;
|
|
superclock_t s;
|
|
|
|
assert (pos.time_domain() != BarTime);
|
|
assert (duration.time_domain() != BarTime);
|
|
assert (return_domain != BarTime);
|
|
|
|
if (return_domain == duration.time_domain()) {
|
|
return duration;
|
|
}
|
|
|
|
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 (pos.time_domain()) {
|
|
case BeatTime:
|
|
/* pos is already in beats */
|
|
p = pos;
|
|
break;
|
|
case AudioTime:
|
|
/* Determine beats at sc pos, so that we can add beats */
|
|
p = metric_at (pos).quarters_at (pos.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 - pos.superclocks(), pos);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case BeatTime:
|
|
switch (duration.time_domain()) {
|
|
case AudioTime:
|
|
/* duration is in superclocks but we're asked to return beats */
|
|
switch (pos.time_domain ()) {
|
|
case AudioTime:
|
|
/* pos is already in superclocks */
|
|
p = pos;
|
|
break;
|
|
case BeatTime:
|
|
/* determined sc at beat position so we can add superclocks */
|
|
p = metric_at (pos).superclock_at (pos.beats());
|
|
break;
|
|
}
|
|
/* add superclocks */
|
|
p += duration;
|
|
/* determine beats */
|
|
b = metric_at (p).quarters_at (p.superclocks());
|
|
/* return duration in beats */
|
|
return timecnt_t (b - pos.beats(), pos);
|
|
break;
|
|
case BeatTime:
|
|
/*NOTREACHED*/
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*NOTREACHED*/
|
|
abort ();
|
|
/*NOTREACHED*/
|
|
|
|
return timecnt_t::from_superclock (0);
|
|
|
|
}
|
|
|
|
Tempo const *
|
|
TempoMap::next_tempo (Tempo const & t) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
|
|
Tempos::const_iterator p = _tempos.begin();
|
|
|
|
while (p != _tempos.end()) {
|
|
if (&t == &*p) {
|
|
break;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
if (p != _tempos.end()) {
|
|
++p;
|
|
|
|
if (p != _tempos.end()) {
|
|
return &*p;;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t
|
|
TempoMap::n_meters () const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return _meters.size();
|
|
}
|
|
|
|
uint32_t
|
|
TempoMap::n_tempos () const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return _tempos.size();
|
|
}
|
|
|
|
void
|
|
TempoMap::insert_time (timepos_t const & pos, timecnt_t const & duration)
|
|
{
|
|
assert (time_domain() != BarTime);
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
if (pos == std::numeric_limits<timepos_t>::min()) {
|
|
/* can't insert time at the front of the map: those entries are fixed */
|
|
return;
|
|
}
|
|
|
|
{
|
|
TraceableWriterLock lm (_lock);
|
|
|
|
Tempos::iterator t (_tempos.begin());
|
|
Meters::iterator m (_meters.begin());
|
|
MusicTimes::iterator b (_bartimes.begin());
|
|
|
|
TempoPoint current_tempo = *t;
|
|
MeterPoint current_meter = *m;
|
|
MusicTimePoint current_time_point (*this);
|
|
|
|
if (_bartimes.size() > 0) {
|
|
current_time_point = *b;
|
|
}
|
|
|
|
superclock_t sc;
|
|
Beats beats;
|
|
BBT_Time bbt;
|
|
|
|
/* set these to true so that we set current_* on our first pass
|
|
* through the while loop(s)
|
|
*/
|
|
|
|
bool moved_tempo = true;
|
|
bool moved_meter = true;
|
|
bool moved_bartime = true;
|
|
|
|
switch (duration.time_domain()) {
|
|
case AudioTime:
|
|
sc = pos.superclocks();
|
|
|
|
/* handle a common case quickly */
|
|
|
|
if ((_tempos.size() < 2 || sc > _tempos.back().sclock()) &&
|
|
(_meters.size() < 2 || sc > _meters.back().sclock()) &&
|
|
(_bartimes.size() < 2 || (_bartimes.empty() || sc > _bartimes.back().sclock()))) {
|
|
|
|
/* only one tempo, plus one meter and zero or
|
|
one bartimes, or insertion point is after last
|
|
item. nothing to do here.
|
|
*/
|
|
|
|
return;
|
|
}
|
|
|
|
/* advance fundamental iterators to correct position */
|
|
|
|
while (t != _tempos.end() && t->sclock() < sc) ++t;
|
|
while (m != _meters.end() && m->sclock() < sc) ++m;
|
|
while (b != _bartimes.end() && b->sclock() < sc) ++b;
|
|
|
|
while (t != _tempos.end() && m != _meters.end() && b != _bartimes.end()) {
|
|
|
|
if (moved_tempo && t != _tempos.end()) {
|
|
current_tempo = *t;
|
|
moved_tempo = false;
|
|
}
|
|
if (moved_meter && m != _meters.end()) {
|
|
current_meter = *m;
|
|
moved_meter = false;
|
|
}
|
|
if (moved_bartime && b != _bartimes.end()) {
|
|
current_time_point = *b;
|
|
moved_bartime = false;
|
|
}
|
|
|
|
/* for each of t, m and b:
|
|
|
|
if the point is earlier than the other two,
|
|
recompute the superclock, beat and bbt
|
|
positions, and reset the point.
|
|
*/
|
|
|
|
if (t->sclock() < m->sclock() && t->sclock() < b->sclock()) {
|
|
|
|
sc = t->sclock() + duration.superclocks();
|
|
beats = current_tempo.quarters_at (sc);
|
|
/* round tempo to beats */
|
|
beats = beats.round_to_beat ();
|
|
sc = current_tempo.superclock_at (beats);
|
|
bbt = current_meter.bbt_at (beats);
|
|
|
|
t->set (sc, beats, bbt);
|
|
++t;
|
|
moved_tempo = true;
|
|
}
|
|
|
|
if (m->sclock() < t->sclock() && m->sclock() < b->sclock()) {
|
|
|
|
sc = m->sclock() + duration.superclocks();
|
|
beats = current_tempo.quarters_at (sc);
|
|
/* round meter to bars */
|
|
bbt = current_meter.bbt_at (beats);
|
|
beats = current_meter.quarters_at (current_meter.round_to_bar(bbt));
|
|
/* recompute */
|
|
sc = current_tempo.superclock_at (beats);
|
|
|
|
m->set (sc, beats, bbt);
|
|
++m;
|
|
moved_meter = true;
|
|
}
|
|
|
|
if (b->sclock() < t->sclock() && b->sclock() < m->sclock()) {
|
|
|
|
sc = b->sclock() + duration.superclocks();
|
|
beats = current_tempo.quarters_at (sc);
|
|
/* round bartime to beats */
|
|
beats = beats.round_to_beat();
|
|
sc = current_tempo.superclock_at (beats);
|
|
bbt = current_meter.bbt_at (beats);
|
|
|
|
m->set (sc, beats, bbt);
|
|
++m;
|
|
moved_meter = true;
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
case BeatTime:
|
|
break;
|
|
}
|
|
|
|
set_dirty (true);
|
|
}
|
|
|
|
Changed ();
|
|
}
|
|
|
|
bool
|
|
TempoMap::remove_time (timepos_t const & pos, timecnt_t const & duration)
|
|
{
|
|
bool moved = false;
|
|
|
|
if (moved) {
|
|
Changed ();
|
|
}
|
|
|
|
return moved;
|
|
}
|
|
|
|
TempoPoint const *
|
|
TempoMap::previous_tempo (TempoPoint const & point) const
|
|
{
|
|
Tempos::const_iterator t = _tempos.begin();
|
|
Tempos::const_iterator prev = _tempos.end();
|
|
|
|
while (t != _tempos.end()) {
|
|
if (t->sclock() == point.sclock()) {
|
|
if (prev != _tempos.end()) {
|
|
return &*prev;
|
|
}
|
|
}
|
|
prev = t;
|
|
++t;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 s) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (s);
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at (Beats const & b) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (b);
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at (BBT_Time const & bbt) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (_lock);
|
|
return metric_at_locked (bbt);
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at_locked (superclock_t sc, bool can_match) const
|
|
{
|
|
Tempos::const_iterator t, prev_t;
|
|
Meters::const_iterator m, prev_m;
|
|
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
/* Yes, linear search because the typical size of _tempos and _meters
|
|
* is 1, and extreme sizes are on the order of 10
|
|
*/
|
|
|
|
for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->sclock() < sc; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->sclock() < sc; ++m) { prev_m = m; }
|
|
|
|
if (can_match || sc == 0) {
|
|
/* may have found tempo and/or meter precisely at @param sc */
|
|
|
|
if (t != _tempos.end() && t->sclock() == sc) {
|
|
prev_t = t;
|
|
}
|
|
|
|
if (m != _meters.end() && m->sclock() == sc) {
|
|
prev_m = m;
|
|
}
|
|
}
|
|
|
|
/* I hate doing this const_cast<>, but making this method non-const
|
|
* propagates into everything that just calls metric_at(), and that's a
|
|
* bit ridiculous. Yes, the TempoMetric returned here can be used to
|
|
* change the map, and that's bad, but the non-const propagation is
|
|
* worse.
|
|
*/
|
|
|
|
return TempoMetric (*const_cast<TempoPoint*>(&*prev_t), *const_cast<MeterPoint*> (&*prev_m));
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at_locked (Beats const & b, bool can_match) const
|
|
{
|
|
Tempos::const_iterator t, prev_t;
|
|
Meters::const_iterator m, prev_m;
|
|
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
/* Yes, linear search because the typical size of _tempos and _meters
|
|
* is 1, and extreme sizes are on the order of 10
|
|
*/
|
|
|
|
for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->beats() < b; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->beats() < b; ++m) { prev_m = m; }
|
|
|
|
if (can_match || b == Beats()) {
|
|
/* may have found tempo and/or meter precisely at @param b */
|
|
if (t != _tempos.end() && t->beats() == b) {
|
|
prev_t = t;
|
|
}
|
|
|
|
if (m != _meters.end() && m->beats() == b) {
|
|
prev_m = m;
|
|
}
|
|
}
|
|
|
|
/* I hate doing this const_cast<>, but making this method non-const
|
|
* propagates into everything that just calls metric_at(), and that's a
|
|
* bit ridiculous. Yes, the TempoMetric returned here can be used to
|
|
* change the map, and that's bad, but the non-const propagation is
|
|
* worse.
|
|
*/
|
|
|
|
return TempoMetric (*const_cast<TempoPoint*>(&*prev_t), *const_cast<MeterPoint*> (&*prev_m));
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at_locked (BBT_Time const & bbt, bool can_match) const
|
|
{
|
|
Tempos::const_iterator t, prev_t;
|
|
Meters::const_iterator m, prev_m;
|
|
|
|
assert (!_tempos.empty());
|
|
assert (!_meters.empty());
|
|
|
|
/* Yes, linear search because the typical size of _tempos and _meters
|
|
* is 1, and extreme sizes are on the order of 10
|
|
*/
|
|
|
|
for (t = _tempos.begin(), prev_t = t; t != _tempos.end() && t->bbt() < bbt; ++t) { prev_t = t; }
|
|
for (m = _meters.begin(), prev_m = m; m != _meters.end() && m->bbt() < bbt; ++m) { prev_m = m; }
|
|
|
|
if (can_match || bbt == BBT_Time()) {
|
|
/* may have found tempo and/or meter precisely at @param bbt */
|
|
|
|
if (t != _tempos.end() && t->bbt() == bbt) {
|
|
prev_t = t;
|
|
}
|
|
|
|
if (m != _meters.end() && m->bbt() == bbt) {
|
|
prev_m = m;
|
|
}
|
|
}
|
|
|
|
/* I hate doing this const_cast<>, but making this method non-const
|
|
* propagates into everything that just calls metric_at(), and that's a
|
|
* bit ridiculous. Yes, the TempoMetric returned here can be used to
|
|
* change the map, and that's bad, but the non-const propagation is
|
|
* worse.
|
|
*/
|
|
|
|
return TempoMetric (*const_cast<TempoPoint*>(&*prev_t), *const_cast<MeterPoint*> (&*prev_m));
|
|
}
|
|
|
|
bool
|
|
TempoMap::set_ramped (TempoPoint & tp, bool yn)
|
|
{
|
|
Rampable & r (tp);
|
|
bool ret = r.set_ramped (yn);
|
|
if (ret) {
|
|
reset_starting_at (tp.sclock());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
bool
|
|
TempoMap::twist_tempi (TempoSection* ts, const Tempo& bpm, const framepos_t frame, const framepos_t end_frame)
|
|
{
|
|
TempoSection* next_t = 0;
|
|
TempoSection* next_to_next_t = 0;
|
|
Metrics future_map;
|
|
bool can_solve = false;
|
|
|
|
/* minimum allowed measurement distance in frames */
|
|
framepos_t const min_dframe = 2;
|
|
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
if (!ts) {
|
|
return false;
|
|
}
|
|
|
|
TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
|
|
TempoSection* prev_to_prev_t = 0;
|
|
const frameoffset_t fr_off = end_frame - frame;
|
|
|
|
if (!tempo_copy) {
|
|
return false;
|
|
}
|
|
|
|
if (tempo_copy->pulse() > 0.0) {
|
|
prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_minute_locked (future_map, minute_at_frame (tempo_copy->frame() - 1)));
|
|
}
|
|
|
|
for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) {
|
|
if ((*i)->is_tempo() && (*i)->minute() > tempo_copy->minute()) {
|
|
next_t = static_cast<TempoSection*> (*i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!next_t) {
|
|
return false;
|
|
}
|
|
|
|
for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) {
|
|
if ((*i)->is_tempo() && (*i)->minute() > next_t->minute()) {
|
|
next_to_next_t = static_cast<TempoSection*> (*i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!next_to_next_t) {
|
|
return false;
|
|
}
|
|
|
|
double prev_contribution = 0.0;
|
|
|
|
if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
|
|
prev_contribution = (tempo_copy->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
|
|
}
|
|
|
|
const frameoffset_t tempo_copy_frame_contribution = fr_off - (prev_contribution * (double) fr_off);
|
|
|
|
|
|
framepos_t old_tc_minute = tempo_copy->minute();
|
|
double old_next_minute = next_t->minute();
|
|
double old_next_to_next_minute = next_to_next_t->minute();
|
|
|
|
double new_bpm;
|
|
double new_next_bpm;
|
|
double new_copy_end_bpm;
|
|
|
|
if (frame > tempo_copy->frame() + min_dframe && (frame + tempo_copy_frame_contribution) > tempo_copy->frame() + min_dframe) {
|
|
new_bpm = tempo_copy->note_types_per_minute() * ((frame - tempo_copy->frame())
|
|
/ (double) (end_frame - tempo_copy->frame()));
|
|
} else {
|
|
new_bpm = tempo_copy->note_types_per_minute();
|
|
}
|
|
|
|
/* 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 false;
|
|
}
|
|
|
|
new_bpm = min (new_bpm, (double) 1000.0);
|
|
|
|
tempo_copy->set_note_types_per_minute (new_bpm);
|
|
if (tempo_copy->type() == TempoSection::Constant) {
|
|
tempo_copy->set_end_note_types_per_minute (new_bpm);
|
|
}
|
|
|
|
recompute_tempi (future_map);
|
|
|
|
if (check_solved (future_map)) {
|
|
|
|
if (!next_t) {
|
|
return false;
|
|
}
|
|
|
|
ts->set_note_types_per_minute (new_bpm);
|
|
if (ts->type() == TempoSection::Constant) {
|
|
ts->set_end_note_types_per_minute (new_bpm);
|
|
}
|
|
|
|
recompute_map (_metrics);
|
|
|
|
can_solve = true;
|
|
}
|
|
|
|
if (next_t->type() == TempoSection::Constant || next_t->c() == 0.0) {
|
|
if (frame > tempo_copy->frame() + min_dframe && end_frame > tempo_copy->frame() + min_dframe) {
|
|
|
|
new_next_bpm = next_t->note_types_per_minute() * ((next_to_next_t->minute() - old_next_minute)
|
|
/ (double) ((old_next_to_next_minute) - old_next_minute));
|
|
|
|
} else {
|
|
new_next_bpm = next_t->note_types_per_minute();
|
|
}
|
|
|
|
next_t->set_note_types_per_minute (new_next_bpm);
|
|
recompute_tempi (future_map);
|
|
|
|
if (check_solved (future_map)) {
|
|
for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
|
|
if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) {
|
|
next_t = static_cast<TempoSection*> (*i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!next_t) {
|
|
return false;
|
|
}
|
|
next_t->set_note_types_per_minute (new_next_bpm);
|
|
recompute_map (_metrics);
|
|
can_solve = true;
|
|
}
|
|
} else {
|
|
double next_frame_ratio = 1.0;
|
|
double copy_frame_ratio = 1.0;
|
|
|
|
if (next_to_next_t) {
|
|
next_frame_ratio = (next_to_next_t->minute() - old_next_minute) / (old_next_to_next_minute - old_next_minute);
|
|
|
|
copy_frame_ratio = ((old_tc_minute - next_t->minute()) / (double) (old_tc_minute - old_next_minute));
|
|
}
|
|
|
|
new_next_bpm = next_t->note_types_per_minute() * next_frame_ratio;
|
|
new_copy_end_bpm = tempo_copy->end_note_types_per_minute() * copy_frame_ratio;
|
|
|
|
tempo_copy->set_end_note_types_per_minute (new_copy_end_bpm);
|
|
|
|
if (next_t->clamped()) {
|
|
next_t->set_note_types_per_minute (new_copy_end_bpm);
|
|
} else {
|
|
next_t->set_note_types_per_minute (new_next_bpm);
|
|
}
|
|
|
|
recompute_tempi (future_map);
|
|
|
|
if (check_solved (future_map)) {
|
|
for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
|
|
if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) {
|
|
next_t = static_cast<TempoSection*> (*i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!next_t) {
|
|
return false;
|
|
}
|
|
|
|
if (next_t->clamped()) {
|
|
next_t->set_note_types_per_minute (new_copy_end_bpm);
|
|
} else {
|
|
next_t->set_note_types_per_minute (new_next_bpm);
|
|
}
|
|
|
|
ts->set_end_note_types_per_minute (new_copy_end_bpm);
|
|
recompute_map (_metrics);
|
|
can_solve = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Metrics::const_iterator d = future_map.begin();
|
|
while (d != future_map.end()) {
|
|
delete (*d);
|
|
++d;
|
|
}
|
|
|
|
MetricPositionChanged (PropertyChange ()); // Emit Signal
|
|
|
|
return can_solve;
|
|
}
|
|
|
|
#endif
|