2503 lines
62 KiB
C++
2503 lines
62 KiB
C++
/*
|
|
Copyright (C) 2000-2002 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 <stdexcept>
|
|
#include <cmath>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <glibmm/threads.h>
|
|
#include "pbd/xml++.h"
|
|
#include "evoral/types.hpp"
|
|
#include "ardour/debug.h"
|
|
#include "ardour/lmath.h"
|
|
#include "ardour/tempo.h"
|
|
|
|
#include "i18n.h"
|
|
#include <locale.h>
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
using Timecode::BBT_Time;
|
|
|
|
/* _default tempo is 4/4 qtr=120 */
|
|
|
|
Meter TempoMap::_default_meter (4.0, 4.0);
|
|
Tempo TempoMap::_default_tempo (120.0);
|
|
|
|
/***********************************************************************/
|
|
|
|
double
|
|
Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const
|
|
{
|
|
/* This is tempo- and meter-sensitive. The number it returns
|
|
is based on the interval between any two lines in the
|
|
grid that is constructed from tempo and meter sections.
|
|
|
|
The return value IS NOT interpretable in terms of "beats".
|
|
*/
|
|
|
|
return (60.0 * sr) / (tempo.beats_per_minute() * (_note_type/tempo.note_type()));
|
|
}
|
|
|
|
double
|
|
Meter::frames_per_bar (const Tempo& tempo, framecnt_t sr) const
|
|
{
|
|
return frames_per_grid (tempo, sr) * _divisions_per_bar;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
|
|
const string TempoSection::xml_state_node_name = "Tempo";
|
|
|
|
TempoSection::TempoSection (const XMLNode& node)
|
|
: MetricSection (BBT_Time()), Tempo (TempoMap::default_tempo())
|
|
{
|
|
const XMLProperty *prop;
|
|
BBT_Time start;
|
|
LocaleGuard lg (X_("C"));
|
|
|
|
if ((prop = node.property ("start")) == 0) {
|
|
error << _("TempoSection XML node has no \"start\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
|
|
&start.bars,
|
|
&start.beats,
|
|
&start.ticks) < 3) {
|
|
error << _("TempoSection XML node has an illegal \"start\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
set_start (start);
|
|
|
|
if ((prop = node.property ("beats-per-minute")) == 0) {
|
|
error << _("TempoSection XML node has no \"beats-per-minute\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if (sscanf (prop->value().c_str(), "%lf", &_beats_per_minute) != 1 || _beats_per_minute < 0.0) {
|
|
error << _("TempoSection XML node has an illegal \"beats_per_minute\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if ((prop = node.property ("note-type")) == 0) {
|
|
/* older session, make note type be quarter by default */
|
|
_note_type = 4.0;
|
|
} else {
|
|
if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 1.0) {
|
|
error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
}
|
|
|
|
if ((prop = node.property ("movable")) == 0) {
|
|
error << _("TempoSection XML node has no \"movable\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
set_movable (string_is_affirmative (prop->value()));
|
|
|
|
if ((prop = node.property ("bar-offset")) == 0) {
|
|
_bar_offset = -1.0;
|
|
} else {
|
|
if (sscanf (prop->value().c_str(), "%lf", &_bar_offset) != 1 || _bar_offset < 0.0) {
|
|
error << _("TempoSection XML node has an illegal \"bar-offset\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
}
|
|
}
|
|
|
|
XMLNode&
|
|
TempoSection::get_state() const
|
|
{
|
|
XMLNode *root = new XMLNode (xml_state_node_name);
|
|
char buf[256];
|
|
LocaleGuard lg (X_("C"));
|
|
|
|
snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
|
|
start().bars,
|
|
start().beats,
|
|
start().ticks);
|
|
root->add_property ("start", buf);
|
|
snprintf (buf, sizeof (buf), "%f", _beats_per_minute);
|
|
root->add_property ("beats-per-minute", buf);
|
|
snprintf (buf, sizeof (buf), "%f", _note_type);
|
|
root->add_property ("note-type", buf);
|
|
// snprintf (buf, sizeof (buf), "%f", _bar_offset);
|
|
// root->add_property ("bar-offset", buf);
|
|
snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
|
|
root->add_property ("movable", buf);
|
|
|
|
return *root;
|
|
}
|
|
|
|
void
|
|
|
|
TempoSection::update_bar_offset_from_bbt (const Meter& m)
|
|
{
|
|
_bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_beat + start().ticks) /
|
|
(m.divisions_per_bar() * BBT_Time::ticks_per_beat);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
|
|
}
|
|
|
|
void
|
|
TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
|
|
{
|
|
BBT_Time new_start;
|
|
|
|
if (_bar_offset < 0.0) {
|
|
/* not set yet */
|
|
return;
|
|
}
|
|
|
|
new_start.bars = start().bars;
|
|
|
|
double ticks = BBT_Time::ticks_per_beat * meter.divisions_per_bar() * _bar_offset;
|
|
new_start.beats = (uint32_t) floor (ticks/BBT_Time::ticks_per_beat);
|
|
new_start.ticks = 0; /* (uint32_t) fmod (ticks, BBT_Time::ticks_per_beat); */
|
|
|
|
/* remember the 1-based counting properties of beats */
|
|
new_start.beats += 1;
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("from bar offset %1 and dpb %2, ticks = %3->%4 beats = %5\n",
|
|
_bar_offset, meter.divisions_per_bar(), ticks, new_start.ticks, new_start.beats));
|
|
|
|
set_start (new_start);
|
|
}
|
|
|
|
/***********************************************************************/
|
|
|
|
const string MeterSection::xml_state_node_name = "Meter";
|
|
|
|
MeterSection::MeterSection (const XMLNode& node)
|
|
: MetricSection (BBT_Time()), Meter (TempoMap::default_meter())
|
|
{
|
|
const XMLProperty *prop;
|
|
BBT_Time start;
|
|
LocaleGuard lg (X_("C"));
|
|
|
|
if ((prop = node.property ("start")) == 0) {
|
|
error << _("MeterSection XML node has no \"start\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if (sscanf (prop->value().c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
|
|
&start.bars,
|
|
&start.beats,
|
|
&start.ticks) < 3) {
|
|
error << _("MeterSection XML node has an illegal \"start\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
set_start (start);
|
|
|
|
/* beats-per-bar is old; divisions-per-bar is new */
|
|
|
|
if ((prop = node.property ("divisions-per-bar")) == 0) {
|
|
if ((prop = node.property ("beats-per-bar")) == 0) {
|
|
error << _("MeterSection XML node has no \"beats-per-bar\" or \"divisions-per-bar\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
}
|
|
|
|
if (sscanf (prop->value().c_str(), "%lf", &_divisions_per_bar) != 1 || _divisions_per_bar < 0.0) {
|
|
error << _("MeterSection XML node has an illegal \"beats-per-bar\" or \"divisions-per-bar\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if ((prop = node.property ("note-type")) == 0) {
|
|
error << _("MeterSection XML node has no \"note-type\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 0.0) {
|
|
error << _("MeterSection XML node has an illegal \"note-type\" value") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
if ((prop = node.property ("movable")) == 0) {
|
|
error << _("MeterSection XML node has no \"movable\" property") << endmsg;
|
|
throw failed_constructor();
|
|
}
|
|
|
|
set_movable (string_is_affirmative (prop->value()));
|
|
}
|
|
|
|
XMLNode&
|
|
MeterSection::get_state() const
|
|
{
|
|
XMLNode *root = new XMLNode (xml_state_node_name);
|
|
char buf[256];
|
|
LocaleGuard lg (X_("C"));
|
|
|
|
snprintf (buf, sizeof (buf), "%" PRIu32 "|%" PRIu32 "|%" PRIu32,
|
|
start().bars,
|
|
start().beats,
|
|
start().ticks);
|
|
root->add_property ("start", buf);
|
|
snprintf (buf, sizeof (buf), "%f", _note_type);
|
|
root->add_property ("note-type", buf);
|
|
snprintf (buf, sizeof (buf), "%f", _divisions_per_bar);
|
|
root->add_property ("divisions-per-bar", buf);
|
|
snprintf (buf, sizeof (buf), "%s", movable()?"yes":"no");
|
|
root->add_property ("movable", buf);
|
|
|
|
return *root;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
|
|
struct MetricSectionSorter {
|
|
bool operator() (const MetricSection* a, const MetricSection* b) {
|
|
return a->start() < b->start();
|
|
}
|
|
};
|
|
|
|
TempoMap::TempoMap (framecnt_t fr)
|
|
{
|
|
_frame_rate = fr;
|
|
BBT_Time start;
|
|
|
|
start.bars = 1;
|
|
start.beats = 1;
|
|
start.ticks = 0;
|
|
|
|
TempoSection *t = new TempoSection (start, _default_tempo.beats_per_minute(), _default_tempo.note_type());
|
|
MeterSection *m = new MeterSection (start, _default_meter.divisions_per_bar(), _default_meter.note_divisor());
|
|
|
|
t->set_movable (false);
|
|
m->set_movable (false);
|
|
|
|
/* note: frame time is correct (zero) for both of these */
|
|
|
|
metrics.push_back (t);
|
|
metrics.push_back (m);
|
|
}
|
|
|
|
TempoMap::~TempoMap ()
|
|
{
|
|
}
|
|
|
|
void
|
|
TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
|
|
{
|
|
bool removed = false;
|
|
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
if ((removed = remove_tempo_locked (tempo))) {
|
|
if (complete_operation) {
|
|
recompute_map (true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removed && complete_operation) {
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
}
|
|
|
|
bool
|
|
TempoMap::remove_tempo_locked (const TempoSection& tempo)
|
|
{
|
|
Metrics::iterator i;
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if (dynamic_cast<TempoSection*> (*i) != 0) {
|
|
if (tempo.frame() == (*i)->frame()) {
|
|
if ((*i)->movable()) {
|
|
metrics.erase (i);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
|
|
{
|
|
bool removed = false;
|
|
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
if ((removed = remove_meter_locked (tempo))) {
|
|
if (complete_operation) {
|
|
recompute_map (true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (removed && complete_operation) {
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
}
|
|
|
|
bool
|
|
TempoMap::remove_meter_locked (const MeterSection& tempo)
|
|
{
|
|
Metrics::iterator i;
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if (dynamic_cast<MeterSection*> (*i) != 0) {
|
|
if (tempo.frame() == (*i)->frame()) {
|
|
if ((*i)->movable()) {
|
|
metrics.erase (i);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
TempoMap::do_insert (MetricSection* section)
|
|
{
|
|
bool need_add = true;
|
|
|
|
assert (section->start().ticks == 0);
|
|
|
|
/* we only allow new meters to be inserted on beat 1 of an existing
|
|
* measure.
|
|
*/
|
|
|
|
if (dynamic_cast<MeterSection*>(section)) {
|
|
|
|
/* we need to (potentially) update the BBT times of tempo
|
|
sections based on this new meter.
|
|
*/
|
|
|
|
if ((section->start().beats != 1) || (section->start().ticks != 0)) {
|
|
|
|
BBT_Time corrected = section->start();
|
|
corrected.beats = 1;
|
|
corrected.ticks = 0;
|
|
|
|
warning << string_compose (_("Meter changes can only be positioned on the first beat of a bar. Moving from %1 to %2"),
|
|
section->start(), corrected) << endmsg;
|
|
|
|
section->set_start (corrected);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Look for any existing MetricSection that is of the same type and
|
|
in the same bar as the new one, and remove it before adding
|
|
the new one. Note that this means that if we find a matching,
|
|
existing section, we can break out of the loop since we're
|
|
guaranteed that there is only one such match.
|
|
*/
|
|
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
bool const iter_is_tempo = dynamic_cast<TempoSection*> (*i) != 0;
|
|
bool const insert_is_tempo = dynamic_cast<TempoSection*> (section) != 0;
|
|
|
|
if (iter_is_tempo && insert_is_tempo) {
|
|
|
|
/* Tempo sections */
|
|
|
|
if ((*i)->start().bars == section->start().bars &&
|
|
(*i)->start().beats == section->start().beats) {
|
|
|
|
if (!(*i)->movable()) {
|
|
|
|
/* can't (re)move this section, so overwrite
|
|
* its data content (but not its properties as
|
|
* a section).
|
|
*/
|
|
|
|
*(dynamic_cast<Tempo*>(*i)) = *(dynamic_cast<Tempo*>(section));
|
|
need_add = false;
|
|
} else {
|
|
metrics.erase (i);
|
|
}
|
|
break;
|
|
}
|
|
|
|
} else if (!iter_is_tempo && !insert_is_tempo) {
|
|
|
|
/* Meter Sections */
|
|
|
|
if ((*i)->start().bars == section->start().bars) {
|
|
|
|
if (!(*i)->movable()) {
|
|
|
|
/* can't (re)move this section, so overwrite
|
|
* its data content (but not its properties as
|
|
* a section
|
|
*/
|
|
|
|
*(dynamic_cast<Meter*>(*i)) = *(dynamic_cast<Meter*>(section));
|
|
need_add = false;
|
|
} else {
|
|
metrics.erase (i);
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
} else {
|
|
/* non-matching types, so we don't care */
|
|
}
|
|
}
|
|
|
|
/* Add the given MetricSection, if we didn't just reset an existing
|
|
* one above
|
|
*/
|
|
|
|
if (need_add) {
|
|
|
|
Metrics::iterator i;
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((*i)->start() > section->start()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
metrics.insert (i, section);
|
|
}
|
|
}
|
|
|
|
void
|
|
TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_Time& where)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
TempoSection& first (first_tempo());
|
|
|
|
if (ts.start() != first.start()) {
|
|
remove_tempo_locked (ts);
|
|
add_tempo_locked (tempo, where, true);
|
|
} else {
|
|
{
|
|
/* cannot move the first tempo section */
|
|
*static_cast<Tempo*>(&first) = tempo;
|
|
recompute_map (false);
|
|
}
|
|
}
|
|
}
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
|
|
void
|
|
TempoMap::add_tempo (const Tempo& tempo, BBT_Time where)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
add_tempo_locked (tempo, where, true);
|
|
}
|
|
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
|
|
void
|
|
TempoMap::add_tempo_locked (const Tempo& tempo, BBT_Time where, bool recompute)
|
|
{
|
|
/* new tempos always start on a beat */
|
|
where.ticks = 0;
|
|
|
|
TempoSection* ts = new TempoSection (where, tempo.beats_per_minute(), tempo.note_type());
|
|
|
|
/* find the meter to use to set the bar offset of this
|
|
* tempo section.
|
|
*/
|
|
|
|
const Meter* meter = &first_meter();
|
|
|
|
/* as we start, we are *guaranteed* to have m.meter and m.tempo pointing
|
|
at something, because we insert the default tempo and meter during
|
|
TempoMap construction.
|
|
|
|
now see if we can find better candidates.
|
|
*/
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
const MeterSection* m;
|
|
|
|
if (where < (*i)->start()) {
|
|
break;
|
|
}
|
|
|
|
if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
|
|
meter = m;
|
|
}
|
|
}
|
|
|
|
ts->update_bar_offset_from_bbt (*meter);
|
|
|
|
/* and insert it */
|
|
|
|
do_insert (ts);
|
|
|
|
if (recompute) {
|
|
recompute_map (false);
|
|
}
|
|
}
|
|
|
|
void
|
|
TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_Time& where)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
MeterSection& first (first_meter());
|
|
|
|
if (ms.start() != first.start()) {
|
|
remove_meter_locked (ms);
|
|
add_meter_locked (meter, where, true);
|
|
} else {
|
|
/* cannot move the first meter section */
|
|
*static_cast<Meter*>(&first) = meter;
|
|
recompute_map (true);
|
|
}
|
|
}
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
|
|
void
|
|
TempoMap::add_meter (const Meter& meter, BBT_Time where)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
add_meter_locked (meter, where, true);
|
|
}
|
|
|
|
|
|
#ifndef NDEBUG
|
|
if (DEBUG_ENABLED(DEBUG::TempoMap)) {
|
|
dump (std::cerr);
|
|
}
|
|
#endif
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
|
|
void
|
|
TempoMap::add_meter_locked (const Meter& meter, BBT_Time where, bool recompute)
|
|
{
|
|
/* a new meter always starts a new bar on the first beat. so
|
|
round the start time appropriately. remember that
|
|
`where' is based on the existing tempo map, not
|
|
the result after we insert the new meter.
|
|
|
|
*/
|
|
|
|
if (where.beats != 1) {
|
|
where.beats = 1;
|
|
where.bars++;
|
|
}
|
|
|
|
/* new meters *always* start on a beat. */
|
|
where.ticks = 0;
|
|
|
|
do_insert (new MeterSection (where, meter.divisions_per_bar(), meter.note_divisor()));
|
|
|
|
if (recompute) {
|
|
recompute_map (true);
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
TempoMap::change_initial_tempo (double beats_per_minute, double note_type)
|
|
{
|
|
Tempo newtempo (beats_per_minute, note_type);
|
|
TempoSection* t;
|
|
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
*((Tempo*) t) = newtempo;
|
|
recompute_map (false);
|
|
}
|
|
PropertyChanged (PropertyChange ());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TempoMap::change_existing_tempo_at (framepos_t where, double beats_per_minute, double note_type)
|
|
{
|
|
Tempo newtempo (beats_per_minute, note_type);
|
|
|
|
TempoSection* prev;
|
|
TempoSection* first;
|
|
Metrics::iterator i;
|
|
|
|
/* find the TempoSection immediately preceding "where"
|
|
*/
|
|
|
|
for (first = 0, i = metrics.begin(), prev = 0; i != metrics.end(); ++i) {
|
|
|
|
if ((*i)->frame() > where) {
|
|
break;
|
|
}
|
|
|
|
TempoSection* t;
|
|
|
|
if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
|
|
if (!first) {
|
|
first = t;
|
|
}
|
|
prev = t;
|
|
}
|
|
}
|
|
|
|
if (!prev) {
|
|
if (!first) {
|
|
error << string_compose (_("no tempo sections defined in tempo map - cannot change tempo @ %1"), where) << endmsg;
|
|
return;
|
|
}
|
|
|
|
prev = first;
|
|
}
|
|
|
|
/* reset */
|
|
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
/* cannot move the first tempo section */
|
|
*((Tempo*)prev) = newtempo;
|
|
recompute_map (false);
|
|
}
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
|
|
const MeterSection&
|
|
TempoMap::first_meter () const
|
|
{
|
|
const MeterSection *m = 0;
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((m = dynamic_cast<const MeterSection *> (*i)) != 0) {
|
|
return *m;
|
|
}
|
|
}
|
|
|
|
fatal << _("programming error: no tempo section in tempo map!") << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
return *m;
|
|
}
|
|
|
|
MeterSection&
|
|
TempoMap::first_meter ()
|
|
{
|
|
MeterSection *m = 0;
|
|
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
|
|
return *m;
|
|
}
|
|
}
|
|
|
|
fatal << _("programming error: no tempo section in tempo map!") << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
return *m;
|
|
}
|
|
|
|
const TempoSection&
|
|
TempoMap::first_tempo () const
|
|
{
|
|
const TempoSection *t = 0;
|
|
|
|
/* CALLER MUST HOLD LOCK */
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((t = dynamic_cast<const TempoSection *> (*i)) != 0) {
|
|
return *t;
|
|
}
|
|
}
|
|
|
|
fatal << _("programming error: no tempo section in tempo map!") << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
return *t;
|
|
}
|
|
|
|
TempoSection&
|
|
TempoMap::first_tempo ()
|
|
{
|
|
TempoSection *t = 0;
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((t = dynamic_cast<TempoSection *> (*i)) != 0) {
|
|
return *t;
|
|
}
|
|
}
|
|
|
|
fatal << _("programming error: no tempo section in tempo map!") << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
return *t;
|
|
}
|
|
|
|
void
|
|
TempoMap::require_map_to (framepos_t pos)
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
|
|
if (_map.empty() || _map.back().frame < pos) {
|
|
extend_map (pos);
|
|
}
|
|
}
|
|
|
|
void
|
|
TempoMap::require_map_to (const BBT_Time& bbt)
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
|
|
/* since we have no idea where BBT is if its off the map, see the last
|
|
* point in the map is past BBT, and if not add an arbitrary amount of
|
|
* time until it is.
|
|
*/
|
|
|
|
int additional_minutes = 1;
|
|
|
|
while (1) {
|
|
if (!_map.empty() && _map.back().bar >= (bbt.bars + 1)) {
|
|
break;
|
|
}
|
|
/* add some more distance, using bigger steps each time */
|
|
extend_map (_map.back().frame + (_frame_rate * 60 * additional_minutes));
|
|
additional_minutes *= 2;
|
|
}
|
|
}
|
|
|
|
void
|
|
TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
|
|
{
|
|
/* CALLER MUST HOLD WRITE LOCK */
|
|
|
|
MeterSection* meter = 0;
|
|
TempoSection* tempo = 0;
|
|
double current_frame;
|
|
BBT_Time current;
|
|
Metrics::iterator next_metric;
|
|
|
|
if (end < 0) {
|
|
|
|
/* we will actually stop once we hit
|
|
the last metric.
|
|
*/
|
|
end = max_framepos;
|
|
|
|
} else {
|
|
if (!_map.empty ()) {
|
|
/* never allow the map to be shortened */
|
|
end = max (end, _map.back().frame);
|
|
}
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
|
|
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
MeterSection* ms;
|
|
|
|
if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
|
|
meter = ms;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(meter);
|
|
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
TempoSection* ts;
|
|
|
|
if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
|
|
tempo = ts;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(tempo);
|
|
|
|
/* assumes that the first meter & tempo are at frame zero */
|
|
current_frame = 0;
|
|
meter->set_frame (0);
|
|
tempo->set_frame (0);
|
|
|
|
/* assumes that the first meter & tempo are at 1|1|0 */
|
|
current.bars = 1;
|
|
current.beats = 1;
|
|
current.ticks = 0;
|
|
|
|
if (reassign_tempo_bbt) {
|
|
|
|
MeterSection* rmeter = meter;
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
|
|
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
TempoSection* ts;
|
|
MeterSection* ms;
|
|
|
|
if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
|
|
|
|
/* reassign the BBT time of this tempo section
|
|
* based on its bar offset position.
|
|
*/
|
|
|
|
ts->update_bbt_time_from_bar_offset (*rmeter);
|
|
|
|
} else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
|
|
rmeter = ms;
|
|
} else {
|
|
fatal << _("programming error: unhandled MetricSection type") << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2\n", *((Meter*)meter), *((Tempo*)tempo)));
|
|
|
|
next_metric = metrics.begin();
|
|
++next_metric; // skip meter (or tempo)
|
|
++next_metric; // skip tempo (or meter)
|
|
|
|
_map.clear ();
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add first bar at 1|1 @ %2\n", current.bars, current_frame));
|
|
_map.push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current_frame), 1, 1));
|
|
|
|
if (end == 0) {
|
|
/* silly call from Session::process() during startup
|
|
*/
|
|
return;
|
|
}
|
|
|
|
_extend_map (tempo, meter, next_metric, current, current_frame, end);
|
|
}
|
|
|
|
void
|
|
TempoMap::extend_map (framepos_t end)
|
|
{
|
|
/* CALLER MUST HOLD WRITE LOCK */
|
|
|
|
if (_map.empty()) {
|
|
recompute_map (false, end);
|
|
return;
|
|
}
|
|
|
|
BBTPointList::const_iterator i = _map.end();
|
|
Metrics::iterator next_metric;
|
|
|
|
--i;
|
|
|
|
BBT_Time last_metric_start;
|
|
|
|
if ((*i).tempo->frame() > (*i).meter->frame()) {
|
|
last_metric_start = (*i).tempo->start();
|
|
} else {
|
|
last_metric_start = (*i).meter->start();
|
|
}
|
|
|
|
/* find the metric immediately after the tempo + meter sections for the
|
|
* last point in the map
|
|
*/
|
|
|
|
for (next_metric = metrics.begin(); next_metric != metrics.end(); ++next_metric) {
|
|
if ((*next_metric)->start() > last_metric_start) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* we cast away const here because this is the one place where we need
|
|
* to actually modify the frame time of each metric section.
|
|
*/
|
|
|
|
_extend_map (const_cast<TempoSection*> ((*i).tempo),
|
|
const_cast<MeterSection*> ((*i).meter),
|
|
next_metric, BBT_Time ((*i).bar, (*i).beat, 0), (*i).frame, end);
|
|
}
|
|
|
|
void
|
|
TempoMap::_extend_map (TempoSection* tempo, MeterSection* meter,
|
|
Metrics::iterator next_metric,
|
|
BBT_Time current, framepos_t current_frame, framepos_t end)
|
|
{
|
|
/* CALLER MUST HOLD WRITE LOCK */
|
|
|
|
TempoSection* ts;
|
|
MeterSection* ms;
|
|
double beat_frames;
|
|
double current_frame_exact;
|
|
framepos_t bar_start_frame;
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Extend map to %1 from %2 = %3\n", end, current, current_frame));
|
|
|
|
if (current.beats == 1) {
|
|
bar_start_frame = current_frame;
|
|
} else {
|
|
bar_start_frame = 0;
|
|
}
|
|
|
|
beat_frames = meter->frames_per_grid (*tempo,_frame_rate);
|
|
current_frame_exact = current_frame;
|
|
|
|
while (current_frame < end) {
|
|
|
|
current.beats++;
|
|
current_frame_exact += beat_frames;
|
|
current_frame = llrint(current_frame_exact);
|
|
|
|
if (current.beats > meter->divisions_per_bar()) {
|
|
current.bars++;
|
|
current.beats = 1;
|
|
}
|
|
|
|
if (next_metric != metrics.end()) {
|
|
|
|
/* no operator >= so invert operator < */
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1 next metric @ %2\n", current, (*next_metric)->start()));
|
|
|
|
if (!(current < (*next_metric)->start())) {
|
|
|
|
set_metrics:
|
|
if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0)) {
|
|
|
|
tempo = ts;
|
|
|
|
/* new tempo section: if its on a beat,
|
|
* we don't have to do anything other
|
|
* than recompute various distances,
|
|
* done further below as we transition
|
|
* the next metric section.
|
|
*
|
|
* if its not on the beat, we have to
|
|
* compute the duration of the beat it
|
|
* is within, which will be different
|
|
* from the preceding following ones
|
|
* since it takes part of its duration
|
|
* from the preceding tempo and part
|
|
* from this new tempo.
|
|
*/
|
|
|
|
if (tempo->start().ticks != 0) {
|
|
|
|
double next_beat_frames = tempo->frames_per_beat (_frame_rate);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
|
|
tempo->start(), current_frame, tempo->bar_offset()));
|
|
|
|
/* back up to previous beat */
|
|
current_frame_exact -= beat_frames;
|
|
current_frame = llrint(current_frame_exact);
|
|
|
|
/* set tempo section location
|
|
* based on offset from last
|
|
* bar start
|
|
*/
|
|
tempo->set_frame (bar_start_frame +
|
|
llrint ((ts->bar_offset() * meter->divisions_per_bar() * beat_frames)));
|
|
|
|
/* advance to the location of
|
|
* the new (adjusted) beat. do
|
|
* this by figuring out the
|
|
* offset within the beat that
|
|
* would have been there
|
|
* without the tempo
|
|
* change. then stretch the
|
|
* beat accordingly.
|
|
*/
|
|
|
|
double offset_within_old_beat = (tempo->frame() - current_frame) / beat_frames;
|
|
|
|
current_frame_exact += (offset_within_old_beat * beat_frames) + ((1.0 - offset_within_old_beat) * next_beat_frames);
|
|
current_frame = llrint(current_frame_exact);
|
|
|
|
/* next metric doesn't have to
|
|
* match this precisely to
|
|
* merit a reloop ...
|
|
*/
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Adjusted last beat to %1\n", current_frame));
|
|
|
|
} else {
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into beat-aligned tempo metric at %1 = %2\n",
|
|
tempo->start(), current_frame));
|
|
tempo->set_frame (current_frame);
|
|
}
|
|
|
|
} else if ((ms = dynamic_cast<MeterSection*>(*next_metric)) != 0) {
|
|
|
|
meter = ms;
|
|
|
|
/* new meter section: always defines the
|
|
* start of a bar.
|
|
*/
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into meter section at %1 vs %2 (%3)\n",
|
|
meter->start(), current, current_frame));
|
|
|
|
assert (current.beats == 1);
|
|
|
|
meter->set_frame (current_frame);
|
|
}
|
|
|
|
beat_frames = meter->frames_per_grid (*tempo, _frame_rate);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
|
|
beat_frames, meter->divisions_per_bar(), *((Meter*)meter), *((Tempo*)tempo)));
|
|
|
|
++next_metric;
|
|
|
|
if (next_metric != metrics.end() && ((*next_metric)->start() == current)) {
|
|
/* same position so go back and set this one up before advancing
|
|
*/
|
|
goto set_metrics;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (current.beats == 1) {
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", current.bars, current_frame));
|
|
_map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, 1));
|
|
bar_start_frame = current_frame;
|
|
} else {
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", current.bars, current.beats, current_frame));
|
|
_map.push_back (BBTPoint (*meter, *tempo, current_frame, current.bars, current.beats));
|
|
}
|
|
|
|
if (next_metric == metrics.end()) {
|
|
/* no more metrics - we've timestamped them all, stop here */
|
|
if (end == max_framepos) {
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("stop extending map now that we've reach the end @ %1|%2 = %3\n",
|
|
current.bars, current.beats, current_frame));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at (framepos_t frame, Metrics::const_iterator* last) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
TempoMetric m (first_meter(), first_tempo());
|
|
|
|
/* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
|
|
at something, because we insert the default tempo and meter during
|
|
TempoMap construction.
|
|
|
|
now see if we can find better candidates.
|
|
*/
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
if ((*i)->frame() > frame) {
|
|
break;
|
|
}
|
|
|
|
m.set_metric(*i);
|
|
|
|
if (last) {
|
|
*last = i;
|
|
}
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
TempoMetric
|
|
TempoMap::metric_at (BBT_Time bbt) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
TempoMetric m (first_meter(), first_tempo());
|
|
|
|
/* at this point, we are *guaranteed* to have m.meter and m.tempo pointing
|
|
at something, because we insert the default tempo and meter during
|
|
TempoMap construction.
|
|
|
|
now see if we can find better candidates.
|
|
*/
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
BBT_Time section_start ((*i)->start());
|
|
|
|
if (section_start.bars > bbt.bars || (section_start.bars == bbt.bars && section_start.beats > bbt.beats)) {
|
|
break;
|
|
}
|
|
|
|
m.set_metric (*i);
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
void
|
|
TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt)
|
|
{
|
|
require_map_to (frame);
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
|
|
if (frame < 0) {
|
|
bbt.bars = 1;
|
|
bbt.beats = 1;
|
|
bbt.ticks = 0;
|
|
warning << string_compose (_("tempo map asked for BBT time at frame %1\n"), frame) << endmsg;
|
|
return;
|
|
}
|
|
|
|
return bbt_time (frame, bbt, bbt_before_or_at (frame));
|
|
}
|
|
|
|
void
|
|
TempoMap::bbt_time_rt (framepos_t frame, BBT_Time& bbt)
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
|
|
|
|
if (!lm.locked()) {
|
|
throw std::logic_error ("TempoMap::bbt_time_rt() could not lock tempo map");
|
|
}
|
|
|
|
if (_map.empty() || _map.back().frame < frame) {
|
|
throw std::logic_error (string_compose ("map not long enough to reach %1", frame));
|
|
}
|
|
|
|
return bbt_time (frame, bbt, bbt_before_or_at (frame));
|
|
}
|
|
|
|
void
|
|
TempoMap::bbt_time (framepos_t frame, BBT_Time& bbt, const BBTPointList::const_iterator& i)
|
|
{
|
|
/* CALLER MUST HOLD READ LOCK */
|
|
|
|
bbt.bars = (*i).bar;
|
|
bbt.beats = (*i).beat;
|
|
|
|
if ((*i).frame == frame) {
|
|
bbt.ticks = 0;
|
|
} else {
|
|
bbt.ticks = llrint (((frame - (*i).frame) / (*i).tempo->frames_per_beat(_frame_rate)) *
|
|
BBT_Time::ticks_per_beat);
|
|
}
|
|
}
|
|
|
|
framepos_t
|
|
TempoMap::frame_time (const BBT_Time& bbt)
|
|
{
|
|
if (bbt.bars < 1) {
|
|
warning << string_compose (_("tempo map asked for frame time at bar < 1 (%1)\n"), bbt) << endmsg;
|
|
return 0;
|
|
}
|
|
|
|
if (bbt.beats < 1) {
|
|
throw std::logic_error ("beats are counted from one");
|
|
}
|
|
|
|
require_map_to (bbt);
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
|
|
BBTPointList::const_iterator s = bbt_before_or_at (BBT_Time (1, 1, 0));
|
|
BBTPointList::const_iterator e = bbt_before_or_at (BBT_Time (bbt.bars, bbt.beats, 0));
|
|
|
|
if (bbt.ticks != 0) {
|
|
return ((*e).frame - (*s).frame) +
|
|
llrint ((*e).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat));
|
|
} else {
|
|
return ((*e).frame - (*s).frame);
|
|
}
|
|
}
|
|
|
|
framecnt_t
|
|
TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
|
|
{
|
|
BBT_Time when;
|
|
bbt_time (pos, when);
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
return bbt_duration_at_unlocked (when, bbt, dir);
|
|
}
|
|
|
|
framecnt_t
|
|
TempoMap::bbt_duration_at_unlocked (const BBT_Time& when, const BBT_Time& bbt, int /*dir*/)
|
|
{
|
|
if (bbt.bars == 0 && bbt.beats == 0 && bbt.ticks == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* round back to the previous precise beat */
|
|
BBTPointList::const_iterator wi = bbt_before_or_at (BBT_Time (when.bars, when.beats, 0));
|
|
BBTPointList::const_iterator start (wi);
|
|
|
|
assert (wi != _map.end());
|
|
|
|
uint32_t bars = 0;
|
|
uint32_t beats = 0;
|
|
|
|
while (wi != _map.end() && bars < bbt.bars) {
|
|
++wi;
|
|
if ((*wi).is_bar()) {
|
|
++bars;
|
|
}
|
|
}
|
|
assert (wi != _map.end());
|
|
|
|
while (wi != _map.end() && beats < bbt.beats) {
|
|
++wi;
|
|
++beats;
|
|
}
|
|
assert (wi != _map.end());
|
|
|
|
/* add any additional frames related to ticks in the added value */
|
|
|
|
if (bbt.ticks != 0) {
|
|
return ((*wi).frame - (*start).frame) +
|
|
(*wi).tempo->frames_per_beat (_frame_rate) * (bbt.ticks/BBT_Time::ticks_per_beat);
|
|
} else {
|
|
return ((*wi).frame - (*start).frame);
|
|
}
|
|
}
|
|
|
|
framepos_t
|
|
TempoMap::round_to_bar (framepos_t fr, RoundMode dir)
|
|
{
|
|
return round_to_type (fr, dir, Bar);
|
|
}
|
|
|
|
framepos_t
|
|
TempoMap::round_to_beat (framepos_t fr, RoundMode dir)
|
|
{
|
|
return round_to_type (fr, dir, Beat);
|
|
}
|
|
|
|
framepos_t
|
|
TempoMap::round_to_beat_subdivision (framepos_t fr, int sub_num, RoundMode dir)
|
|
{
|
|
require_map_to (fr);
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
BBTPointList::const_iterator i = bbt_before_or_at (fr);
|
|
BBT_Time the_beat;
|
|
uint32_t ticks_one_subdivisions_worth;
|
|
|
|
bbt_time (fr, the_beat, i);
|
|
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round %1 to nearest 1/%2 beat, before-or-at = %3 @ %4|%5 precise = %6\n",
|
|
fr, sub_num, (*i).frame, (*i).bar, (*i).beat, the_beat));
|
|
|
|
ticks_one_subdivisions_worth = (uint32_t)BBT_Time::ticks_per_beat / sub_num;
|
|
|
|
if (dir > 0) {
|
|
|
|
/* round to next (or same iff dir == RoundUpMaybe) */
|
|
|
|
uint32_t mod = the_beat.ticks % ticks_one_subdivisions_worth;
|
|
|
|
if (mod == 0 && dir == RoundUpMaybe) {
|
|
/* right on the subdivision, which is fine, so do nothing */
|
|
|
|
} else if (mod == 0) {
|
|
/* right on the subdivision, so the difference is just the subdivision ticks */
|
|
the_beat.ticks += ticks_one_subdivisions_worth;
|
|
|
|
} else {
|
|
/* not on subdivision, compute distance to next subdivision */
|
|
|
|
the_beat.ticks += ticks_one_subdivisions_worth - mod;
|
|
}
|
|
|
|
if (the_beat.ticks > BBT_Time::ticks_per_beat) {
|
|
assert (i != _map.end());
|
|
++i;
|
|
assert (i != _map.end());
|
|
the_beat.ticks -= BBT_Time::ticks_per_beat;
|
|
}
|
|
|
|
|
|
} else if (dir < 0) {
|
|
|
|
/* round to previous (or same iff dir == RoundDownMaybe) */
|
|
|
|
uint32_t difference = the_beat.ticks % ticks_one_subdivisions_worth;
|
|
|
|
if (difference == 0 && dir == RoundDownAlways) {
|
|
/* right on the subdivision, but force-rounding down,
|
|
so the difference is just the subdivision ticks */
|
|
difference = ticks_one_subdivisions_worth;
|
|
}
|
|
|
|
if (the_beat.ticks < difference) {
|
|
if (i == _map.begin()) {
|
|
/* can't go backwards from wherever pos is, so just return it */
|
|
return fr;
|
|
}
|
|
--i;
|
|
the_beat.ticks = BBT_Time::ticks_per_beat - the_beat.ticks;
|
|
} else {
|
|
the_beat.ticks -= difference;
|
|
}
|
|
|
|
} else {
|
|
/* round to nearest */
|
|
|
|
double rem;
|
|
|
|
/* compute the distance to the previous and next subdivision */
|
|
|
|
if ((rem = fmod ((double) the_beat.ticks, (double) ticks_one_subdivisions_worth)) > ticks_one_subdivisions_worth/2.0) {
|
|
|
|
/* closer to the next subdivision, so shift forward */
|
|
|
|
the_beat.ticks = lrint (the_beat.ticks + (ticks_one_subdivisions_worth - rem));
|
|
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved forward to %1\n", the_beat.ticks));
|
|
|
|
if (the_beat.ticks > BBT_Time::ticks_per_beat) {
|
|
assert (i != _map.end());
|
|
++i;
|
|
assert (i != _map.end());
|
|
the_beat.ticks -= BBT_Time::ticks_per_beat;
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("fold beat to %1\n", the_beat));
|
|
}
|
|
|
|
} else if (rem > 0) {
|
|
|
|
/* closer to previous subdivision, so shift backward */
|
|
|
|
if (rem > the_beat.ticks) {
|
|
if (i == _map.begin()) {
|
|
/* can't go backwards past zero, so ... */
|
|
return 0;
|
|
}
|
|
/* step back to previous beat */
|
|
--i;
|
|
the_beat.ticks = lrint (BBT_Time::ticks_per_beat - rem);
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("step back beat to %1\n", the_beat));
|
|
} else {
|
|
the_beat.ticks = lrint (the_beat.ticks - rem);
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("moved backward to %1\n", the_beat.ticks));
|
|
}
|
|
} else {
|
|
/* on the subdivision, do nothing */
|
|
}
|
|
}
|
|
|
|
return (*i).frame + (the_beat.ticks/BBT_Time::ticks_per_beat) *
|
|
(*i).tempo->frames_per_beat (_frame_rate);
|
|
}
|
|
|
|
framepos_t
|
|
TempoMap::round_to_type (framepos_t frame, RoundMode dir, BBTPointType type)
|
|
{
|
|
require_map_to (frame);
|
|
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
BBTPointList::const_iterator fi;
|
|
|
|
if (dir > 0) {
|
|
fi = bbt_after_or_at (frame);
|
|
} else {
|
|
fi = bbt_before_or_at (frame);
|
|
}
|
|
|
|
assert (fi != _map.end());
|
|
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("round from %1 (%3|%4 @ %5) to %6 in direction %2\n", frame, dir, (*fi).bar, (*fi).beat, (*fi).frame,
|
|
(type == Bar ? "bar" : "beat")));
|
|
|
|
switch (type) {
|
|
case Bar:
|
|
if (dir < 0) {
|
|
/* find bar previous to 'frame' */
|
|
|
|
if (fi == _map.begin()) {
|
|
return 0;
|
|
}
|
|
|
|
if ((*fi).is_bar() && (*fi).frame == frame) {
|
|
if (dir == RoundDownMaybe) {
|
|
return frame;
|
|
}
|
|
--fi;
|
|
}
|
|
|
|
while (!(*fi).is_bar()) {
|
|
if (fi == _map.begin()) {
|
|
break;
|
|
}
|
|
fi--;
|
|
}
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
|
|
(*fi).bar, (*fi).beat, (*fi).frame));
|
|
return (*fi).frame;
|
|
|
|
} else if (dir > 0) {
|
|
|
|
/* find bar following 'frame' */
|
|
|
|
if ((*fi).is_bar() && (*fi).frame == frame) {
|
|
if (dir == RoundUpMaybe) {
|
|
return frame;
|
|
}
|
|
++fi;
|
|
}
|
|
|
|
while (!(*fi).is_bar()) {
|
|
fi++;
|
|
if (fi == _map.end()) {
|
|
--fi;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to bar: map iter at %1|%2 %3, return\n",
|
|
(*fi).bar, (*fi).beat, (*fi).frame));
|
|
return (*fi).frame;
|
|
|
|
} else {
|
|
|
|
/* true rounding: find nearest bar */
|
|
|
|
BBTPointList::const_iterator prev = fi;
|
|
BBTPointList::const_iterator next = fi;
|
|
|
|
if ((*fi).frame == frame) {
|
|
return frame;
|
|
}
|
|
|
|
while ((*prev).beat != 1) {
|
|
if (prev == _map.begin()) {
|
|
break;
|
|
}
|
|
prev--;
|
|
}
|
|
|
|
while ((next != _map.end()) && (*next).beat != 1) {
|
|
next++;
|
|
}
|
|
|
|
if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
|
|
return (*prev).frame;
|
|
} else {
|
|
return (*next).frame;
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Beat:
|
|
if (dir < 0) {
|
|
|
|
if (fi == _map.begin()) {
|
|
return 0;
|
|
}
|
|
|
|
if ((*fi).frame > frame || ((*fi).frame == frame && dir == RoundDownAlways)) {
|
|
DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step back\n");
|
|
--fi;
|
|
}
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
|
|
(*fi).bar, (*fi).beat, (*fi).frame));
|
|
return (*fi).frame;
|
|
} else if (dir > 0) {
|
|
if ((*fi).frame < frame || ((*fi).frame == frame && dir == RoundUpAlways)) {
|
|
DEBUG_TRACE (DEBUG::SnapBBT, "requested frame is on beat, step forward\n");
|
|
++fi;
|
|
}
|
|
DEBUG_TRACE (DEBUG::SnapBBT, string_compose ("rounded to beat: map iter at %1|%2 %3, return\n",
|
|
(*fi).bar, (*fi).beat, (*fi).frame));
|
|
return (*fi).frame;
|
|
} else {
|
|
/* find beat nearest to frame */
|
|
if ((*fi).frame == frame) {
|
|
return frame;
|
|
}
|
|
|
|
BBTPointList::const_iterator prev = fi;
|
|
BBTPointList::const_iterator next = fi;
|
|
|
|
/* fi is already the beat before_or_at frame, and
|
|
we've just established that its not at frame, so its
|
|
the beat before frame.
|
|
*/
|
|
++next;
|
|
|
|
if ((next == _map.end()) || (frame - (*prev).frame) < ((*next).frame - frame)) {
|
|
return (*prev).frame;
|
|
} else {
|
|
return (*next).frame;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
abort(); /* NOTREACHED */
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
TempoMap::get_grid (TempoMap::BBTPointList::const_iterator& begin,
|
|
TempoMap::BBTPointList::const_iterator& end,
|
|
framepos_t lower, framepos_t upper)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
if (_map.empty() || (_map.back().frame < upper)) {
|
|
recompute_map (false, upper);
|
|
}
|
|
}
|
|
|
|
begin = lower_bound (_map.begin(), _map.end(), lower);
|
|
end = upper_bound (_map.begin(), _map.end(), upper);
|
|
}
|
|
|
|
const TempoSection&
|
|
TempoMap::tempo_section_at (framepos_t frame) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
Metrics::const_iterator i;
|
|
TempoSection* prev = 0;
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
TempoSection* t;
|
|
|
|
if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
|
|
|
|
if ((*i)->frame() > frame) {
|
|
break;
|
|
}
|
|
|
|
prev = t;
|
|
}
|
|
}
|
|
|
|
if (prev == 0) {
|
|
fatal << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
|
|
return *prev;
|
|
}
|
|
|
|
const Tempo&
|
|
TempoMap::tempo_at (framepos_t frame) const
|
|
{
|
|
TempoMetric m (metric_at (frame));
|
|
return m.tempo();
|
|
}
|
|
|
|
const MeterSection&
|
|
TempoMap::meter_section_at (framepos_t frame) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
Metrics::const_iterator i;
|
|
MeterSection* prev = 0;
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
MeterSection* t;
|
|
|
|
if ((t = dynamic_cast<MeterSection*> (*i)) != 0) {
|
|
|
|
if ((*i)->frame() > frame) {
|
|
break;
|
|
}
|
|
|
|
prev = t;
|
|
}
|
|
}
|
|
|
|
if (prev == 0) {
|
|
fatal << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
|
|
return *prev;
|
|
}
|
|
|
|
const Meter&
|
|
TempoMap::meter_at (framepos_t frame) const
|
|
{
|
|
TempoMetric m (metric_at (frame));
|
|
return m.meter();
|
|
}
|
|
|
|
XMLNode&
|
|
TempoMap::get_state ()
|
|
{
|
|
Metrics::const_iterator i;
|
|
XMLNode *root = new XMLNode ("TempoMap");
|
|
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
root->add_child_nocopy ((*i)->get_state());
|
|
}
|
|
}
|
|
|
|
return *root;
|
|
}
|
|
|
|
int
|
|
TempoMap::set_state (const XMLNode& node, int /*version*/)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
|
|
XMLNodeList nlist;
|
|
XMLNodeConstIterator niter;
|
|
Metrics old_metrics (metrics);
|
|
MeterSection* last_meter = 0;
|
|
metrics.clear();
|
|
|
|
nlist = node.children();
|
|
|
|
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
|
XMLNode* child = *niter;
|
|
|
|
if (child->name() == TempoSection::xml_state_node_name) {
|
|
|
|
try {
|
|
TempoSection* ts = new TempoSection (*child);
|
|
metrics.push_back (ts);
|
|
|
|
if (ts->bar_offset() < 0.0) {
|
|
if (last_meter) {
|
|
ts->update_bar_offset_from_bbt (*last_meter);
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (failed_constructor& err){
|
|
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
|
|
metrics = old_metrics;
|
|
break;
|
|
}
|
|
|
|
} else if (child->name() == MeterSection::xml_state_node_name) {
|
|
|
|
try {
|
|
MeterSection* ms = new MeterSection (*child);
|
|
metrics.push_back (ms);
|
|
last_meter = ms;
|
|
}
|
|
|
|
catch (failed_constructor& err) {
|
|
error << _("Tempo map: could not set new state, restoring old one.") << endmsg;
|
|
metrics = old_metrics;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (niter == nlist.end()) {
|
|
MetricSectionSorter cmp;
|
|
metrics.sort (cmp);
|
|
}
|
|
|
|
/* check for multiple tempo/meters at the same location, which
|
|
ardour2 somehow allowed.
|
|
*/
|
|
|
|
Metrics::iterator prev = metrics.end();
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if (prev != metrics.end()) {
|
|
if (dynamic_cast<MeterSection*>(*prev) && dynamic_cast<MeterSection*>(*i)) {
|
|
if ((*prev)->start() == (*i)->start()) {
|
|
cerr << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
|
|
error << string_compose (_("Multiple meter definitions found at %1"), (*prev)->start()) << endmsg;
|
|
return -1;
|
|
}
|
|
} else if (dynamic_cast<TempoSection*>(*prev) && dynamic_cast<TempoSection*>(*i)) {
|
|
if ((*prev)->start() == (*i)->start()) {
|
|
cerr << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
|
|
error << string_compose (_("Multiple tempo definitions found at %1"), (*prev)->start()) << endmsg;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
prev = i;
|
|
}
|
|
|
|
recompute_map (true, -1);
|
|
}
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
TempoMap::dump (std::ostream& o) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock, Glib::Threads::TRY_LOCK);
|
|
const MeterSection* m;
|
|
const TempoSection* t;
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
|
|
o << "Tempo @ " << *i << " (Bar-offset: " << t->bar_offset() << ") " << t->beats_per_minute() << " BPM (pulse = 1/" << t->note_type() << ") at " << t->start() << " frame= " << t->frame() << " (movable? "
|
|
<< t->movable() << ')' << endl;
|
|
} else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
|
|
o << "Meter @ " << *i << ' ' << m->divisions_per_bar() << '/' << m->note_divisor() << " at " << m->start() << " frame= " << m->frame()
|
|
<< " (movable? " << m->movable() << ')' << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
TempoMap::n_tempos() const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
int cnt = 0;
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if (dynamic_cast<const TempoSection*>(*i) != 0) {
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
int
|
|
TempoMap::n_meters() const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
int cnt = 0;
|
|
|
|
for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if (dynamic_cast<const MeterSection*>(*i) != 0) {
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
void
|
|
TempoMap::insert_time (framepos_t where, framecnt_t amount)
|
|
{
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((*i)->frame() >= where && (*i)->movable ()) {
|
|
(*i)->set_frame ((*i)->frame() + amount);
|
|
}
|
|
}
|
|
|
|
/* now reset the BBT time of all metrics, based on their new
|
|
* audio time. This is the only place where we do this reverse
|
|
* timestamp.
|
|
*/
|
|
|
|
Metrics::iterator i;
|
|
const MeterSection* meter;
|
|
const TempoSection* tempo;
|
|
MeterSection *m;
|
|
TempoSection *t;
|
|
|
|
meter = &first_meter ();
|
|
tempo = &first_tempo ();
|
|
|
|
BBT_Time start;
|
|
BBT_Time end;
|
|
|
|
// cerr << "\n###################### TIMESTAMP via AUDIO ##############\n" << endl;
|
|
|
|
bool first = true;
|
|
MetricSection* prev = 0;
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
BBT_Time bbt;
|
|
TempoMetric metric (*meter, *tempo);
|
|
|
|
if (prev) {
|
|
metric.set_start (prev->start());
|
|
metric.set_frame (prev->frame());
|
|
} else {
|
|
// metric will be at frames=0 bbt=1|1|0 by default
|
|
// which is correct for our purpose
|
|
}
|
|
|
|
BBTPointList::const_iterator bi = bbt_before_or_at ((*i)->frame());
|
|
bbt_time ((*i)->frame(), bbt, bi);
|
|
|
|
// cerr << "timestamp @ " << (*i)->frame() << " with " << bbt.bars << "|" << bbt.beats << "|" << bbt.ticks << " => ";
|
|
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
|
|
if (bbt.ticks > BBT_Time::ticks_per_beat/2) {
|
|
/* round up to next beat */
|
|
bbt.beats += 1;
|
|
}
|
|
|
|
bbt.ticks = 0;
|
|
|
|
if (bbt.beats != 1) {
|
|
/* round up to next bar */
|
|
bbt.bars += 1;
|
|
bbt.beats = 1;
|
|
}
|
|
}
|
|
|
|
// cerr << bbt << endl;
|
|
|
|
(*i)->set_start (bbt);
|
|
|
|
if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
|
|
tempo = t;
|
|
// cerr << "NEW TEMPO, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
|
|
} else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
|
|
meter = m;
|
|
// cerr << "NEW METER, frame = " << (*i)->frame() << " start = " << (*i)->start() <<endl;
|
|
} else {
|
|
fatal << _("programming error: unhandled MetricSection type") << endmsg;
|
|
abort(); /*NOTREACHED*/
|
|
}
|
|
|
|
prev = (*i);
|
|
}
|
|
|
|
recompute_map (true);
|
|
}
|
|
|
|
|
|
PropertyChanged (PropertyChange ());
|
|
}
|
|
bool
|
|
TempoMap::remove_time (framepos_t where, framecnt_t amount)
|
|
{
|
|
bool moved = false;
|
|
|
|
std::list<MetricSection*> metric_kill_list;
|
|
|
|
TempoSection* last_tempo = NULL;
|
|
MeterSection* last_meter = NULL;
|
|
bool tempo_after = false; // is there a tempo marker at the first sample after the removed range?
|
|
bool meter_after = false; // is there a meter marker likewise?
|
|
{
|
|
Glib::Threads::RWLock::WriterLock lm (lock);
|
|
for (Metrics::iterator i = metrics.begin(); i != metrics.end(); ++i) {
|
|
if ((*i)->frame() >= where && (*i)->frame() < where+amount) {
|
|
metric_kill_list.push_back(*i);
|
|
TempoSection *lt = dynamic_cast<TempoSection*> (*i);
|
|
if (lt)
|
|
last_tempo = lt;
|
|
MeterSection *lm = dynamic_cast<MeterSection*> (*i);
|
|
if (lm)
|
|
last_meter = lm;
|
|
}
|
|
else if ((*i)->frame() >= where) {
|
|
// TODO: make sure that moved tempo/meter markers are rounded to beat/bar boundaries
|
|
(*i)->set_frame ((*i)->frame() - amount);
|
|
if ((*i)->frame() == where) {
|
|
// marker was immediately after end of range
|
|
tempo_after = dynamic_cast<TempoSection*> (*i);
|
|
meter_after = dynamic_cast<MeterSection*> (*i);
|
|
}
|
|
moved = true;
|
|
}
|
|
}
|
|
|
|
//find the last TEMPO and METER metric (if any) and move it to the cut point so future stuff is correct
|
|
if (last_tempo && !tempo_after) {
|
|
metric_kill_list.remove(last_tempo);
|
|
last_tempo->set_frame(where);
|
|
moved = true;
|
|
}
|
|
if (last_meter && !meter_after) {
|
|
metric_kill_list.remove(last_meter);
|
|
last_meter->set_frame(where);
|
|
moved = true;
|
|
}
|
|
|
|
//remove all the remaining metrics
|
|
for (std::list<MetricSection*>::iterator i = metric_kill_list.begin(); i != metric_kill_list.end(); ++i) {
|
|
metrics.remove(*i);
|
|
moved = true;
|
|
}
|
|
|
|
if (moved) {
|
|
recompute_map (true);
|
|
}
|
|
}
|
|
PropertyChanged (PropertyChange ());
|
|
return moved;
|
|
}
|
|
|
|
/** Add some (fractional) beats to a session frame position, and return the result in frames.
|
|
* pos can be -ve, if required.
|
|
*/
|
|
framepos_t
|
|
TempoMap::framepos_plus_beats (framepos_t pos, Evoral::Beats beats) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
Metrics::const_iterator next_tempo;
|
|
const TempoSection* tempo = 0;
|
|
|
|
/* Find the starting tempo metric */
|
|
|
|
for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
|
|
|
|
const TempoSection* t;
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
|
|
|
|
/* This is a bit of a hack, but pos could be -ve, and if it is,
|
|
we consider the initial metric changes (at time 0) to actually
|
|
be in effect at pos.
|
|
*/
|
|
|
|
framepos_t f = (*next_tempo)->frame ();
|
|
|
|
if (pos < 0 && f == 0) {
|
|
f = pos;
|
|
}
|
|
|
|
if (f > pos) {
|
|
break;
|
|
}
|
|
|
|
tempo = t;
|
|
}
|
|
}
|
|
|
|
/* We now have:
|
|
|
|
tempo -> the Tempo for "pos"
|
|
next_tempo -> first tempo after "pos", possibly metrics.end()
|
|
*/
|
|
assert(tempo);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath,
|
|
string_compose ("frame %1 plus %2 beats, start with tempo = %3 @ %4\n",
|
|
pos, beats, *((const Tempo*)tempo), tempo->frame()));
|
|
|
|
while (!!beats) {
|
|
|
|
/* Distance to the end of this section in frames */
|
|
framecnt_t distance_frames = (next_tempo == metrics.end() ? max_framepos : ((*next_tempo)->frame() - pos));
|
|
|
|
/* Distance to the end in beats */
|
|
Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate(
|
|
distance_frames, tempo->frames_per_beat (_frame_rate));
|
|
|
|
/* Amount to subtract this time */
|
|
Evoral::Beats const delta = min (distance_beats, beats);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
|
|
(next_tempo == metrics.end() ? max_framepos : (*next_tempo)->frame()),
|
|
distance_frames, distance_beats));
|
|
|
|
/* Update */
|
|
beats -= delta;
|
|
pos += delta.to_ticks(tempo->frames_per_beat (_frame_rate));
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left\n", pos, beats));
|
|
|
|
/* step forwards to next tempo section */
|
|
|
|
if (next_tempo != metrics.end()) {
|
|
|
|
tempo = dynamic_cast<const TempoSection*>(*next_tempo);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
|
|
*((const Tempo*)tempo), tempo->frame(),
|
|
tempo->frames_per_beat (_frame_rate)));
|
|
|
|
while (next_tempo != metrics.end ()) {
|
|
|
|
++next_tempo;
|
|
|
|
if (next_tempo != metrics.end() && dynamic_cast<const TempoSection*>(*next_tempo)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/** Subtract some (fractional) beats from a frame position, and return the result in frames */
|
|
framepos_t
|
|
TempoMap::framepos_minus_beats (framepos_t pos, Evoral::Beats beats) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
Metrics::const_reverse_iterator prev_tempo;
|
|
const TempoSection* tempo = 0;
|
|
|
|
/* Find the starting tempo metric */
|
|
|
|
for (prev_tempo = metrics.rbegin(); prev_tempo != metrics.rend(); ++prev_tempo) {
|
|
|
|
const TempoSection* t;
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*prev_tempo)) != 0) {
|
|
|
|
/* This is a bit of a hack, but pos could be -ve, and if it is,
|
|
we consider the initial metric changes (at time 0) to actually
|
|
be in effect at pos.
|
|
*/
|
|
|
|
framepos_t f = (*prev_tempo)->frame ();
|
|
|
|
if (pos < 0 && f == 0) {
|
|
f = pos;
|
|
}
|
|
|
|
/* this is slightly more complex than the forward case
|
|
because we reach the tempo in effect at pos after
|
|
passing through pos (rather before, as in the
|
|
forward case). having done that, we then need to
|
|
keep going to get the previous tempo (or
|
|
metrics.rend())
|
|
*/
|
|
|
|
if (f <= pos) {
|
|
if (tempo == 0) {
|
|
/* first tempo with position at or
|
|
before pos
|
|
*/
|
|
tempo = t;
|
|
} else if (f < pos) {
|
|
/* some other tempo section that
|
|
is even earlier than 'tempo'
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(tempo);
|
|
DEBUG_TRACE (DEBUG::TempoMath,
|
|
string_compose ("frame %1 minus %2 beats, start with tempo = %3 @ %4 prev at beg? %5\n",
|
|
pos, beats, *((const Tempo*)tempo), tempo->frame(),
|
|
prev_tempo == metrics.rend()));
|
|
|
|
/* We now have:
|
|
|
|
tempo -> the Tempo for "pos"
|
|
prev_tempo -> the first metric before "pos", possibly metrics.rend()
|
|
*/
|
|
|
|
while (!!beats) {
|
|
|
|
/* Distance to the start of this section in frames */
|
|
framecnt_t distance_frames = (pos - tempo->frame());
|
|
|
|
/* Distance to the start in beats */
|
|
Evoral::Beats distance_beats = Evoral::Beats::ticks_at_rate(
|
|
distance_frames, tempo->frames_per_beat (_frame_rate));
|
|
|
|
/* Amount to subtract this time */
|
|
Evoral::Beats const sub = min (distance_beats, beats);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tdistance to %1 = %2 (%3 beats)\n",
|
|
tempo->frame(), distance_frames, distance_beats));
|
|
/* Update */
|
|
|
|
beats -= sub;
|
|
pos -= sub.to_double() * tempo->frames_per_beat (_frame_rate);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("\tnow at %1, %2 beats left, prev at end ? %3\n", pos, beats,
|
|
(prev_tempo == metrics.rend())));
|
|
|
|
/* step backwards to prior TempoSection */
|
|
|
|
if (prev_tempo != metrics.rend()) {
|
|
|
|
tempo = dynamic_cast<const TempoSection*>(*prev_tempo);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath,
|
|
string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
|
|
*((const Tempo*)tempo), tempo->frame(),
|
|
tempo->frames_per_beat (_frame_rate)));
|
|
|
|
while (prev_tempo != metrics.rend ()) {
|
|
|
|
++prev_tempo;
|
|
|
|
if (prev_tempo != metrics.rend() && dynamic_cast<const TempoSection*>(*prev_tempo) != 0) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
pos -= llrint (beats.to_double() * tempo->frames_per_beat (_frame_rate));
|
|
beats = Evoral::Beats();
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/** Add the BBT interval op to pos and return the result */
|
|
framepos_t
|
|
TempoMap::framepos_plus_bbt (framepos_t pos, BBT_Time op) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
Metrics::const_iterator i;
|
|
const MeterSection* meter;
|
|
const MeterSection* m;
|
|
const TempoSection* tempo;
|
|
const TempoSection* t;
|
|
double frames_per_beat;
|
|
framepos_t effective_pos = max (pos, (framepos_t) 0);
|
|
|
|
meter = &first_meter ();
|
|
tempo = &first_tempo ();
|
|
|
|
assert (meter);
|
|
assert (tempo);
|
|
|
|
/* find the starting metrics for tempo & meter */
|
|
|
|
for (i = metrics.begin(); i != metrics.end(); ++i) {
|
|
|
|
if ((*i)->frame() > effective_pos) {
|
|
break;
|
|
}
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
|
|
tempo = t;
|
|
} else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
|
|
meter = m;
|
|
}
|
|
}
|
|
|
|
/* We now have:
|
|
|
|
meter -> the Meter for "pos"
|
|
tempo -> the Tempo for "pos"
|
|
i -> for first new metric after "pos", possibly metrics.end()
|
|
*/
|
|
|
|
/* now comes the complicated part. we have to add one beat a time,
|
|
checking for a new metric on every beat.
|
|
*/
|
|
|
|
frames_per_beat = tempo->frames_per_beat (_frame_rate);
|
|
|
|
uint64_t bars = 0;
|
|
|
|
while (op.bars) {
|
|
|
|
bars++;
|
|
op.bars--;
|
|
|
|
/* check if we need to use a new metric section: has adding frames moved us
|
|
to or after the start of the next metric section? in which case, use it.
|
|
*/
|
|
|
|
if (i != metrics.end()) {
|
|
if ((*i)->frame() <= pos) {
|
|
|
|
/* about to change tempo or meter, so add the
|
|
* number of frames for the bars we've just
|
|
* traversed before we change the
|
|
* frames_per_beat value.
|
|
*/
|
|
|
|
pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
|
|
bars = 0;
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
|
|
tempo = t;
|
|
} else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
|
|
meter = m;
|
|
}
|
|
++i;
|
|
frames_per_beat = tempo->frames_per_beat (_frame_rate);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
pos += llrint (frames_per_beat * (bars * meter->divisions_per_bar()));
|
|
|
|
uint64_t beats = 0;
|
|
|
|
while (op.beats) {
|
|
|
|
/* given the current meter, have we gone past the end of the bar ? */
|
|
|
|
beats++;
|
|
op.beats--;
|
|
|
|
/* check if we need to use a new metric section: has adding frames moved us
|
|
to or after the start of the next metric section? in which case, use it.
|
|
*/
|
|
|
|
if (i != metrics.end()) {
|
|
if ((*i)->frame() <= pos) {
|
|
|
|
/* about to change tempo or meter, so add the
|
|
* number of frames for the beats we've just
|
|
* traversed before we change the
|
|
* frames_per_beat value.
|
|
*/
|
|
|
|
pos += llrint (beats * frames_per_beat);
|
|
beats = 0;
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*i)) != 0) {
|
|
tempo = t;
|
|
} else if ((m = dynamic_cast<const MeterSection*>(*i)) != 0) {
|
|
meter = m;
|
|
}
|
|
++i;
|
|
frames_per_beat = tempo->frames_per_beat (_frame_rate);
|
|
}
|
|
}
|
|
}
|
|
|
|
pos += llrint (beats * frames_per_beat);
|
|
|
|
if (op.ticks) {
|
|
if (op.ticks >= BBT_Time::ticks_per_beat) {
|
|
pos += llrint (frames_per_beat + /* extra beat */
|
|
(frames_per_beat * ((op.ticks % (uint32_t) BBT_Time::ticks_per_beat) /
|
|
(double) BBT_Time::ticks_per_beat)));
|
|
} else {
|
|
pos += llrint (frames_per_beat * (op.ticks / (double) BBT_Time::ticks_per_beat));
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/** Count the number of beats that are equivalent to distance when going forward,
|
|
starting at pos.
|
|
*/
|
|
Evoral::Beats
|
|
TempoMap::framewalk_to_beats (framepos_t pos, framecnt_t distance) const
|
|
{
|
|
Glib::Threads::RWLock::ReaderLock lm (lock);
|
|
Metrics::const_iterator next_tempo;
|
|
const TempoSection* tempo = 0;
|
|
framepos_t effective_pos = max (pos, (framepos_t) 0);
|
|
|
|
/* Find the relevant initial tempo metric */
|
|
|
|
for (next_tempo = metrics.begin(); next_tempo != metrics.end(); ++next_tempo) {
|
|
|
|
const TempoSection* t;
|
|
|
|
if ((t = dynamic_cast<const TempoSection*>(*next_tempo)) != 0) {
|
|
|
|
if ((*next_tempo)->frame() > effective_pos) {
|
|
break;
|
|
}
|
|
|
|
tempo = t;
|
|
}
|
|
}
|
|
|
|
/* We now have:
|
|
|
|
tempo -> the Tempo for "pos"
|
|
next_tempo -> the next tempo after "pos", possibly metrics.end()
|
|
*/
|
|
assert (tempo);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath,
|
|
string_compose ("frame %1 walk by %2 frames, start with tempo = %3 @ %4\n",
|
|
pos, distance, *((const Tempo*)tempo), tempo->frame()));
|
|
|
|
Evoral::Beats beats = Evoral::Beats();
|
|
|
|
while (distance) {
|
|
|
|
/* End of this section */
|
|
framepos_t end;
|
|
/* Distance to `end' in frames */
|
|
framepos_t distance_to_end;
|
|
|
|
if (next_tempo == metrics.end ()) {
|
|
/* We can't do (end - pos) if end is max_framepos, as it will overflow if pos is -ve */
|
|
end = max_framepos;
|
|
distance_to_end = max_framepos;
|
|
} else {
|
|
end = (*next_tempo)->frame ();
|
|
distance_to_end = end - pos;
|
|
}
|
|
|
|
/* Amount to subtract this time in frames */
|
|
framecnt_t const sub = min (distance, distance_to_end);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("to reach end at %1 (end ? %2), distance= %3 sub=%4\n", end, (next_tempo == metrics.end()),
|
|
distance_to_end, sub));
|
|
|
|
/* Update */
|
|
pos += sub;
|
|
distance -= sub;
|
|
assert (tempo);
|
|
beats += Evoral::Beats::ticks_at_rate(sub, tempo->frames_per_beat (_frame_rate));
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("now at %1, beats = %2 distance left %3\n",
|
|
pos, beats, distance));
|
|
|
|
/* Move on if there's anything to move to */
|
|
|
|
if (next_tempo != metrics.end()) {
|
|
|
|
tempo = dynamic_cast<const TempoSection*>(*next_tempo);
|
|
|
|
DEBUG_TRACE (DEBUG::TempoMath,
|
|
string_compose ("\tnew tempo = %1 @ %2 fpb = %3\n",
|
|
*((const Tempo*)tempo), tempo->frame(),
|
|
tempo->frames_per_beat (_frame_rate)));
|
|
|
|
while (next_tempo != metrics.end ()) {
|
|
|
|
++next_tempo;
|
|
|
|
if (next_tempo != metrics.end() && dynamic_cast<const TempoSection*>(*next_tempo)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (next_tempo == metrics.end()) {
|
|
DEBUG_TRACE (DEBUG::TempoMath, "no more tempo sections\n");
|
|
} else {
|
|
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("next tempo section is %1 @ %2\n",
|
|
**next_tempo, (*next_tempo)->frame()));
|
|
}
|
|
|
|
}
|
|
assert (tempo);
|
|
}
|
|
|
|
return beats;
|
|
}
|
|
|
|
TempoMap::BBTPointList::const_iterator
|
|
TempoMap::bbt_before_or_at (framepos_t pos)
|
|
{
|
|
/* CALLER MUST HOLD READ LOCK */
|
|
|
|
BBTPointList::const_iterator i;
|
|
|
|
if (pos < 0) {
|
|
/* not really correct, but we should catch pos < 0 at a higher
|
|
level
|
|
*/
|
|
return _map.begin();
|
|
}
|
|
|
|
i = lower_bound (_map.begin(), _map.end(), pos);
|
|
assert (i != _map.end());
|
|
if ((*i).frame > pos) {
|
|
assert (i != _map.begin());
|
|
--i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
struct bbtcmp {
|
|
bool operator() (const BBT_Time& a, const BBT_Time& b) {
|
|
return a < b;
|
|
}
|
|
};
|
|
|
|
TempoMap::BBTPointList::const_iterator
|
|
TempoMap::bbt_before_or_at (const BBT_Time& bbt)
|
|
{
|
|
BBTPointList::const_iterator i;
|
|
bbtcmp cmp;
|
|
|
|
i = lower_bound (_map.begin(), _map.end(), bbt, cmp);
|
|
assert (i != _map.end());
|
|
if ((*i).bar > bbt.bars || (*i).beat > bbt.beats) {
|
|
assert (i != _map.begin());
|
|
--i;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
TempoMap::BBTPointList::const_iterator
|
|
TempoMap::bbt_after_or_at (framepos_t pos)
|
|
{
|
|
/* CALLER MUST HOLD READ LOCK */
|
|
|
|
BBTPointList::const_iterator i;
|
|
|
|
if (_map.back().frame == pos) {
|
|
i = _map.end();
|
|
assert (i != _map.begin());
|
|
--i;
|
|
return i;
|
|
}
|
|
|
|
i = upper_bound (_map.begin(), _map.end(), pos);
|
|
assert (i != _map.end());
|
|
return i;
|
|
}
|
|
|
|
std::ostream&
|
|
operator<< (std::ostream& o, const Meter& m) {
|
|
return o << m.divisions_per_bar() << '/' << m.note_divisor();
|
|
}
|
|
|
|
std::ostream&
|
|
operator<< (std::ostream& o, const Tempo& t) {
|
|
return o << t.beats_per_minute() << " 1/" << t.note_type() << "'s per minute";
|
|
}
|
|
|
|
std::ostream&
|
|
operator<< (std::ostream& o, const MetricSection& section) {
|
|
|
|
o << "MetricSection @ " << section.frame() << " aka " << section.start() << ' ';
|
|
|
|
const TempoSection* ts;
|
|
const MeterSection* ms;
|
|
|
|
if ((ts = dynamic_cast<const TempoSection*> (§ion)) != 0) {
|
|
o << *((const Tempo*) ts);
|
|
} else if ((ms = dynamic_cast<const MeterSection*> (§ion)) != 0) {
|
|
o << *((const Meter*) ms);
|
|
}
|
|
|
|
return o;
|
|
}
|