+
+
+}
+void
+TempoMap::gui_stretch_tempo_end (TempoSection* ts, const samplepos_t sample, const samplepos_t end_sample)
+{
+ /*
+ Ts (future prev_t) Tnext
+ | |
+ | [drag^] |
+ |----------|----------
+ e_f qn_beats(sample)
+ */
+
+ Metrics future_map;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+
+ if (!ts) {
+ return;
+ }
+
+ TempoSection* prev_t = copy_metrics_and_point (_metrics, future_map, ts);
+
+ if (!prev_t) {
+ return;
+ }
+
+ /* minimum allowed measurement distance in samples */
+ samplepos_t const min_dframe = 2;
+ double new_bpm;
+
+ if (sample > prev_t->sample() + min_dframe && end_sample > prev_t->sample() + min_dframe) {
+ new_bpm = prev_t->end_note_types_per_minute() * ((prev_t->sample() - sample)
+ / (double) (prev_t->sample() - end_sample));
+ } 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 samplepos_t sample, const samplepos_t end_sample)
+{
+ TempoSection* next_t = 0;
+ TempoSection* next_to_next_t = 0;
+ Metrics future_map;
+ bool can_solve = false;
+
+ /* minimum allowed measurement distance in samples */
+ samplepos_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 sampleoffset_t fr_off = end_sample - sample;
+
+ 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_sample (tempo_copy->sample() - 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->sample() - prev_to_prev_t->sample()) / (double) (next_t->sample() - prev_to_prev_t->sample());
+ }
+
+ const sampleoffset_t tempo_copy_sample_contribution = fr_off - (prev_contribution * (double) fr_off);
+
+
+ samplepos_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 (sample > tempo_copy->sample() + min_dframe && (sample + tempo_copy_sample_contribution) > tempo_copy->sample() + min_dframe) {
+ new_bpm = tempo_copy->note_types_per_minute() * ((sample - tempo_copy->sample())
+ / (double) (end_sample - tempo_copy->sample()));
+ } 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 (sample > tempo_copy->sample() + min_dframe && end_sample > tempo_copy->sample() + 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_sample_ratio = 1.0;
+ double copy_sample_ratio = 1.0;
+
+ if (next_to_next_t) {
+ next_sample_ratio = (next_to_next_t->minute() - old_next_minute) / (old_next_to_next_minute - old_next_minute);
+
+ copy_sample_ratio = ((old_tc_minute - next_t->minute()) / (double) (old_tc_minute - old_next_minute));
+ }
+
+ new_next_bpm = next_t->note_types_per_minute() * next_sample_ratio;
+ new_copy_end_bpm = tempo_copy->end_note_types_per_minute() * copy_sample_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 sample position of the musical position zero */
+samplepos_t
+TempoMap::music_origin ()
+{
+ Glib::Threads::RWLock::ReaderLock lm (lock);
+
+ return first_tempo().sample();
+}