+#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) {