+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return pulse_at_beat_locked (_metrics, beat);
+}
+
+double
+TempoMap::pulse_at_beat_locked (const Metrics& metrics, const double& beat) const
+{
+ const MeterSection* prev_m = &meter_section_at_beat_locked (metrics, beat);
+
+ return prev_m->pulse() + ((beat - prev_m->beat()) / prev_m->note_divisor());
+}
+
+double
+TempoMap::beat_at_pulse (const double& pulse) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return beat_at_pulse_locked (_metrics, pulse);
+}
+
+double
+TempoMap::beat_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+ MeterSection* prev_m = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (prev_m && m->pulse() > pulse) {
+ if (((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat() > m->beat()) {
+ break;
+ }
+ }
+ prev_m = m;
+ }
+ }
+
+ double const ret = ((pulse - prev_m->pulse()) * prev_m->note_divisor()) + prev_m->beat();
+ return ret;
+}
+
+double
+TempoMap::pulse_at_frame (const framecnt_t& frame) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return pulse_at_frame_locked (_metrics, frame);
+}
+
+/* tempo section based */
+double
+TempoMap::pulse_at_frame_locked (const Metrics& metrics, const framecnt_t& frame) const
+{
+ /* HOLD (at least) THE READER LOCK */
+ TempoSection* prev_t = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (!t->active()) {
+ continue;
+ }
+ if (prev_t && t->frame() > frame) {
+ /*the previous ts is the one containing the frame */
+ const double ret = prev_t->pulse_at_frame (frame, _frame_rate);
+ return ret;
+ }
+ prev_t = t;
+ }
+ }
+
+ /* treated as constant for this ts */
+ const double pulses_in_section = (frame - prev_t->frame()) / prev_t->frames_per_pulse (_frame_rate);
+
+ return pulses_in_section + prev_t->pulse();
+}
+
+framecnt_t
+TempoMap::frame_at_pulse (const double& pulse) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return frame_at_pulse_locked (_metrics, pulse);
+}
+
+/* tempo section based */
+framecnt_t
+TempoMap::frame_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+ /* HOLD THE READER LOCK */
+
+ const TempoSection* prev_t = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (!t->active()) {
+ continue;
+ }
+ if (prev_t && t->pulse() > pulse) {
+ return prev_t->frame_at_pulse (pulse, _frame_rate);
+ }
+
+ prev_t = t;
+ }
+ }
+ /* must be treated as constant, irrespective of _type */
+ double const pulses_in_section = pulse - prev_t->pulse();
+ double const dtime = pulses_in_section * prev_t->frames_per_pulse (_frame_rate);
+
+ framecnt_t const ret = (framecnt_t) floor (dtime) + prev_t->frame();
+
+ return ret;
+}
+
+double
+TempoMap::beat_at_bbt (const Timecode::BBT_Time& bbt)
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return beat_at_bbt_locked (_metrics, bbt);
+}
+
+
+double
+TempoMap::beat_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+{
+ /* CALLER HOLDS READ LOCK */
+
+ MeterSection* prev_m = 0;
+
+ /* because audio-locked meters have 'fake' integral beats,
+ there is no pulse offset here.
+ */
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (prev_m) {
+ const double bars_to_m = (m->beat() - prev_m->beat()) / prev_m->divisions_per_bar();
+ if ((bars_to_m + (prev_m->bbt().bars - 1)) > (bbt.bars - 1)) {
+ break;
+ }
+ }
+ prev_m = m;
+ }
+ }
+
+ const double remaining_bars = bbt.bars - prev_m->bbt().bars;
+ const double remaining_bars_in_beats = remaining_bars * prev_m->divisions_per_bar();
+ const double ret = remaining_bars_in_beats + prev_m->beat() + (bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat);
+
+ return ret;
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_beat (const double& beats)
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ return bbt_at_beat_locked (_metrics, beats);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_beat_locked (const Metrics& metrics, const double& b) const
+{
+ /* CALLER HOLDS READ LOCK */
+ MeterSection* prev_m = 0;
+ const double beats = max (0.0, b);
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m = 0;
+
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (prev_m) {
+ if (m->beat() > beats) {
+ /* this is the meter after the one our beat is on*/
+ break;
+ }
+ }
+
+ prev_m = m;
+ }
+ }
+
+ const double beats_in_ms = beats - prev_m->beat();
+ const uint32_t bars_in_ms = (uint32_t) floor (beats_in_ms / prev_m->divisions_per_bar());
+ const uint32_t total_bars = bars_in_ms + (prev_m->bbt().bars - 1);
+ const double remaining_beats = beats_in_ms - (bars_in_ms * prev_m->divisions_per_bar());
+ const double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat;
+
+ BBT_Time ret;
+
+ ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
+ ret.beats = (uint32_t) floor (remaining_beats);
+ ret.bars = total_bars;
+
+ /* 0 0 0 to 1 1 0 - based mapping*/
+ ++ret.bars;
+ ++ret.beats;
+
+ if (ret.ticks >= BBT_Time::ticks_per_beat) {
+ ++ret.beats;
+ ret.ticks -= BBT_Time::ticks_per_beat;
+ }
+
+ if (ret.beats >= prev_m->divisions_per_bar() + 1) {
+ ++ret.bars;
+ ret.beats = 1;
+ }
+
+ return ret;
+}
+
+double
+TempoMap::pulse_at_bbt (const Timecode::BBT_Time& bbt)
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return pulse_at_bbt_locked (_metrics, bbt);
+}
+
+double
+TempoMap::pulse_at_bbt_locked (const Metrics& metrics, const Timecode::BBT_Time& bbt) const
+{
+ /* CALLER HOLDS READ LOCK */
+
+ MeterSection* prev_m = 0;
+
+ /* because audio-locked meters have 'fake' integral beats,
+ there is no pulse offset here.
+ */
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (prev_m) {
+ if (m->bbt().bars > bbt.bars) {
+ break;
+ }
+ }
+ prev_m = m;
+ }
+ }
+
+ const double remaining_bars = bbt.bars - prev_m->bbt().bars;
+ const double remaining_pulses = remaining_bars * prev_m->divisions_per_bar() / prev_m->note_divisor();
+ const double ret = remaining_pulses + prev_m->pulse() + (((bbt.beats - 1) + (bbt.ticks / BBT_Time::ticks_per_beat)) / prev_m->note_divisor());
+
+ return ret;
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_pulse (const double& pulse)
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return bbt_at_pulse_locked (_metrics, pulse);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_pulse_locked (const Metrics& metrics, const double& pulse) const
+{
+ MeterSection* prev_m = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ MeterSection* m = 0;
+
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+
+ if (prev_m) {
+ double const pulses_to_m = m->pulse() - prev_m->pulse();
+ if (prev_m->pulse() + pulses_to_m > pulse) {
+ /* this is the meter after the one our beat is on*/
+ break;
+ }
+ }
+
+ prev_m = m;
+ }
+ }
+
+ const double beats_in_ms = (pulse - prev_m->pulse()) * prev_m->note_divisor();
+ const uint32_t bars_in_ms = (uint32_t) floor (beats_in_ms / prev_m->divisions_per_bar());
+ const uint32_t total_bars = bars_in_ms + (prev_m->bbt().bars - 1);
+ const double remaining_beats = beats_in_ms - (bars_in_ms * prev_m->divisions_per_bar());
+ const double remaining_ticks = (remaining_beats - floor (remaining_beats)) * BBT_Time::ticks_per_beat;
+
+ BBT_Time ret;
+
+ ret.ticks = (uint32_t) floor (remaining_ticks + 0.5);
+ ret.beats = (uint32_t) floor (remaining_beats);
+ ret.bars = total_bars;
+
+ /* 0 0 0 to 1 1 0 mapping*/
+ ++ret.bars;
+ ++ret.beats;
+
+ if (ret.ticks >= BBT_Time::ticks_per_beat) {
+ ++ret.beats;
+ ret.ticks -= BBT_Time::ticks_per_beat;
+ }
+
+ if (ret.beats >= prev_m->divisions_per_bar() + 1) {
+ ++ret.bars;
+ ret.beats = 1;
+ }
+
+ return ret;
+}
+
+BBT_Time
+TempoMap::bbt_at_frame (framepos_t frame)
+{
+ if (frame < 0) {
+ BBT_Time bbt;
+ 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 bbt;
+ }
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return bbt_at_frame_locked (_metrics, frame);
+}
+
+BBT_Time
+TempoMap::bbt_at_frame_rt (framepos_t frame)
+{
+ 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");
+ }
+
+ return bbt_at_frame_locked (_metrics, frame);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_frame_locked (const Metrics& metrics, const framepos_t& frame) const
+{
+ if (frame < 0) {
+ BBT_Time bbt;
+ 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 bbt;
+ }
+ const double beat = beat_at_frame_locked (metrics, frame);
+
+ return bbt_at_beat_locked (metrics, beat);
+}
+
+framepos_t
+TempoMap::frame_at_bbt (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");
+ }
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return frame_at_bbt_locked (_metrics, bbt);
+}
+
+/* meter & tempo section based */
+framepos_t
+TempoMap::frame_at_bbt_locked (const Metrics& metrics, const BBT_Time& bbt) const
+{
+ /* HOLD THE READER LOCK */
+
+ const framepos_t ret = frame_at_beat_locked (metrics, beat_at_bbt_locked (metrics, bbt));
+ return ret;
+}
+
+bool
+TempoMap::check_solved (const Metrics& metrics) const
+{
+ TempoSection* prev_t = 0;
+ MeterSection* prev_m = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ MeterSection* m;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (!t->active()) {
+ continue;
+ }
+ if (prev_t) {
+ /* check ordering */
+ if ((t->frame() <= prev_t->frame()) || (t->pulse() <= prev_t->pulse())) {
+ return false;
+ }
+
+ /* precision check ensures tempo and frames align.*/
+ if (t->frame() != prev_t->frame_at_tempo (t->pulses_per_minute(), t->pulse(), _frame_rate)) {
+ if (!t->locked_to_meter()) {
+ return false;
+ }
+ }
+
+ /* gradient limit - who knows what it should be?
+ things are also ok (if a little chaotic) without this
+ */
+ if (fabs (prev_t->c_func()) > 1000.0) {
+ //std::cout << "c : " << prev_t->c_func() << std::endl;
+ return false;
+ }
+ }
+ prev_t = t;
+ }
+
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (prev_m && m->position_lock_style() == AudioTime) {
+ TempoSection* t = const_cast<TempoSection*>(&tempo_section_at_frame_locked (metrics, m->frame() - 1));
+ const double nascent_m_pulse = ((m->beat() - prev_m->beat()) / prev_m->note_divisor()) + prev_m->pulse();
+ const framepos_t nascent_m_frame = t->frame_at_pulse (nascent_m_pulse, _frame_rate);
+
+ if (t && (nascent_m_frame > m->frame() || nascent_m_frame < 0)) {
+ return false;
+ }
+ }
+
+ prev_m = m;
+ }
+
+ }
+
+ return true;
+}
+
+bool
+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_frame (Metrics& imaginary, TempoSection* section, const framepos_t& frame)
+{
+ TempoSection* prev_t = 0;
+ TempoSection* section_prev = 0;
+ framepos_t first_m_frame = 0;
+
+ /* can't move a tempo before the first meter */
+ 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;
+ }
+ }
+ }
+ if (section->movable() && frame <= first_m_frame) {
+ return false;
+ }
+
+ section->set_active (true);
+ section->set_frame (frame);
+
+ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+
+ if (!t->active()) {
+ continue;
+ }
+ if (prev_t) {
+ if (t == section) {
+ section_prev = prev_t;
+ if (t->locked_to_meter()) {
+ prev_t = t;
+ }
+ continue;
+ }
+ if (t->position_lock_style() == MusicTime) {
+ prev_t->set_c_func (prev_t->compute_c_func_pulse (t->pulses_per_minute(), t->pulse(), _frame_rate));
+ t->set_frame (prev_t->frame_at_pulse (t->pulse(), _frame_rate));
+ } else {
+ prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+ if (!t->locked_to_meter()) {
+ t->set_pulse (prev_t->pulse_at_frame (t->frame(), _frame_rate));
+ }
+ }
+ }
+ prev_t = t;
+ }
+ }
+
+ if (section_prev) {
+ section_prev->set_c_func (section_prev->compute_c_func_frame (section->pulses_per_minute(), frame, _frame_rate));
+ if (!section->locked_to_meter()) {
+ section->set_pulse (section_prev->pulse_at_frame (frame, _frame_rate));
+ }
+ }
+
+#if (0)
+ recompute_tempos (imaginary);
+
+ if (check_solved (imaginary)) {
+ return true;
+ } else {
+ dunp (imaginary, std::cout);
+ }
+#endif
+
+ MetricSectionFrameSorter fcmp;
+ imaginary.sort (fcmp);
+
+ recompute_tempos (imaginary);
+
+ if (check_solved (imaginary)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const double& pulse)
+{
+ TempoSection* prev_t = 0;
+ TempoSection* section_prev = 0;
+
+ section->set_pulse (pulse);
+
+ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (!t->active()) {
+ continue;
+ }
+ if (!t->movable()) {
+ t->set_pulse (0.0);
+ prev_t = t;
+ continue;
+ }
+ if (prev_t) {
+ if (t == section) {
+ section_prev = prev_t;
+ continue;
+ }
+ if (t->position_lock_style() == MusicTime) {
+ prev_t->set_c_func (prev_t->compute_c_func_pulse (t->pulses_per_minute(), t->pulse(), _frame_rate));
+ t->set_frame (prev_t->frame_at_pulse (t->pulse(), _frame_rate));
+ } else {
+ prev_t->set_c_func (prev_t->compute_c_func_frame (t->pulses_per_minute(), t->frame(), _frame_rate));
+ if (!t->locked_to_meter()) {
+ t->set_pulse (prev_t->pulse_at_frame (t->frame(), _frame_rate));
+ }
+ }
+ }
+ prev_t = t;
+ }
+ }
+
+ if (section_prev) {
+ section_prev->set_c_func (section_prev->compute_c_func_pulse (section->pulses_per_minute(), pulse, _frame_rate));
+ section->set_frame (section_prev->frame_at_pulse (pulse, _frame_rate));
+ }
+
+#if (0)
+ recompute_tempos (imaginary);
+
+ if (check_solved (imaginary)) {
+ return true;
+ } else {
+ dunp (imaginary, std::cout);
+ }
+#endif
+
+ MetricSectionSorter cmp;
+ imaginary.sort (cmp);
+
+ recompute_tempos (imaginary);
+ /* Reordering
+ * XX need a restriction here, but only for this case,
+ * as audio locked tempos don't interact in the same way.
+ *
+ * With music-locked tempos, the solution to cross-dragging can fly off the screen
+ * e.g.
+ * |50 bpm |250 bpm |60 bpm
+ * drag 250 to the pulse after 60->
+ * a clue: dragging the second 60 <- past the 250 would cause no such problem.
+ */
+ if (check_solved (imaginary)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+TempoMap::solve_map_frame (Metrics& imaginary, MeterSection* section, const framepos_t& frame)
+{
+ /* disallow moving first meter past any subsequent one, and any movable meter before the first one */
+ const MeterSection* other = &meter_section_at_frame_locked (imaginary, frame);
+ if ((!section->movable() && other->movable()) || (!other->movable() && section->movable() && other->frame() >= frame)) {
+ return false;
+ }
+
+ if (!section->movable()) {
+ /* lock the first tempo to our first meter */
+ if (!set_active_tempos (imaginary, frame)) {
+ return false;
+ }
+ }
+
+ TempoSection* meter_locked_tempo = 0;
+
+ for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
+ if ((t->locked_to_meter() || !t->movable()) && t->frame() == section->frame()) {
+ meter_locked_tempo = t;
+ break;
+ }
+ }
+ }
+
+ if (!meter_locked_tempo) {
+ return false;
+ }
+
+ MeterSection* prev_m = 0;
+ Metrics future_map;
+ TempoSection* tempo_copy = copy_metrics_and_point (imaginary, future_map, meter_locked_tempo);
+ bool solved = false;
+
+ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (m == section){
+ if (prev_m && section->movable()) {
+ const double beats = (pulse_at_frame_locked (imaginary, frame) - prev_m->pulse()) * prev_m->note_divisor();
+ if (beats + prev_m->beat() < section->beat()) {
+ /* set the frame/pulse corresponding to its musical position,
+ * as an earlier time than this has been requested.
+ */
+ const double new_pulse = ((section->beat() - prev_m->beat())
+ / prev_m->note_divisor()) + prev_m->pulse();
+
+ const framepos_t smallest_frame = frame_at_pulse_locked (future_map, new_pulse);
+
+ if ((solved = solve_map_frame (future_map, tempo_copy, smallest_frame))) {
+ meter_locked_tempo->set_pulse (new_pulse);
+ solve_map_frame (imaginary, meter_locked_tempo, smallest_frame);
+ section->set_frame (smallest_frame);
+ section->set_pulse (new_pulse);
+ } else {
+ solved = false;
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ if (!solved) {
+ return false;
+ }
+ } else {
+ /* all is ok. set section's locked tempo if allowed.
+ possibly disallowed if there is an adjacent audio-locked tempo.
+ XX this check could possibly go. its never actually happened here.
+ */
+ MeterSection* meter_copy = const_cast<MeterSection*> (&meter_section_at_frame_locked (future_map, section->frame()));
+ meter_copy->set_frame (frame);
+
+ if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
+ section->set_frame (frame);
+ meter_locked_tempo->set_pulse (((section->beat() - prev_m->beat())
+ / prev_m->note_divisor()) + prev_m->pulse());
+ solve_map_frame (imaginary, meter_locked_tempo, frame);
+ } else {
+ solved = false;
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ if (!solved) {
+ return false;
+ }
+ }
+ } else {
+ /* not movable (first meter atm) */
+
+ tempo_copy->set_frame (frame);
+ tempo_copy->set_pulse (0.0);
+
+ if ((solved = solve_map_frame (future_map, tempo_copy, frame))) {
+ section->set_frame (frame);
+ meter_locked_tempo->set_frame (frame);
+ meter_locked_tempo->set_pulse (0.0);
+ solve_map_frame (imaginary, meter_locked_tempo, frame);
+ } else {
+ solved = false;
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ if (!solved) {
+ return false;
+ }
+
+ pair<double, BBT_Time> b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+ section->set_beat (b_bbt);
+ section->set_pulse (0.0);
+
+ }
+ break;
+ }
+
+ prev_m = m;
+ }
+ }
+
+ MetricSectionFrameSorter fcmp;
+ imaginary.sort (fcmp);
+
+ recompute_meters (imaginary);
+
+ return true;
+}
+
+bool
+TempoMap::solve_map_bbt (Metrics& imaginary, MeterSection* section, const BBT_Time& when)
+{
+ /* disallow setting section to an existing meter's bbt */
+ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ if (m != section && m->bbt().bars == when.bars) {
+ return false;
+ }
+ }
+ }
+
+ MeterSection* prev_m = 0;
+ MeterSection* section_prev = 0;
+
+ for (Metrics::iterator i = imaginary.begin(); i != imaginary.end(); ++i) {
+ MeterSection* m;
+ if ((m = dynamic_cast<MeterSection*> (*i)) != 0) {
+ pair<double, BBT_Time> b_bbt;
+ double new_pulse = 0.0;
+
+ if (prev_m && m->bbt().bars > when.bars && !section_prev){
+ section_prev = prev_m;
+ const double beats = (when.bars - section_prev->bbt().bars) * section_prev->divisions_per_bar();
+ const double pulse = (beats / section_prev->note_divisor()) + section_prev->pulse();
+ pair<double, BBT_Time> b_bbt = make_pair (beats + section_prev->beat(), when);
+
+ section->set_beat (b_bbt);
+ section->set_pulse (pulse);
+ section->set_frame (frame_at_pulse_locked (imaginary, pulse));
+ prev_m = section;
+ continue;
+ }
+
+ if (m->position_lock_style() == AudioTime) {
+ TempoSection* meter_locked_tempo = 0;
+
+ for (Metrics::const_iterator ii = imaginary.begin(); ii != imaginary.end(); ++ii) {
+ TempoSection* t;
+ if ((t = dynamic_cast<TempoSection*> (*ii)) != 0) {
+ if ((t->locked_to_meter() || !t->movable()) && t->frame() == m->frame()) {
+ meter_locked_tempo = t;
+ break;
+ }
+ }
+ }
+
+ if (!meter_locked_tempo) {
+ return false;
+ }
+
+ if (prev_m) {
+ const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar());
+
+ if (beats + prev_m->beat() != m->beat()) {
+ /* tempo/ meter change caused a change in beat (bar). */
+ b_bbt = make_pair (beats + prev_m->beat()
+ , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
+ new_pulse = prev_m->pulse() + (beats / prev_m->note_divisor());
+ } else if (m->movable()) {
+ b_bbt = make_pair (m->beat(), m->bbt());
+ new_pulse = prev_m->pulse() + (beats / prev_m->note_divisor());
+ }
+ } else {
+ b_bbt = make_pair (0.0, BBT_Time (1, 1, 0));
+ }
+
+ meter_locked_tempo->set_pulse (new_pulse);
+ m->set_beat (b_bbt);
+ m->set_pulse (new_pulse);
+
+ } else {
+ /* MusicTime */
+ const double beats = ((m->bbt().bars - prev_m->bbt().bars) * prev_m->divisions_per_bar());
+ if (beats + prev_m->beat() != m->beat()) {
+ /* tempo/ meter change caused a change in beat (bar). */
+ b_bbt = make_pair (beats + prev_m->beat()
+ , BBT_Time ((beats / prev_m->divisions_per_bar()) + prev_m->bbt().bars, 1, 0));
+ } else {
+ b_bbt = make_pair (beats + prev_m->beat()
+ , m->bbt());
+ }
+ new_pulse = (beats / prev_m->note_divisor()) + prev_m->pulse();
+ m->set_beat (b_bbt);
+ m->set_pulse (new_pulse);
+ m->set_frame (frame_at_pulse_locked (imaginary, new_pulse));
+ }
+
+ prev_m = m;
+ }
+ }
+
+ if (!section_prev) {
+
+ const double beats = (when.bars - prev_m->bbt().bars) * prev_m->divisions_per_bar();
+ const double pulse = (beats / prev_m->note_divisor()) + prev_m->pulse();
+ pair<double, BBT_Time> b_bbt = make_pair (beats + prev_m->beat(), when);
+
+ section->set_beat (b_bbt);
+ section->set_pulse (pulse);
+ section->set_frame (frame_at_pulse_locked (imaginary, pulse));
+ }
+
+ MetricSectionSorter cmp;
+ imaginary.sort (cmp);
+
+ recompute_meters (imaginary);
+
+ return true;
+}
+
+/** places a copy of _metrics into copy and returns a pointer
+ * to section's equivalent in copy.
+ */
+TempoSection*
+TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, TempoSection* section)
+{
+ TempoSection* ret = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ MeterSection* m;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (t == section) {
+ ret = new TempoSection (*t);
+ copy.push_back (ret);
+ continue;
+ }
+
+ TempoSection* cp = new TempoSection (*t);
+ copy.push_back (cp);
+ }
+ if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+ MeterSection* cp = new MeterSection (*m);
+ copy.push_back (cp);
+ }
+ }
+
+ return ret;
+}
+
+MeterSection*
+TempoMap::copy_metrics_and_point (const Metrics& metrics, Metrics& copy, MeterSection* section)
+{
+ MeterSection* ret = 0;
+
+ for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
+ TempoSection* t;
+ MeterSection* m;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ TempoSection* cp = new TempoSection (*t);
+ copy.push_back (cp);
+ }
+
+ if ((m = dynamic_cast<MeterSection *> (*i)) != 0) {
+ if (m == section) {
+ ret = new MeterSection (*m);
+ copy.push_back (ret);
+ continue;
+ }
+ MeterSection* cp = new MeterSection (*m);
+ copy.push_back (cp);
+ }
+ }
+
+ return ret;
+}
+
+/** answers the question "is this a valid beat position for this tempo section?".
+ * it returns true if the tempo section can be moved to the requested bbt position,
+ * leaving the tempo map in a solved state.
+ * @param section the tempo section to be moved
+ * @param bbt the requested new position for the tempo section
+ * @return true if the tempo section can be moved to the position, otherwise false.
+ */
+bool
+TempoMap::can_solve_bbt (TempoSection* ts, const BBT_Time& bbt)
+{
+ Metrics copy;
+ TempoSection* tempo_copy = 0;
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+ tempo_copy = copy_metrics_and_point (_metrics, copy, ts);
+ if (!tempo_copy) {
+ return false;
+ }
+ }
+
+ const bool ret = solve_map_pulse (copy, tempo_copy, pulse_at_bbt_locked (copy, bbt));
+
+ Metrics::const_iterator d = copy.begin();
+ while (d != copy.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ return ret;
+}
+
+/**
+* This is for a gui that needs to know the pulse or frame of a tempo section if it were to be moved to some bbt time,
+* taking any possible reordering as a consequence of this into account.
+* @param section - the section to be altered
+* @param bbt - the bbt where the altered tempo will fall
+* @return returns - the position in pulses and frames (as a pair) where the new tempo section will lie.
+*/
+pair<double, framepos_t>
+TempoMap::predict_tempo_position (TempoSection* section, const BBT_Time& bbt)
+{
+ Metrics future_map;
+ pair<double, framepos_t> ret = make_pair (0.0, 0);
+
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, section);
+
+ const double beat = beat_at_bbt_locked (future_map, bbt);
+
+ if (solve_map_pulse (future_map, tempo_copy, pulse_at_beat_locked (future_map, beat))) {
+ ret.first = tempo_copy->pulse();
+ ret.second = tempo_copy->frame();
+ } else {
+ ret.first = section->pulse();
+ ret.second = section->frame();
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+ return ret;
+}
+
+void
+TempoMap::gui_move_tempo (TempoSection* ts, const framepos_t& frame, const int& sub_num)
+{
+ Metrics future_map;
+
+ if (ts->position_lock_style() == MusicTime) {
+ {
+ /* if we're snapping to a musical grid, set the pulse exactly instead of via the supplied frame. */
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+ double beat = beat_at_frame_locked (future_map, frame);
+
+ if (sub_num > 0) {
+ beat = floor (beat) + (floor (((beat - floor (beat)) * (double) sub_num) + 0.5) / sub_num);
+ } else if (sub_num == -2) {
+ /* snap to beat */
+ beat = floor (beat + 0.5);
+ }
+
+ double pulse = pulse_at_beat_locked (future_map, beat);
+
+ if (sub_num == -3) {
+ /* snap to bar */
+ pulse = floor (pulse + 0.5);
+ }
+
+ if (solve_map_pulse (future_map, tempo_copy, pulse)) {
+ solve_map_pulse (_metrics, ts, pulse);
+ recompute_meters (_metrics);
+ }
+ }
+
+ } else {
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+ if (solve_map_frame (future_map, tempo_copy, frame)) {
+ solve_map_frame (_metrics, ts, frame);
+ recompute_meters (_metrics);
+ }
+ }
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ MetricPositionChanged (); // Emit Signal
+}
+
+void
+TempoMap::gui_move_meter (MeterSection* ms, const framepos_t& frame)
+{
+ Metrics future_map;
+
+ if (ms->position_lock_style() == AudioTime) {
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
+
+ if (solve_map_frame (future_map, copy, frame)) {
+ solve_map_frame (_metrics, ms, frame);
+ recompute_tempos (_metrics);
+ }
+ }
+ } else {
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ MeterSection* copy = copy_metrics_and_point (_metrics, future_map, ms);
+
+ const double beat = beat_at_frame_locked (_metrics, frame);
+ const Timecode::BBT_Time bbt = bbt_at_beat_locked (_metrics, beat);
+
+ if (solve_map_bbt (future_map, copy, bbt)) {
+ solve_map_bbt (_metrics, ms, bbt);
+ recompute_tempos (_metrics);
+ }
+ }
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ MetricPositionChanged (); // Emit Signal
+}
+
+bool
+TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm)
+{
+ Metrics future_map;
+ bool can_solve = false;
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+ tempo_copy->set_beats_per_minute (bpm.beats_per_minute());
+ recompute_tempos (future_map);
+
+ if (check_solved (future_map)) {
+ ts->set_beats_per_minute (bpm.beats_per_minute());
+ recompute_map (_metrics);
+ can_solve = true;
+ }
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+ if (can_solve) {
+ MetricPositionChanged (); // Emit Signal
+ }
+ return can_solve;
+}
+
+void
+TempoMap::gui_dilate_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame, const double& pulse)
+{
+ /*
+ Ts (future prev_t) Tnext
+ | |
+ | [drag^] |
+ |----------|----------
+ e_f pulse(frame)
+ */
+
+ Metrics future_map;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+
+ if (!ts) {
+ return;
+ }
+
+ TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts);
+ TempoSection* prev_to_prev_t = 0;
+ const frameoffset_t fr_off = end_frame - frame;
+
+ if (prev_t && prev_t->pulse() > 0.0) {
+ prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_frame_locked (future_map, prev_t->frame() - 1));
+ }
+
+ TempoSection* next_t = 0;
+ for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) {
+ TempoSection* t = 0;
+ if ((t = dynamic_cast<TempoSection*> (*i)) != 0) {
+ if (t->frame() > ts->frame()) {
+ next_t = t;
+ break;
+ }
+ }
+ }
+ /* minimum allowed measurement distance in frames */
+ const framepos_t min_dframe = 2;
+
+ /* the change in frames is the result of changing the slope of at most 2 previous tempo sections.
+ constant to constant is straightforward, as the tempo prev to prev_t has constant slope.
+ */
+ double contribution = 0.0;
+
+ if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
+ }
+
+ const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+
+ const double start_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
+ const double end_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);