+ if (!ts) {
+ return;
+ }
+
+ TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts);
+
+ if (!prev_t) {
+ return;
+ }
+
+ /* minimum allowed measurement distance in frames */
+ framepos_t const min_dframe = 2;
+ double new_bpm;
+
+ if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) {
+ new_bpm = prev_t->end_note_types_per_minute() * ((prev_t->frame() - frame)
+ / (double) (prev_t->frame() - end_frame));
+ } else {
+ new_bpm = prev_t->end_note_types_per_minute();
+ }
+
+ new_bpm = min (new_bpm, (double) 1000.0);
+
+ if (new_bpm < 0.5) {
+ goto out;
+ }
+
+ prev_t->set_end_note_types_per_minute (new_bpm);
+
+ TempoSection* next = 0;
+ if ((next = next_tempo_section_locked (future_map, prev_t)) != 0) {
+ if (next->clamped()) {
+ next->set_note_types_per_minute (prev_t->end_note_types_per_minute());
+ }
+ }
+
+ recompute_tempi (future_map);
+ recompute_meters (future_map);
+
+ if (check_solved (future_map)) {
+ ts->set_end_note_types_per_minute (new_bpm);
+
+ TempoSection* true_next = 0;
+ if ((true_next = next_tempo_section_locked (_metrics, ts)) != 0) {
+ if (true_next->clamped()) {
+ true_next->set_note_types_per_minute (ts->end_note_types_per_minute());
+ }
+ }
+
+ recompute_tempi (_metrics);
+ recompute_meters (_metrics);
+ }
+ }
+
+
+out:
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ MetricPositionChanged (PropertyChange ()); // Emit Signal
+}
+
+bool
+TempoMap::gui_twist_tempi (TempoSection* ts, const Tempo& bpm, const framepos_t frame, const framepos_t end_frame)
+{
+ TempoSection* next_t = 0;
+ TempoSection* next_to_next_t = 0;
+ Metrics future_map;
+ bool can_solve = false;
+
+ /* minimum allowed measurement distance in frames */
+ framepos_t const min_dframe = 2;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ if (!ts) {
+ return false;
+ }
+
+ TempoSection* tempo_copy = copy_metrics_and_point (_metrics, future_map, ts);
+ TempoSection* prev_to_prev_t = 0;
+ const frameoffset_t fr_off = end_frame - frame;
+
+ if (!tempo_copy) {
+ return false;
+ }
+
+ if (tempo_copy->pulse() > 0.0) {
+ prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_minute_locked (future_map, minute_at_frame (tempo_copy->frame() - 1)));
+ }
+
+ for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) {
+ if ((*i)->is_tempo() && (*i)->minute() > tempo_copy->minute()) {
+ next_t = static_cast<TempoSection*> (*i);
+ break;
+ }
+ }
+
+ if (!next_t) {
+ return false;
+ }
+
+ for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) {
+ if ((*i)->is_tempo() && (*i)->minute() > next_t->minute()) {
+ next_to_next_t = static_cast<TempoSection*> (*i);
+ break;
+ }
+ }
+
+ if (!next_to_next_t) {
+ return false;
+ }
+
+ double prev_contribution = 0.0;
+
+ if (next_t && prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ prev_contribution = (tempo_copy->frame() - prev_to_prev_t->frame()) / (double) (next_t->frame() - prev_to_prev_t->frame());
+ }
+
+ const frameoffset_t tempo_copy_frame_contribution = fr_off - (prev_contribution * (double) fr_off);
+
+
+ framepos_t old_tc_minute = tempo_copy->minute();
+ double old_next_minute = next_t->minute();
+ double old_next_to_next_minute = next_to_next_t->minute();
+
+ double new_bpm;
+ double new_next_bpm;
+ double new_copy_end_bpm;
+
+ if (frame > tempo_copy->frame() + min_dframe && (frame + tempo_copy_frame_contribution) > tempo_copy->frame() + min_dframe) {
+ new_bpm = tempo_copy->note_types_per_minute() * ((frame - tempo_copy->frame())
+ / (double) (end_frame - tempo_copy->frame()));
+ } else {
+ new_bpm = tempo_copy->note_types_per_minute();
+ }
+
+ /* don't clamp and proceed here.
+ testing has revealed that this can go negative,
+ which is an entirely different thing to just being too low.
+ */
+ if (new_bpm < 0.5) {
+ return false;
+ }
+
+ new_bpm = min (new_bpm, (double) 1000.0);
+
+ tempo_copy->set_note_types_per_minute (new_bpm);
+ if (tempo_copy->type() == TempoSection::Constant) {
+ tempo_copy->set_end_note_types_per_minute (new_bpm);
+ }
+
+ recompute_tempi (future_map);
+
+ if (check_solved (future_map)) {
+
+ if (!next_t) {
+ return false;
+ }
+
+ ts->set_note_types_per_minute (new_bpm);
+ if (ts->type() == TempoSection::Constant) {
+ ts->set_end_note_types_per_minute (new_bpm);
+ }
+
+ recompute_map (_metrics);
+
+ can_solve = true;
+ }
+
+ if (next_t->type() == TempoSection::Constant || next_t->c() == 0.0) {
+ if (frame > tempo_copy->frame() + min_dframe && end_frame > tempo_copy->frame() + min_dframe) {
+
+ new_next_bpm = next_t->note_types_per_minute() * ((next_to_next_t->minute() - old_next_minute)
+ / (double) ((old_next_to_next_minute) - old_next_minute));
+
+ } else {
+ new_next_bpm = next_t->note_types_per_minute();
+ }
+
+ next_t->set_note_types_per_minute (new_next_bpm);
+ recompute_tempi (future_map);
+
+ if (check_solved (future_map)) {
+ for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+ if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) {
+ next_t = static_cast<TempoSection*> (*i);
+ break;
+ }
+ }
+
+ if (!next_t) {
+ return false;
+ }
+ next_t->set_note_types_per_minute (new_next_bpm);
+ recompute_map (_metrics);
+ can_solve = true;
+ }
+ } else {
+ double next_frame_ratio = 1.0;
+ double copy_frame_ratio = 1.0;
+
+ if (next_to_next_t) {
+ next_frame_ratio = (next_to_next_t->minute() - old_next_minute) / (old_next_to_next_minute - old_next_minute);
+
+ copy_frame_ratio = ((old_tc_minute - next_t->minute()) / (double) (old_tc_minute - old_next_minute));
+ }
+
+ new_next_bpm = next_t->note_types_per_minute() * next_frame_ratio;
+ new_copy_end_bpm = tempo_copy->end_note_types_per_minute() * copy_frame_ratio;
+
+ tempo_copy->set_end_note_types_per_minute (new_copy_end_bpm);
+
+ if (next_t->clamped()) {
+ next_t->set_note_types_per_minute (new_copy_end_bpm);
+ } else {
+ next_t->set_note_types_per_minute (new_next_bpm);
+ }
+
+ recompute_tempi (future_map);
+
+ if (check_solved (future_map)) {
+ for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) {
+ if ((*i)->is_tempo() && (*i)->minute() > ts->minute()) {
+ next_t = static_cast<TempoSection*> (*i);
+ break;
+ }
+ }
+
+ if (!next_t) {
+ return false;
+ }
+
+ if (next_t->clamped()) {
+ next_t->set_note_types_per_minute (new_copy_end_bpm);
+ } else {
+ next_t->set_note_types_per_minute (new_next_bpm);
+ }
+
+ ts->set_end_note_types_per_minute (new_copy_end_bpm);
+ recompute_map (_metrics);
+ can_solve = true;
+ }
+ }
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ MetricPositionChanged (PropertyChange ()); // Emit Signal
+
+ return can_solve;
+}
+
+/** Returns the frame position of the musical position zero */
+framepos_t
+TempoMap::music_origin ()
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return first_tempo().frame();
+}
+
+/** Returns the exact bbt-based beat corresponding to the bar, beat or quarter note subdivision nearest to
+ * the supplied frame, possibly returning a negative value.
+ *
+ * @param frame The session frame position.
+ * @param sub_num The subdivision to use when rounding the beat.
+ * A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats.
+ * Positive integers indicate quarter note (non BBT) divisions.
+ * 0 indicates that the returned beat should not be rounded (equivalent to quarter_note_at_frame()).
+ * @return The beat position of the supplied frame.
+ *
+ * when working to a musical grid, the use of sub_nom indicates that
+ * the position should be interpreted musically.
+ *
+ * it effectively snaps to meter bars, meter beats or quarter note divisions
+ * (as per current gui convention) and returns a musical position independent of frame rate.
+ *
+ * If the supplied frame lies before the first meter, the return will be negative,
+ * in which case the returned beat uses the first meter (for BBT subdivisions) and
+ * the continuation of the tempo curve (backwards).
+ *
+ * This function is sensitive to tempo and meter.
+ */
+double
+TempoMap::exact_beat_at_frame (const framepos_t frame, const int32_t sub_num) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return exact_beat_at_frame_locked (_metrics, frame, sub_num);
+}
+
+double
+TempoMap::exact_beat_at_frame_locked (const Metrics& metrics, const framepos_t frame, const int32_t divisions) const
+{
+ return beat_at_pulse_locked (_metrics, exact_qn_at_frame_locked (metrics, frame, divisions) / 4.0);
+}
+
+/** Returns the exact quarter note corresponding to the bar, beat or quarter note subdivision nearest to
+ * the supplied frame, possibly returning a negative value.
+ *
+ * @param frame The session frame position.
+ * @param sub_num The subdivision to use when rounding the quarter note.
+ * A value of -1 indicates rounding to BBT bar. 1 indicates rounding to BBT beats.
+ * Positive integers indicate quarter note (non BBT) divisions.
+ * 0 indicates that the returned quarter note should not be rounded (equivalent to quarter_note_at_frame()).