/*
Tempo Map Overview
- We have tempos, which are nice to think of in whole pulses per minute,
- and meters which divide tempo pulses into bars (via divisions_per_bar)
- and beats (via note_divisor).
- Tempos and meters may be locked to audio or music.
- Because the notion of a beat cannot be determined without both tempo and meter, the first tempo
- and first meter are special. they must move together, and must be locked to audio.
+ Tempos can be thought of as a source of the musical pulse.
+
+ Note that Tempo::beats_per_minute() has nothing to do with musical beats.
+ It should rather be thought of as tempo note divisions per minute.
+
+ TempoSections, which are nice to think of in whole pulses per minute,
+ and MeterSecions which divide tempo pulses into measures (via divisions_per_bar)
+ and beats (via note_divisor) are used to form a tempo map.
+ TempoSections and MeterSections may be locked to either audio or music (position lock style).
+ We construct the tempo map by first using the frame or pulse position (depending on position lock style) of each tempo.
+ We then use this pulse/frame layout to find the beat & pulse or frame position of each meter (again depending on lock style).
+
+ Having done this, we can now find any one of tempo, beat, frame or pulse if a beat, frame, pulse or tempo is known.
+
+ The first tempo and first meter are special. they must move together, and must be locked to audio.
Audio locked tempos which lie before the first meter are made inactive.
They will be re-activated if the first meter is again placed before them.
Both tempos and meters have a pulse position and a frame position.
- Meters also have a beat position, which is 0.0 for the first meter.
- A tempo locked to music is locked to pulses.
+ Meters also have a beat position, which is always 0.0 for the first meter.
+
+ A tempo locked to music is locked to musical pulses.
A meter locked to music is locked to beats.
+
Recomputing the tempo map is the process where the 'missing' position
- (tempo pulse or meter pulse & beat in the case of AudioTime and frame for MusicTime) is calculated
- based on the lock preference (position_lock_style).
+ (tempo pulse or meter pulse & beat in the case of AudioTime, frame for MusicTime) is calculated.
It is important to keep the _metrics in an order that makes sense.
- Because ramped MusicTime and AudioTime tempos can interact with each other
- and cause reordering, care must be taken to keep _metrics in a solved state.
+ Because ramped MusicTime and AudioTime tempos can interact with each other,
+ reordering is frequent. Care must be taken to keep _metrics in a solved state.
Solved means ordered by frame or pulse with frame-accurate precision (see check_solved()).
*/
struct MetricSectionSorter {
Glib::Threads::RWLock::WriterLock lm (lock);
MeterSection& first (first_meter());
- const PositionLockStyle pl = ms.position_lock_style();
- if (ms.pulse() != first.pulse()) {
+ if (ms.movable()) {
remove_meter_locked (ms);
add_meter_locked (meter, bbt_to_beats_locked (_metrics, where), where, true);
} else {
+ const PositionLockStyle pl = ms.position_lock_style();
/* cannot move the first meter section */
*static_cast<Meter*>(&first) = meter;
first.set_position_lock_style (pl);
{
{
Glib::Threads::RWLock::WriterLock lm (lock);
- MeterSection& first (first_meter());
- TempoSection& first_t (first_tempo());
- if (ms.pulse() != first.pulse()) {
+ const double beat = ms.beat();
+ const BBT_Time bbt = ms.bbt();
+ if (ms.movable()) {
remove_meter_locked (ms);
- add_meter_locked (meter, frame, true);
+ add_meter_locked (meter, frame, beat, bbt, true);
} else {
+ MeterSection& first (first_meter());
+ TempoSection& first_t (first_tempo());
/* cannot move the first meter section */
*static_cast<Meter*>(&first) = meter;
first.set_position_lock_style (AudioTime);
first.set_frame (frame);
pair<double, BBT_Time> beat = make_pair (0.0, BBT_Time (1, 1, 0));
first.set_beat (beat);
- recompute_meters (_metrics);
first_t.set_frame (first.frame());
first_t.set_pulse (0.0);
first_t.set_position_lock_style (AudioTime);
}
void
-TempoMap::add_meter (const Meter& meter, const framepos_t& frame)
+TempoMap::add_meter (const Meter& meter, const framepos_t& frame, const double& beat, const Timecode::BBT_Time& where)
{
{
Glib::Threads::RWLock::WriterLock lm (lock);
- add_meter_locked (meter, frame, true);
+ add_meter_locked (meter, frame, beat, where, true);
}
}
void
-TempoMap::add_meter_locked (const Meter& meter, framepos_t frame, bool recompute)
+TempoMap::add_meter_locked (const Meter& meter, framepos_t frame, double beat, Timecode::BBT_Time where, bool recompute)
{
- MeterSection* new_meter = new MeterSection (frame, 0.0, meter.divisions_per_bar(), meter.note_divisor());
+ MeterSection* new_meter = new MeterSection (frame, beat, where, meter.divisions_per_bar(), meter.note_divisor());
+
+ double pulse = pulse_at_frame_locked (_metrics, frame);
+ new_meter->set_pulse (pulse);
do_insert (new_meter);
Glib::Threads::RWLock::WriterLock lm (lock);
TempoSection* new_section = copy_metrics_and_point (future_map, ts);
if (solve_map (future_map, new_section, bpm, pulse_at_beat_locked (future_map, beat))) {
- solve_map (_metrics, ts, bpm, pulse_at_beat_locked (_metrics, beat));
+ solve_map (_metrics, ts, bpm, pulse_at_beat_locked (_metrics, beat));
}
}
copy.push_back (cp);
}
if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+ MeterSection* cp = 0;
if (m->position_lock_style() == MusicTime) {
- copy.push_back (new MeterSection (m->pulse(), m->beat(), m->bbt(), m->divisions_per_bar(), m->note_divisor()));
+ cp = new MeterSection (m->pulse(), m->beat(), m->bbt(), m->divisions_per_bar(), m->note_divisor());
} else {
- copy.push_back (new MeterSection (m->frame(), m->beat(), m->divisions_per_bar(), m->note_divisor()));
+ cp = new MeterSection (m->frame(), m->beat(), m->bbt(), m->divisions_per_bar(), m->note_divisor());
}
+ cp->set_movable (m->movable());
+ copy.push_back (cp);
}
}
- recompute_map (copy);
+ //recompute_map (copy);
return ret;
}
if (!t->active()) {
continue;
}
- return *t;
+ if (!t->movable()) {
+ return *t;
+ }
}
}
if (!t->active()) {
continue;
}
- return *t;
+ if (!t->movable()) {
+ return *t;
+ }
}
}
{
MeterSection* meter = 0;
MeterSection* prev_m = 0;
- double accumulated_beats = 0.0;
uint32_t accumulated_bars = 0;
for (Metrics::const_iterator mi = metrics.begin(); mi != metrics.end(); ++mi) {
if ((meter = dynamic_cast<MeterSection*> (*mi)) != 0) {
if (prev_m) {
const double beats_in_m = (meter->pulse() - prev_m->pulse()) * prev_m->note_divisor();
- accumulated_beats += beats_in_m;
accumulated_bars += (beats_in_m + 1) / prev_m->divisions_per_bar();
}
if (meter->position_lock_style() == AudioTime) {
double pulse = 0.0;
- pair<double, BBT_Time> bt = make_pair (accumulated_beats, BBT_Time (accumulated_bars + 1, 1, 0));
- meter->set_beat (bt);
+ pair<double, BBT_Time> b_bbt;
if (prev_m) {
- pulse = prev_m->pulse() + (meter->beat() - prev_m->beat()) / prev_m->note_divisor();
- } else {
- if (meter->movable()) {
- pulse = pulse_at_frame_locked (metrics, meter->frame());
- } else {
- pulse = 0.0;
- }
+ double beats = ((pulse_at_frame_locked (metrics, meter->frame()) - prev_m->pulse()) * prev_m->note_divisor()) - prev_m->beat();
+ b_bbt = make_pair (ceil (beats), BBT_Time (accumulated_bars + 1, 1, 0));
+ const double true_pulse = prev_m->pulse() + (ceil (beats) - prev_m->beat()) / prev_m->note_divisor();
+ const double pulse_off = true_pulse - ((beats - prev_m->beat()) / prev_m->note_divisor());
+ pulse = true_pulse - pulse_off;
+ }
+ if (!meter->movable()) {
+ b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
}
+ meter->set_beat (b_bbt);
meter->set_pulse (pulse);
} else {
double pulse = 0.0;
double
TempoMap::beat_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
{
- framecnt_t const offset_frame = frame + frame_offset_at (metrics, frame);
- double const pulse = pulse_at_frame_locked (metrics, offset_frame);
+ //framecnt_t const offset_frame = frame + frame_offset_at (metrics, frame);
+ double const pulse = pulse_at_frame_locked (metrics, frame);
return beat_at_pulse_locked (metrics, pulse);
}
TempoMap::frame_at_beat_locked (const Metrics& metrics, const double& beat) const
{
framecnt_t const frame = frame_at_pulse_locked (metrics, pulse_at_beat_locked (metrics, beat));
- frameoffset_t const frame_off = frame_offset_at (metrics, frame);
- return frame - frame_off;
+ //frameoffset_t const frame_off = frame_offset_at (metrics, frame);
+ return frame;
}
framecnt_t
}
bool
-TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const Tempo& bpm, const framepos_t& frame)
+TempoMap::set_active_tempos (const Metrics& metrics, const framepos_t& frame)
{
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (!t->movable()) {
+ t->set_active (true);
+ continue;
+ }
+ if (t->movable() && t->active () && t->position_lock_style() == AudioTime && t->frame() < frame) {
+ t->set_active (false);
+ t->set_pulse (0.0);
+ } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() > frame) {
+ t->set_active (true);
+ } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() == frame) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+bool
+TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const Tempo& bpm, const framepos_t& frame)
+{
TempoSection* prev_ts = 0;
TempoSection* section_prev = 0;
- MetricSectionFrameSorter fcmp;
- MetricSectionSorter cmp;
framepos_t first_m_frame = 0;
+
for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
MeterSection* m;
if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
if (!m->movable()) {
first_m_frame = m->frame();
+ break;
}
}
}
return true;
}
+ MetricSectionFrameSorter fcmp;
imaginary.sort (fcmp);
if (section->position_lock_style() == MusicTime) {
/* we're setting the frame */
return true;
}
+ MetricSectionSorter cmp;
imaginary.sort (cmp);
if (section->position_lock_style() == MusicTime) {
/* we're setting the frame */
bool
TempoMap::solve_map (Metrics& imaginary, TempoSection* section, const Tempo& bpm, const double& pulse)
{
- MetricSectionSorter cmp;
- MetricSectionFrameSorter fcmp;
TempoSection* prev_ts = 0;
TempoSection* section_prev = 0;
return true;
}
+ MetricSectionSorter cmp;
imaginary.sort (cmp);
if (section->position_lock_style() == AudioTime) {
/* we're setting the pulse */
return true;
}
+ MetricSectionFrameSorter fcmp;
imaginary.sort (fcmp);
if (section->position_lock_style() == AudioTime) {
/* we're setting the pulse */
}
void
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const Meter& mt, const double& pulse)
+TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const Meter& mt, const framepos_t& frame)
{
MeterSection* prev_ms = 0;
- double accumulated_beats = 0.0;
+
+ if (!section->movable()) {
+ /* lock the first tempo to our first meter */
+ if (!set_active_tempos (imaginary, frame)) {
+ return;
+ }
+ TempoSection* first_t = &first_tempo();
+ Metrics future_map;
+ TempoSection* new_section = copy_metrics_and_point (future_map, first_t);
+
+ new_section->set_frame (frame);
+ new_section->set_pulse (0.0);
+ new_section->set_active (true);
+
+ if (solve_map (future_map, new_section, Tempo (new_section->beats_per_minute(), new_section->note_type()), frame)) {
+ first_t->set_frame (frame);
+ first_t->set_pulse (0.0);
+ first_t->set_active (true);
+ solve_map (imaginary, first_t, Tempo (first_t->beats_per_minute(), first_t->note_type()), frame);
+ } else {
+ return;
+ }
+ }
+
uint32_t accumulated_bars = 0;
- section->set_pulse (pulse);
+ section->set_frame (frame);
for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
MeterSection* m;
if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
if (prev_ms) {
- double const beats_in_m = (m->pulse() - prev_ms->pulse()) * prev_ms->note_divisor();
- accumulated_beats += beats_in_m;
+ const double beats_in_m = (m->pulse() - prev_ms->pulse()) * prev_ms->note_divisor();
accumulated_bars += (beats_in_m + 1) / prev_ms->divisions_per_bar();
}
if (m == section){
- section->set_frame (frame_at_pulse_locked (imaginary, pulse));
- pair<double, BBT_Time> b_bbt = make_pair (accumulated_beats, BBT_Time (accumulated_bars + 1, 1, 0));
- section->set_beat (b_bbt);
- prev_ms = section;
+ /*
+ here we set the beat for this frame.
+ we're going to set it 'incorrectly' to the next integer and use this difference
+ to find the meter's pulse later.
+ (meters should fall on absolute beats to keep us sane)
+ */
+ double pulse = 0.0;
+ pair<double, BBT_Time> b_bbt;
+ if (m->movable()) {
+ double beats = ((pulse_at_frame_locked (imaginary, frame) - prev_ms->pulse()) * prev_ms->note_divisor()) - prev_ms->beat();
+ b_bbt = make_pair (ceil (beats), BBT_Time (accumulated_bars + 1, 1, 0));
+ const double true_pulse = prev_ms->pulse() + ((ceil (beats) - prev_ms->beat()) / prev_ms->note_divisor());
+ const double pulse_off = true_pulse - ((beats - prev_ms->beat()) / prev_ms->note_divisor());
+ pulse = true_pulse - pulse_off;
+ } else {
+ b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+ }
+ m->set_beat (b_bbt);
+ m->set_pulse (pulse);
+ prev_ms = m;
continue;
}
if (prev_ms) {
m->set_frame (frame_at_pulse_locked (imaginary, pulse));
m->set_pulse (pulse);
} else {
- pair<double, BBT_Time> b_bbt = make_pair (accumulated_beats, BBT_Time (accumulated_bars + 1, 1, 0));
- m->set_beat (b_bbt);
+ if (!m->movable()) {
+ pair<double, BBT_Time> b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+ m->set_beat (b_bbt);
+ }
const double pulse = prev_ms->pulse() + (m->beat() - prev_ms->beat()) / prev_ms->note_divisor();
m->set_pulse (pulse);
}
}
}
- if (section->position_lock_style() == AudioTime) {
- /* we're setting the pulse */
- section->set_position_lock_style (MusicTime);
- recompute_meters (imaginary);
+ if (section->position_lock_style() == MusicTime) {
+ /* we're setting the frame */
section->set_position_lock_style (AudioTime);
+ recompute_meters (imaginary);
+ section->set_position_lock_style (MusicTime);
} else {
recompute_meters (imaginary);
}
+ //dump (imaginary, std::cerr);
}
void
-TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const Meter& mt, const framepos_t& frame)
+TempoMap::solve_map (Metrics& imaginary, MeterSection* section, const Meter& mt, const double& pulse)
{
MeterSection* prev_ms = 0;
-
- if (!section->movable()) {
- TempoSection* first_t;
- for (Metrics::const_iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
- TempoSection* t;
- if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
- if (!t->movable()) {
- t->set_active (true);
- first_t = t;
- }
- if (t->movable() && t->active () && t->position_lock_style() == AudioTime && t->frame() < frame) {
- t->set_active (false);
- t->set_pulse (0.0);
- } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() > frame) {
- t->set_active (true);
- } else if (t->movable() && t->position_lock_style() == AudioTime && t->frame() == frame) {
- return;
- }
- }
- }
-
- Metrics future_map;
- TempoSection* new_section = copy_metrics_and_point (future_map, first_t);
-
- new_section->set_frame (frame);
- new_section->set_pulse (0.0);
- new_section->set_active (true);
-
- if (solve_map (future_map, new_section, Tempo (new_section->beats_per_minute(), new_section->note_type()), frame)) {
- first_t->set_frame (frame);
- first_t->set_pulse (0.0);
- first_t->set_active (true);
- solve_map (imaginary, first_t, Tempo (first_t->beats_per_minute(), first_t->note_type()), frame);
- } else {
- return;
- }
- }
-
double accumulated_beats = 0.0;
uint32_t accumulated_bars = 0;
- section->set_frame (frame);
+ section->set_pulse (pulse);
for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
MeterSection* m;
if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
if (prev_ms) {
- const double beats_in_m = (m->pulse() - prev_ms->pulse()) * prev_ms->note_divisor();
+ double const beats_in_m = (m->pulse() - prev_ms->pulse()) * prev_ms->note_divisor();
accumulated_beats += beats_in_m;
accumulated_bars += (beats_in_m + 1) / prev_ms->divisions_per_bar();
}
if (m == section){
- /*
- here we define the pulse for this frame.
- we're going to set it 'incorrectly' to the next integer and use this 'error'
- as an offset to the map as far as users of the public methods are concerned.
- (meters should go on absolute pulses to keep us sane)
- */
+ section->set_frame (frame_at_pulse_locked (imaginary, pulse));
pair<double, BBT_Time> b_bbt = make_pair (accumulated_beats, BBT_Time (accumulated_bars + 1, 1, 0));
- if (m->movable()) {
- m->set_pulse (pulse_at_frame_locked (imaginary, frame));
- } else {
- m->set_pulse (0.0);
- }
- m->set_beat (b_bbt);
- prev_ms = m;
+ section->set_beat (b_bbt);
+ prev_ms = section;
continue;
}
if (prev_ms) {
m->set_frame (frame_at_pulse_locked (imaginary, pulse));
m->set_pulse (pulse);
} else {
- pair<double, BBT_Time> b_bbt = make_pair (accumulated_beats, BBT_Time (accumulated_bars + 1, 1, 0));
- m->set_beat (b_bbt);
+ if (!m->movable()) {
+ pair<double, BBT_Time> b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+ m->set_beat (b_bbt);
+ }
const double pulse = prev_ms->pulse() + (m->beat() - prev_ms->beat()) / prev_ms->note_divisor();
m->set_pulse (pulse);
}
}
}
- if (section->position_lock_style() == MusicTime) {
- /* we're setting the frame */
- section->set_position_lock_style (AudioTime);
- recompute_meters (imaginary);
+ if (section->position_lock_style() == AudioTime) {
+ /* we're setting the pulse */
section->set_position_lock_style (MusicTime);
+ recompute_meters (imaginary);
+ section->set_position_lock_style (AudioTime);
} else {
recompute_meters (imaginary);
}
- //dump (imaginary, std::cerr);
}
framecnt_t
TempoSection const tempo = tempo_section_at_locked (pos);
MeterSection const meter = meter_section_at_locked (pos);
BBT_Time const bbt = beats_to_bbt (cnt);
-
- points.push_back (BBTPoint (meter, tempo_at_locked (pos), pos, bbt.bars, bbt.beats, tempo.get_c_func()));
+ BBTPoint point = BBTPoint (meter, tempo_at_locked (pos), pos, bbt.bars, bbt.beats, tempo.get_c_func());
+ points.push_back (point);
++cnt;
}
}