new approach to tempo/meter: compute and store the entire map (every bar|beat point), thus enabling us to use the same computation to set the BBT points AND the metric markers (tempo + meter) on the audio timeline. It is known that snapping to the BBT grid doesn't work correctly right now, but this probably caused by the separate code in TempoMap::round_to_type() and i'll dig into that tomorrow. Note that the Bar|beat point list is evaluated "lazily" - we'll never store more than anyone actually needs to display or know, other than 1 minute's worth starting from frame zero

git-svn-id: svn://localhost/ardour2/branches/3.0@11129 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2012-01-02 04:04:14 +00:00
parent 084fc8b327
commit 69c7dac1a1
8 changed files with 320 additions and 590 deletions

View File

@ -305,7 +305,6 @@ Editor::Editor ()
pre_press_cursor = 0;
_drags = new DragManager (this);
current_mixer_strip = 0;
current_bbt_points = 0;
tempo_lines = 0;
snap_type_strings = I18N (_snap_type_strings);
@ -5335,8 +5334,7 @@ Editor::session_going_away ()
hide_measures ();
clear_marker_display ();
delete current_bbt_points;
current_bbt_points = 0;
current_bbt_points.clear ();
/* get rid of any existing editor mixer strip */

View File

@ -1451,7 +1451,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
/// true if we scroll the tracks rather than the playhead
bool _stationary_playhead;
ARDOUR::TempoMap::BBTPointList *current_bbt_points;
ARDOUR::TempoMap::BBTPointList current_bbt_points;
TempoLines* tempo_lines;

View File

@ -1224,18 +1224,18 @@ Editor::compute_bbt_ruler_scale (framepos_t lower, framepos_t upper)
break;
}
if (current_bbt_points == 0 || current_bbt_points->empty()) {
if (current_bbt_points.empty()) {
return;
}
i = current_bbt_points->end();
i = current_bbt_points.end();
i--;
if ((*i).beat >= (*current_bbt_points->begin()).beat) {
bbt_bars = (*i).bar - (*current_bbt_points->begin()).bar;
if ((*i).beat >= (*current_bbt_points.begin()).beat) {
bbt_bars = (*i).bar - (*current_bbt_points.begin()).bar;
} else {
bbt_bars = (*i).bar - (*current_bbt_points->begin()).bar - 1;
bbt_bars = (*i).bar - (*current_bbt_points.begin()).bar - 1;
}
beats = current_bbt_points->size() - bbt_bars;
beats = current_bbt_points.size() - bbt_bars;
/* Only show the bar helper if there aren't many bars on the screen */
if ((bbt_bars < 2) || (beats < 5)) {
@ -1291,14 +1291,14 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
bool i_am_accented = false;
bool helper_active = false;
if (current_bbt_points == 0 || current_bbt_points->empty()) {
if (current_bbt_points.empty()) {
return 0;
}
switch (bbt_ruler_scale) {
case bbt_show_beats:
beats = current_bbt_points->size();
beats = current_bbt_points.size();
bbt_nmarks = beats + 2;
*marks = (GtkCustomRulerMark *) g_malloc (sizeof(GtkCustomRulerMark) * bbt_nmarks);
@ -1307,10 +1307,8 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
(*marks)[0].position = lower;
(*marks)[0].style = GtkCustomRulerMarkMicro;
for (n = 1, i = current_bbt_points->begin(); n < bbt_nmarks && i != current_bbt_points->end(); ++i) {
if ((*i).type != TempoMap::Beat) {
continue;
}
for (n = 1, i = current_bbt_points.begin(); n < bbt_nmarks && i != current_bbt_points.end(); ++i) {
if ((*i).frame < lower && (bbt_bar_helper_on)) {
snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
(*marks)[0].label = g_strdup (buf);
@ -1336,7 +1334,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
case bbt_show_ticks:
beats = current_bbt_points->size();
beats = current_bbt_points.size();
bbt_nmarks = (beats + 2) * bbt_beat_subdivision;
bbt_position_of_helper = lower + (30 * Editor::get_current_zoom ());
@ -1346,10 +1344,8 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
(*marks)[0].position = lower;
(*marks)[0].style = GtkCustomRulerMarkMicro;
for (n = 1, i = current_bbt_points->begin(); n < bbt_nmarks && i != current_bbt_points->end(); ++i) {
if ((*i).type != TempoMap::Beat) {
continue;
}
for (n = 1, i = current_bbt_points.begin(); n < bbt_nmarks && i != current_bbt_points.end(); ++i) {
if ((*i).frame < lower && (bbt_bar_helper_on)) {
snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
(*marks)[0].label = g_strdup (buf);
@ -1428,7 +1424,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
case bbt_show_ticks_detail:
beats = current_bbt_points->size();
beats = current_bbt_points.size();
bbt_nmarks = (beats + 2) * bbt_beat_subdivision;
bbt_position_of_helper = lower + (30 * Editor::get_current_zoom ());
@ -1438,10 +1434,8 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
(*marks)[0].position = lower;
(*marks)[0].style = GtkCustomRulerMarkMicro;
for (n = 1, i = current_bbt_points->begin(); n < bbt_nmarks && i != current_bbt_points->end(); ++i) {
if ((*i).type != TempoMap::Beat) {
continue;
}
for (n = 1, i = current_bbt_points.begin(); n < bbt_nmarks && i != current_bbt_points.end(); ++i) {
if ((*i).frame < lower && (bbt_bar_helper_on)) {
snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
(*marks)[0].label = g_strdup (buf);
@ -1525,7 +1519,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
case bbt_show_ticks_super_detail:
beats = current_bbt_points->size();
beats = current_bbt_points.size();
bbt_nmarks = (beats + 2) * bbt_beat_subdivision;
bbt_position_of_helper = lower + (30 * Editor::get_current_zoom ());
@ -1535,10 +1529,8 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
(*marks)[0].position = lower;
(*marks)[0].style = GtkCustomRulerMarkMicro;
for (n = 1, i = current_bbt_points->begin(); n < bbt_nmarks && i != current_bbt_points->end(); ++i) {
if ((*i).type != TempoMap::Beat) {
continue;
}
for (n = 1, i = current_bbt_points.begin(); n < bbt_nmarks && i != current_bbt_points.end(); ++i) {
if ((*i).frame < lower && (bbt_bar_helper_on)) {
snprintf (buf, sizeof(buf), "<%" PRIu32 "|%" PRIu32, (*i).bar, (*i).beat);
(*marks)[0].label = g_strdup (buf);
@ -1634,7 +1626,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
case bbt_show_64:
bbt_nmarks = (gint) (bbt_bars / 64) + 1;
*marks = (GtkCustomRulerMark *) g_malloc (sizeof(GtkCustomRulerMark) * bbt_nmarks);
for (n = 0, i = current_bbt_points->begin(); i != current_bbt_points->end() && n < bbt_nmarks; i++) {
for (n = 0, i = current_bbt_points.begin(); i != current_bbt_points.end() && n < bbt_nmarks; i++) {
if ((*i).type == TempoMap::Bar) {
if ((*i).bar % 64 == 1) {
if ((*i).bar % 256 == 1) {
@ -1659,7 +1651,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
case bbt_show_16:
bbt_nmarks = (bbt_bars / 16) + 1;
*marks = (GtkCustomRulerMark *) g_malloc (sizeof(GtkCustomRulerMark) * bbt_nmarks);
for (n = 0, i = current_bbt_points->begin(); i != current_bbt_points->end() && n < bbt_nmarks; i++) {
for (n = 0, i = current_bbt_points.begin(); i != current_bbt_points.end() && n < bbt_nmarks; i++) {
if ((*i).type == TempoMap::Bar) {
if ((*i).bar % 16 == 1) {
if ((*i).bar % 64 == 1) {
@ -1684,7 +1676,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
case bbt_show_4:
bbt_nmarks = (bbt_bars / 4) + 1;
*marks = (GtkCustomRulerMark *) g_malloc (sizeof(GtkCustomRulerMark) * bbt_nmarks);
for (n = 0, i = current_bbt_points->begin(); i != current_bbt_points->end() && n < bbt_nmarks; ++i) {
for (n = 0, i = current_bbt_points.begin(); i != current_bbt_points.end() && n < bbt_nmarks; ++i) {
if ((*i).type == TempoMap::Bar) {
if ((*i).bar % 4 == 1) {
if ((*i).bar % 16 == 1) {
@ -1710,7 +1702,7 @@ Editor::metric_get_bbt (GtkCustomRulerMark **marks, gdouble lower, gdouble /*upp
// default:
bbt_nmarks = bbt_bars + 2;
*marks = (GtkCustomRulerMark *) g_malloc (sizeof(GtkCustomRulerMark) * bbt_nmarks );
for (n = 0, i = current_bbt_points->begin(); i != current_bbt_points->end() && n < bbt_nmarks; i++) {
for (n = 0, i = current_bbt_points.begin(); i != current_bbt_points.end() && n < bbt_nmarks; i++) {
if ((*i).type == TempoMap::Bar) {
if ((*i).bar % 4 == 1) {
snprintf (buf, sizeof(buf), "%" PRIu32, (*i).bar);

View File

@ -114,7 +114,7 @@ Editor::tempo_map_changed (const PropertyChange& /*ignored*/)
tempo_lines->tempo_map_changed();
}
compute_current_bbt_points(leftmost_frame, leftmost_frame + current_page_frames());
compute_current_bbt_points (leftmost_frame, leftmost_frame + current_page_frames());
_session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
redraw_measures ();
update_tempo_based_rulers ();
@ -169,10 +169,8 @@ Editor::compute_current_bbt_points (framepos_t leftmost, framepos_t rightmost)
}
next_beat.ticks = 0;
delete current_bbt_points;
current_bbt_points = 0;
current_bbt_points = _session->tempo_map().get_points (_session->tempo_map().frame_time (previous_beat), _session->tempo_map().frame_time (next_beat) + 1);
current_bbt_points.clear();
_session->tempo_map().map (current_bbt_points, _session->tempo_map().frame_time (previous_beat), _session->tempo_map().frame_time (next_beat) + 1);
}
void
@ -192,8 +190,7 @@ Editor::redraw_measures ()
void
Editor::draw_measures ()
{
if (_session == 0 || _show_measures == false ||
!current_bbt_points || current_bbt_points->empty()) {
if (_session == 0 || _show_measures == false || current_bbt_points.empty()) {
return;
}
@ -201,7 +198,7 @@ Editor::draw_measures ()
tempo_lines = new TempoLines(*track_canvas, time_line_group, physical_screen_height(get_window()));
}
tempo_lines->draw(*current_bbt_points, frames_per_unit);
tempo_lines->draw (current_bbt_points, frames_per_unit);
}
void

View File

@ -131,122 +131,114 @@ TempoLines::draw (ARDOUR::TempoMap::BBTPointList& points, double frames_per_unit
for (i = points.begin(); i != points.end(); ++i) {
switch ((*i).type) {
case ARDOUR::TempoMap::Bar:
break;
case ARDOUR::TempoMap::Beat:
if ((*i).beat == 1) {
color = ARDOUR_UI::config()->canvasvar_MeasureLineBar.get();
} else {
color = ARDOUR_UI::config()->canvasvar_MeasureLineBeat.get();
if (beat_density > 2.0) {
break; /* only draw beat lines if the gaps between beats are large. */
}
if ((*i).type == ARDOUR::TempoMap::Bar) {
color = ARDOUR_UI::config()->canvasvar_MeasureLineBar.get();
} else {
if (beat_density > 2.0) {
continue; /* only draw beat lines if the gaps between beats are large. */
}
color = ARDOUR_UI::config()->canvasvar_MeasureLineBeat.get();
}
xpos = rint(((framepos_t)(*i).frame) / (double)frames_per_unit);
if (inserted_last_time && !_lines.empty()) {
li = _lines.lower_bound(xpos); // first line >= xpos
}
line = (li != _lines.end()) ? li->second : NULL;
assert(!line || line->property_x1() == li->first);
Lines::iterator next = li;
if (next != _lines.end())
++next;
exhausted = (next == _lines.end());
// Hooray, line is perfect
if (line && line->property_x1() == xpos) {
if (li != _lines.end())
++li;
line->property_color_rgba() = color;
inserted_last_time = false; // don't search next time
xpos = rint(((framepos_t)(*i).frame) / (double)frames_per_unit);
if (inserted_last_time && !_lines.empty()) {
li = _lines.lower_bound(xpos); // first line >= xpos
}
line = (li != _lines.end()) ? li->second : NULL;
assert(!line || line->property_x1() == li->first);
Lines::iterator next = li;
if (next != _lines.end())
++next;
exhausted = (next == _lines.end());
// Hooray, line is perfect
if (line && line->property_x1() == xpos) {
if (li != _lines.end())
++li;
line->property_color_rgba() = color;
inserted_last_time = false; // don't search next time
// Use existing line, moving if necessary
} else if (!exhausted) {
Lines::iterator steal = _lines.end();
--steal;
// Steal from the right
if (left->first > needed_left && li != steal && steal->first > needed_right) {
//cout << "*** STEALING FROM RIGHT" << endl;
line = steal->second;
_lines.erase(steal);
line->property_x1() = xpos;
line->property_x2() = xpos;
line->property_color_rgba() = color;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true; // search next time
invalidated = true;
// Shift clean range left
_clean_left = min(_clean_left, xpos);
_clean_right = min(_clean_right, steal->first);
// Move this line to where we need it
} else {
Lines::iterator existing = _lines.find(xpos);
if (existing != _lines.end()) {
//cout << "*** EXISTING LINE" << endl;
li = existing;
li->second->property_color_rgba() = color;
inserted_last_time = false; // don't search next time
} else {
//cout << "*** MOVING LINE" << endl;
const double x1 = line->property_x1();
const bool was_clean = x1 >= _clean_left && x1 <= _clean_right;
invalidated = invalidated || was_clean;
// Invalidate clean portion (XXX: too harsh?)
_clean_left = needed_left;
_clean_right = needed_right;
_lines.erase(li);
line->property_color_rgba() = color;
line->property_x1() = xpos;
line->property_x2() = xpos;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true; // search next time
}
}
// Create a new line
} else if (_lines.size() < needed || _lines.size() < MAX_CACHED_LINES) {
//cout << "*** CREATING LINE" << endl;
assert(_lines.find(xpos) == _lines.end());
line = new ArdourCanvas::SimpleLine (*_group);
line->property_x1() = xpos;
line->property_x2() = xpos;
line->property_y1() = 0.0;
line->property_y2() = _height;
line->property_color_rgba() = color;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true;
// Steal from the left
} else {
//cout << "*** STEALING FROM LEFT" << endl;
assert(_lines.find(xpos) == _lines.end());
Lines::iterator steal = _lines.begin();
} else if (!exhausted) {
Lines::iterator steal = _lines.end();
--steal;
// Steal from the right
if (left->first > needed_left && li != steal && steal->first > needed_right) {
//cout << "*** STEALING FROM RIGHT" << endl;
line = steal->second;
_lines.erase(steal);
line->property_color_rgba() = color;
line->property_x1() = xpos;
line->property_x2() = xpos;
line->property_color_rgba() = color;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true; // search next time
invalidated = true;
// Shift clean range right
_clean_left = max(_clean_left, steal->first);
_clean_right = max(_clean_right, xpos);
// Shift clean range left
_clean_left = min(_clean_left, xpos);
_clean_right = min(_clean_right, steal->first);
// Move this line to where we need it
} else {
Lines::iterator existing = _lines.find(xpos);
if (existing != _lines.end()) {
//cout << "*** EXISTING LINE" << endl;
li = existing;
li->second->property_color_rgba() = color;
inserted_last_time = false; // don't search next time
} else {
//cout << "*** MOVING LINE" << endl;
const double x1 = line->property_x1();
const bool was_clean = x1 >= _clean_left && x1 <= _clean_right;
invalidated = invalidated || was_clean;
// Invalidate clean portion (XXX: too harsh?)
_clean_left = needed_left;
_clean_right = needed_right;
_lines.erase(li);
line->property_color_rgba() = color;
line->property_x1() = xpos;
line->property_x2() = xpos;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true; // search next time
}
}
break;
// Create a new line
} else if (_lines.size() < needed || _lines.size() < MAX_CACHED_LINES) {
//cout << "*** CREATING LINE" << endl;
assert(_lines.find(xpos) == _lines.end());
line = new ArdourCanvas::SimpleLine (*_group);
line->property_x1() = xpos;
line->property_x2() = xpos;
line->property_y1() = 0.0;
line->property_y2() = _height;
line->property_color_rgba() = color;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true;
// Steal from the left
} else {
//cout << "*** STEALING FROM LEFT" << endl;
assert(_lines.find(xpos) == _lines.end());
Lines::iterator steal = _lines.begin();
line = steal->second;
_lines.erase(steal);
line->property_color_rgba() = color;
line->property_x1() = xpos;
line->property_x2() = xpos;
_lines.insert(make_pair(xpos, line));
inserted_last_time = true; // search next time
invalidated = true;
// Shift clean range right
_clean_left = max(_clean_left, steal->first);
_clean_right = max(_clean_right, xpos);
}
}

View File

@ -218,8 +218,9 @@ class TempoMap : public PBD::StatefulDestructible
(obj.*method)(*metrics);
}
BBTPointList *get_points (framepos_t start, framepos_t end) const;
const BBTPointList& map() const { return _map ; }
void map (BBTPointList&, framepos_t start, framepos_t end);
void bbt_time (framepos_t when, Timecode::BBT_Time&) const;
framecnt_t frame_time (const Timecode::BBT_Time&) const;
framecnt_t bbt_duration_at (framepos_t, const Timecode::BBT_Time&, int dir) const;
@ -282,13 +283,15 @@ class TempoMap : public PBD::StatefulDestructible
static Meter _default_meter;
Metrics* metrics;
framecnt_t _frame_rate;
framecnt_t _frame_rate;
framepos_t last_bbt_when;
bool last_bbt_valid;
Timecode::BBT_Time last_bbt;
mutable Glib::RWLock lock;
BBTPointList _map;
void recompute_map (bool reassign_tempo_bbt, framepos_t end = -1);
void timestamp_metrics (bool reassign_bar_references);
void timestamp_metrics_from_audio_time ();
framepos_t round_to_type (framepos_t fr, int dir, BBTPointType);
@ -304,7 +307,6 @@ class TempoMap : public PBD::StatefulDestructible
framecnt_t count_frames_between (const Timecode::BBT_Time&, const Timecode::BBT_Time&) const;
framecnt_t count_frames_with_metrics (const TempoMetric&, const TempoMetric&, const Timecode::BBT_Time&, const Timecode::BBT_Time&) const;
framecnt_t count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const Timecode::BBT_Time& start, const Timecode::BBT_Time& end) const;
int move_metric_section (MetricSection&, const Timecode::BBT_Time& to);
void do_insert (MetricSection* section);

View File

@ -41,7 +41,7 @@ Pool Click::pool ("click", sizeof (Click), 128);
void
Session::click (framepos_t start, framecnt_t nframes)
{
TempoMap::BBTPointList *points;
TempoMap::BBTPointList points;
Sample *buf;
if (_click_io == 0) {
@ -59,18 +59,13 @@ Session::click (framepos_t start, framecnt_t nframes)
BufferSet& bufs = get_scratch_buffers(ChanCount(DataType::AUDIO, 1));
buf = bufs.get_audio(0).data();
points = _tempo_map->get_points (start, end);
_tempo_map->map (points, start, end);
if (points == 0) {
if (points.empty()) {
goto run_clicks;
}
if (points->empty()) {
delete points;
goto run_clicks;
}
for (TempoMap::BBTPointList::iterator i = points->begin(); i != points->end(); ++i) {
for (TempoMap::BBTPointList::iterator i = points.begin(); i != points.end(); ++i) {
switch ((*i).type) {
case TempoMap::Beat:
if (click_emphasis_data == 0 || (click_emphasis_data && (*i).beat != 1)) {

View File

@ -156,8 +156,8 @@ void
TempoSection::update_bar_offset_from_bbt (const Meter& m)
{
_bar_offset = ((double) (start().beats - 1) + (start().ticks/Timecode::BBT_Time::ticks_per_bar_division)) /
(m.divisions_per_bar() - 1);
_bar_offset = ((start().beats - 1) * BBT_Time::ticks_per_bar_division + start().ticks) /
(m.divisions_per_bar() * BBT_Time::ticks_per_bar_division);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Tempo set bar offset to %1 from %2 w/%3\n", _bar_offset, start(), m.divisions_per_bar()));
}
@ -174,18 +174,16 @@ TempoSection::update_bbt_time_from_bar_offset (const Meter& meter)
new_start.bars = start().bars;
double ticks = BBT_Time::ticks_per_bar_division * (_bar_offset * (meter.divisions_per_bar() - 1));
double ticks = BBT_Time::ticks_per_bar_division * meter.divisions_per_bar() * _bar_offset;
new_start.beats = (uint32_t) floor(ticks/BBT_Time::ticks_per_bar_division);
new_start.ticks = (uint32_t) fmod (ticks, BBT_Time::ticks_per_bar_division);
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));
/* remember the 1-based counting properties of beats */
new_start.beats += 1;
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("tempo updated BBT time to %1 from bar offset %2 w/dpb = %3\n", new_start, _bar_offset, meter.divisions_per_bar()));
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);
}
@ -305,7 +303,7 @@ TempoMap::~TempoMap ()
}
void
TempoMap::remove_tempo (const TempoSection& tempo, bool send_signal)
TempoMap::remove_tempo (const TempoSection& tempo, bool complete_operation)
{
bool removed = false;
@ -327,15 +325,15 @@ TempoMap::remove_tempo (const TempoSection& tempo, bool send_signal)
}
if (removed) {
timestamp_metrics (false);
if (send_signal) {
if (complete_operation) {
recompute_map (false);
PropertyChanged (PropertyChange ());
}
}
}
void
TempoMap::remove_meter (const MeterSection& tempo, bool send_signal)
TempoMap::remove_meter (const MeterSection& tempo, bool complete_operation)
{
bool removed = false;
@ -357,8 +355,8 @@ TempoMap::remove_meter (const MeterSection& tempo, bool send_signal)
}
if (removed) {
timestamp_metrics (true);
if (send_signal) {
if (complete_operation) {
recompute_map (true);
PropertyChanged (PropertyChange ());
}
}
@ -448,7 +446,7 @@ TempoMap::do_insert (MetricSection* section)
metrics->insert (metrics->end(), section);
}
timestamp_metrics (reassign_tempo_bbt);
recompute_map (reassign_tempo_bbt);
}
void
@ -462,7 +460,7 @@ TempoMap::replace_tempo (const TempoSection& ts, const Tempo& tempo, const BBT_T
} else {
/* cannot move the first tempo section */
*((Tempo*)&first) = tempo;
timestamp_metrics (false);
recompute_map (false);
}
PropertyChanged (PropertyChange ());
@ -526,7 +524,7 @@ TempoMap::replace_meter (const MeterSection& ms, const Meter& meter, const BBT_T
} else {
/* cannot move the first meter section */
*((Meter*)&first) = meter;
timestamp_metrics (true);
recompute_map (true);
}
PropertyChanged (PropertyChange ());
@ -737,41 +735,80 @@ TempoMap::timestamp_metrics_from_audio_time ()
}
void
TempoMap::timestamp_metrics (bool reassign_tempo_bbt)
TempoMap::recompute_map (bool reassign_tempo_bbt, framepos_t end)
{
Metrics::iterator i;
const MeterSection* meter;
const TempoSection* tempo;
MeterSection *m;
TempoSection *t;
MeterSection* meter;
TempoSection* tempo;
TempoSection* ts;
MeterSection* ms;
double divisions_per_bar;
double beat_frames;
double frames_per_bar;
double current_frame;
BBT_Time current;
Metrics::iterator next_metric;
DEBUG_TRACE (DEBUG::TempoMath, "###################### TIMESTAMP via BBT ##############\n");
if (end < 0) {
if (_map.empty()) {
/* compute 1 mins worth */
end = _frame_rate * 60;
} else {
end = _map.back().frame;
}
}
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("recomputing tempo map, zero to %1\n", end));
framepos_t current = 0;
framepos_t section_frames;
BBT_Time start;
BBT_Time end;
_map.clear ();
for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
if ((ms = dynamic_cast<MeterSection *> (*i)) != 0) {
meter = ms;
break;
}
}
for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
if ((ts = dynamic_cast<TempoSection *> (*i)) != 0) {
tempo = ts;
break;
}
}
/* 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;
divisions_per_bar = meter->divisions_per_bar ();
frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
beat_frames = meter->frames_per_division (*tempo,_frame_rate);
if (reassign_tempo_bbt) {
TempoSection* rtempo = tempo;
MeterSection* rmeter = meter;
DEBUG_TRACE (DEBUG::TempoMath, "\tUpdating tempo marks BBT time from bar offset\n");
meter = &first_meter ();
tempo = &first_tempo ();
for (i = metrics->begin(); i != metrics->end(); ++i) {
for (Metrics::iterator i = metrics->begin(); i != metrics->end(); ++i) {
if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
if ((ts = dynamic_cast<TempoSection*>(*i)) != 0) {
/* reassign the BBT time of this tempo section
* based on its bar offset position.
*/
t->update_bbt_time_from_bar_offset (*meter);
tempo = t;
ts->update_bbt_time_from_bar_offset (*rmeter);
rtempo = ts;
} else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
meter = m;
} else if ((ms = dynamic_cast<MeterSection*>(*i)) != 0) {
rmeter = ms;
} else {
fatal << _("programming error: unhandled MetricSection type") << endmsg;
/*NOTREACHED*/
@ -779,44 +816,110 @@ TempoMap::timestamp_metrics (bool reassign_tempo_bbt)
}
}
meter = &first_meter ();
tempo = &first_tempo ();
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("start with meter = %1 tempo = %2 dpb %3 fpb %4\n",
*((Meter*)meter), *((Tempo*)tempo), divisions_per_bar, beat_frames));
for (i = metrics->begin(); i != metrics->end(); ++i) {
next_metric = metrics->begin();
++next_metric; // skip meter (or tempo)
++next_metric; // skip tempo (or meter)
end = (*i)->start();
section_frames = count_frames_between_metrics (*meter, *tempo, start, end);
current += section_frames;
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), Bar, 1, 1));
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("frames between %1 & %2 = %3 using %4 & %6 puts %7 at %8\n",
start, end, section_frames,
*((Meter*) meter), *((Tempo*) tempo),
(*i)->start(), current));
while (current_frame < end) {
current.beats++;
current_frame += beat_frames;
start = end;
(*i)->set_frame (current);
if ((t = dynamic_cast<TempoSection*>(*i)) != 0) {
tempo = t;
} else if ((m = dynamic_cast<MeterSection*>(*i)) != 0) {
meter = m;
} else {
fatal << _("programming error: unhandled MetricSection type") << endmsg;
/*NOTREACHED*/
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())) {
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 = meter->frames_per_division (*tempo,_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 -= beat_frames;
/* set tempo section location based on offset from last beat */
tempo->set_frame (current_frame + (ts->bar_offset() * beat_frames));
/* advance to the location of the new (adjusted) beat */
current_frame += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames);
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 (%2)\n",
meter->start(), current_frame));
assert (current.beats == 1);
meter->set_frame (current_frame);
}
divisions_per_bar = meter->divisions_per_bar ();
frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
beat_frames = meter->frames_per_division (*tempo, _frame_rate);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 dpb %2 meter %3 tempo %4\n",
beat_frames, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
++next_metric;
}
}
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,(framepos_t) llrint(current_frame), Bar, current.bars, 1));
} 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, (framepos_t) llrint(current_frame), Beat, current.bars, current.beats));
}
}
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::TempoMath)) {
dump (cerr);
}
#endif
DEBUG_TRACE (DEBUG::TempoMath, "###############################################\n");
}
TempoMetric
@ -1007,81 +1110,6 @@ TempoMap::count_frames_with_metrics (const TempoMetric& bm, const TempoMetric& e
return frames;
}
framecnt_t
TempoMap::count_frames_between_metrics (const Meter& meter, const Tempo& tempo, const BBT_Time& start, const BBT_Time& end) const
{
/* this is used in timestamping the metrics by actually counting the
* beats between two metrics ONLY. this means that we know we have a
* fixed divisions_per_bar and frames_per_division for the entire
* computation.
*/
framecnt_t frames = 0;
uint32_t bar = start.bars;
double beat = (double) start.beats;
double divisions_counted = 0;
double divisions_per_bar = 0;
double division_frames = 0;
double max_divs;
int32_t ticks = 0;
divisions_per_bar = meter.divisions_per_bar();
max_divs = ceil (divisions_per_bar);
division_frames = meter.frames_per_division (tempo, _frame_rate);
if (start.ticks > 0) {
ticks = -start.ticks;
}
frames = 0;
while (bar < end.bars || (bar == end.bars && beat < end.beats)) {
++beat;
++divisions_counted;
if (beat > max_divs) {
if (beat > divisions_per_bar) {
/* this is a fractional beat at the end of a fractional bar
so it should only count for the fraction
*/
divisions_counted -= (max_divs - divisions_per_bar);
}
++bar;
beat = 1;
}
}
ticks += end.ticks;
#if 0
cerr << "for " << start.ticks << " and " << end.ticks << " adjust divs by " << ticks << " = "
<< (ticks/BBT_Time::ticks_per_bar_division) << " divs => "
<< ((ticks/BBT_Time::ticks_per_bar_division) * division_frames)
<< " (fpd = " << division_frames << ')'
<< endl;
#endif
frames = (framecnt_t) llrint (floor ((divisions_counted + (ticks/BBT_Time::ticks_per_bar_division)) * division_frames));
#if 0
cerr << "Counted " << divisions_counted << " + " << ticks << " from " << start << " to " << end
<< " dpb were " << divisions_per_bar
<< " fpd was " << division_frames
<< " => "
<< frames
<< endl;
#endif
return frames;
}
framepos_t
TempoMap::frame_time (const BBT_Time& bbt) const
{
@ -1440,296 +1468,22 @@ TempoMap::round_to_type (framepos_t frame, int dir, BBTPointType type)
return metric.frame() + count_frames_between (metric.start(), bbt);
}
TempoMap::BBTPointList *
TempoMap::get_points (framepos_t lower, framepos_t upper) const
void
TempoMap::map (TempoMap::BBTPointList& points, framepos_t lower, framepos_t upper)
{
Metrics::const_iterator next_metric;
BBTPointList *points;
double current;
const MeterSection* meter;
const MeterSection* m;
const TempoSection* tempo;
const TempoSection* t;
uint32_t bar;
uint32_t beat;
double divisions_per_bar;
double beat_frame;
double beat_frames;
double frames_per_bar;
double delta_bars;
double delta_beats;
double dummy;
framepos_t limit;
meter = &first_meter ();
tempo = &first_tempo ();
/* find the starting point */
for (next_metric = metrics->begin(); next_metric != metrics->end(); ++next_metric) {
if ((*next_metric)->frame() > lower) {
break;
}
if ((t = dynamic_cast<const TempoSection*>(*next_metric)) != 0) {
tempo = t;
} else if ((m = dynamic_cast<const MeterSection*>(*next_metric)) != 0) {
meter = m;
}
if (_map.empty() || upper >= _map.back().frame) {
recompute_map (false, upper);
}
/* We now have:
meter -> the Meter for "lower"
tempo -> the Tempo for "lower"
i -> for first new metric after "lower", possibly metrics->end()
Now start generating points.
*/
divisions_per_bar = meter->divisions_per_bar ();
frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
beat_frames = meter->frames_per_division (*tempo,_frame_rate);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Start with beat frames = %1 bar = %2\n", beat_frames, frames_per_bar));
if (meter->frame() > tempo->frame()) {
bar = meter->start().bars;
beat = meter->start().beats;
current = meter->frame();
} else {
bar = tempo->start().bars;
beat = tempo->start().beats;
current = tempo->frame();
}
/* initialize current to point to the bar/beat just prior to the
lower frame bound passed in. assumes that current is initialized
above to be on a beat.
*/
delta_bars = (lower-current) / frames_per_bar;
delta_beats = modf(delta_bars, &dummy) * divisions_per_bar;
current += (floor(delta_bars) * frames_per_bar) + (floor(delta_beats) * beat_frames);
// adjust bars and beats too
bar += (uint32_t) (floor(delta_bars));
beat += (uint32_t) (floor(delta_beats));
points = new BBTPointList;
do {
/* we're going to add bar or beat points until we hit the
earlier of:
(1) the end point of this request
(2) the next metric section
*/
if (next_metric == metrics->end()) {
limit = upper;
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("== limit set to end of request @ %1\n", limit));
} else {
limit = (*next_metric)->frame();
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("== limit set to next metric section @ %1\n", limit));
for (BBTPointList::const_iterator i = _map.begin(); i != _map.end(); ++i) {
if ((*i).frame < lower) {
continue;
}
limit = min (limit, upper);
bool reset_current_to_metric_section = true;
bool bar_adjusted = false;
TempoSection* ts;
while (current < limit) {
/* if we're at the start of a bar, add bar point */
if (beat == 1) {
if (current >= lower) {
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Bar at %1|1 @ %2\n", bar, current));
points->push_back (BBTPoint (*meter, *tempo,(framepos_t) llrint(current), Bar, bar, 1));
}
}
/* add some beats if we can */
beat_frame = current;
const uint32_t max_divs = ceil (divisions_per_bar);
while (beat <= max_divs && beat_frame < limit) {
if (beat_frame >= lower) {
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("Add Beat at %1|%2 @ %3\n", bar, beat, beat_frame));
points->push_back (BBTPoint (*meter, *tempo, (framepos_t) llrint(beat_frame), Beat, bar, beat));
}
beat_frame += beat_frames;
current = beat_frame;
beat++;
}
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("break in beats addition @ end ? %1 out of bpb ? %2 beat frame @ %3 vs %4 beat @ %5 vs %6\n",
(next_metric == metrics->end()), (beat > max_divs), beat_frame, limit, beat, max_divs));
if (beat <= max_divs) {
/* we didn't reach the end of the bar.
this could be be because we hit "upper"
or a new metric section.
*/
if (next_metric != metrics->end() && limit == (*next_metric)->frame()) {
/* we bumped into a new metric
* section. meter sections are always
* at bar boundaries, but tempo
* sections can begin anywhere and need
* special handling if they are not on
* a beat boundary.
*/
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("stopped at metric at %1 @ 2\n", (*next_metric)->start(), (*next_metric)->frame()));
if (((ts = dynamic_cast<TempoSection*> (*next_metric)) != 0) && ts->start().ticks != 0) {
/* compute current at the *next* beat,
using the tempo section we just
bumped into.
*/
/* recompute how many frames per
* division using the tempo we've just
* found
*/
double next_beat_frames = meter->frames_per_division (*ts,_frame_rate);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("bumped into non-beat-aligned tempo metric at %1 = %2, adjust next beat using %3\n",
(*next_metric)->start(), (*next_metric)->frame(), ts->bar_offset()));
current -= beat_frames;
current += (ts->bar_offset() * beat_frames) + ((1.0 - ts->bar_offset()) * next_beat_frames);
/* avoid resetting current to position
of the next metric section as we
iterate through "metrics"
further on below.
*/
reset_current_to_metric_section = false;
} else if (dynamic_cast<MeterSection*> (*next_metric)) {
/* we hit a new meter
* section. nothing to do - the
* right thing will happen as
* we move to the next metric
* section down below.
*/
} else {
/* we hit a tempo mark that is
* precisely on beat. nothing
* to do here - the
* right thing will happen as
* we move to the next metric
* section down below.
*/
}
} else {
/* we hit either:
- the end of the requested range
we'll exit from the outer loop soon.
*/
}
} else if ((beat > max_divs) || (next_metric != metrics->end() && dynamic_cast<MeterSection*>(*next_metric))) {
/* we've arrived at either the end of a bar or
a new **meter** marker (not tempo marker).
its important to move `current' forward by
the actual frames_per_bar, not move it to an
integral beat_frame, so that metrics with
non-integral beats-per-bar have their bar
positions set correctly. consider a metric
with 9-1/2 beats-per-bar. the bar we just
filled had 10 beat marks, but the bar end is
1/2 beat before the last beat mark. And it
is also possible that a tempo change occured
in the middle of a bar, so we subtract the
possible extra fraction from the current
*/
if (beat > max_divs) {
/* next bar goes where the numbers suggest */
current -= beat_frames * (max_divs - divisions_per_bar);
DEBUG_TRACE (DEBUG::TempoMath, "++ next bar from numbers\n");
} else {
/* next bar goes where the next meter metric is */
current = limit;
DEBUG_TRACE (DEBUG::TempoMath, "++ next bar at next metric\n");
}
bar++;
beat = 1;
bar_adjusted = true;
}
}
/* if we're done, then we're done */
if (current >= upper) {
if ((*i).frame >= upper) {
break;
}
/* i is an iterator that refers to the next metric (or none).
if there is a next metric, move to it, and continue.
*/
if (next_metric != metrics->end()) {
if ((t = dynamic_cast<const TempoSection*>(*next_metric)) != 0) {
tempo = t;
} else if ((m = dynamic_cast<const MeterSection*>(*next_metric)) != 0) {
meter = m;
if (!bar_adjusted) {
/* new MeterSection, beat always returns to 1 */
beat = 1;
}
}
if (reset_current_to_metric_section) {
current = (*next_metric)->frame ();
}
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("loop around with current @ %1\n", current));
divisions_per_bar = meter->divisions_per_bar ();
frames_per_bar = meter->frames_per_bar (*tempo, _frame_rate);
beat_frames = meter->frames_per_division (*tempo, _frame_rate);
DEBUG_TRACE (DEBUG::TempoMath, string_compose ("New metric with beat frames = %1 bar = %2 dpb %3 meter %4 tempo %5\n",
beat_frames, frames_per_bar, divisions_per_bar, *((Meter*)meter), *((Tempo*)tempo)));
++next_metric;
}
} while (1);
return points;
points.push_back (*i);
}
}
const TempoSection&
@ -1847,7 +1601,7 @@ TempoMap::set_state (const XMLNode& node, int /*version*/)
MetricSectionSorter cmp;
metrics->sort (cmp);
timestamp_metrics (true);
recompute_map (true);
}
}