diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc index 9a12ada070..af5b8a9241 100644 --- a/gtk2_ardour/editor_canvas_events.cc +++ b/gtk2_ardour/editor_canvas_events.cc @@ -1006,8 +1006,59 @@ Editor::canvas_videotl_bar_event (GdkEvent *event, ArdourCanvas::Item* item) } bool -Editor::canvas_tempo_marker_event (GdkEvent *event, ArdourCanvas::Item* item, TempoMarker* /*marker*/) +Editor::canvas_tempo_marker_event (GdkEvent *event, ArdourCanvas::Item* item, TempoMarker* marker) { + + if (event->type == GDK_SCROLL) { + + TempoMap& tmap (session()->tempo_map()); + bool handled = false; + double ntpm_adjust = 2.0; + XMLNode* before_state = &tmap.get_state(); + + if (ArdourKeyboard::modifier_state_contains (event->scroll.state, ArdourKeyboard::fine_adjust_modifier())) { + ntpm_adjust /= 10.0; + } + + switch (event->scroll.direction) { + + case GDK_SCROLL_UP: + + if (ArdourKeyboard::indicates_copy (event->scroll.state) && ArdourKeyboard::indicates_constraint (event->scroll.state)) { + tmap.gui_change_tempo (&marker->tempo(), marker->tempo().note_types_per_minute() + ntpm_adjust, false); + handled = true; + } else if (ArdourKeyboard::indicates_copy (event->scroll.state)) { + tmap.gui_change_tempo (&marker->tempo(), marker->tempo().end_note_types_per_minute() + ntpm_adjust,true); + handled = true; + } + + break; + + case GDK_SCROLL_DOWN: + + if (ArdourKeyboard::indicates_copy (event->scroll.state) && ArdourKeyboard::indicates_constraint (event->scroll.state)) { + tmap.gui_change_tempo (&marker->tempo(), marker->tempo().note_types_per_minute() - ntpm_adjust, false); + handled = true; + } else if (ArdourKeyboard::indicates_copy (event->scroll.state)) { + tmap.gui_change_tempo (&marker->tempo(), marker->tempo().end_note_types_per_minute() - ntpm_adjust, true); + handled = true; + } + + break; + + default: + break; + } + + if (handled) { + begin_reversible_command (_("Change Tempo")); + session()->add_command (new MementoCommand(tmap, before_state, &tmap.get_state())); + commit_reversible_command (); + } + + return handled; + } + return typed_event (item, event, TempoMarkerItem); } diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index ff3b032210..c5d7c68ad8 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -3362,7 +3362,8 @@ MeterMarkerDrag::aborted (bool moved) TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c) : Drag (e, i) , _copy (c) - , _grab_bpm (0.0) + , _grab_bpm (120.0, 4.0) + , _grab_qn (0.0) , before_state (0) { DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n"); @@ -3370,7 +3371,8 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c) _marker = reinterpret_cast (_item->get_data ("marker")); _real_section = &_marker->tempo(); _movable = !_real_section->initial(); - _grab_bpm = _real_section->note_types_per_minute(); + _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute()); + _grab_qn = _real_section->pulse() * 4.0; assert (_marker); } @@ -3397,6 +3399,7 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move) if (!_real_section->active()) { return; } + TempoMap& map (_editor->session()->tempo_map()); if (first_move) { @@ -3419,7 +3422,6 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move) swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME); _marker->hide(); - TempoMap& map (_editor->session()->tempo_map()); /* get current state */ before_state = &map.get_state(); @@ -3449,13 +3451,18 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move) } if (ArdourKeyboard::indicates_constraint (event->button.state)) { - /* use vertical movement to alter tempo .. should be log */ - double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0)); - stringstream strs; - _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type())); - strs << new_bpm; - show_verbose_cursor_text (strs.str()); + /** + adjust the end tempo of the previous ramped marker, or start and end tempo if constant. + depending on position lock style, this may or may not move the mark. + */ + framepos_t const pf = adjusted_current_frame (event, false); + map.gui_stretch_tempo_end (&map.tempo_section_at_frame (_real_section->frame() - 1), map.frame_at_quarter_note (_grab_qn), pf); + + ostringstream sstr; + sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_frame (_real_section->frame() - 1).end_note_types_per_minute() << "\n"; + sstr << "start: " << fixed << setprecision(3) << map.tempo_section_at_frame (_real_section->frame() - 1).note_types_per_minute(); + show_verbose_cursor_text (sstr.str()); } else if (_movable && !_real_section->locked_to_meter()) { framepos_t pf; @@ -3468,8 +3475,6 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move) pf = adjusted_current_frame (event); } - TempoMap& map (_editor->session()->tempo_map()); - /* snap to beat is 1, snap to bar is -1 (sorry) */ const int sub_num = _editor->get_grid_music_divisions (event->button.state); @@ -3534,10 +3539,9 @@ BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) _tempo = const_cast (&map.tempo_section_at_frame (raw_grab_frame())); ostringstream sstr; - sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n"; - sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute(); + sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute(); + sstr << "mouse: " << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n"; show_verbose_cursor_text (sstr.str()); - finished (event, false); } void @@ -3588,8 +3592,8 @@ BBTRulerDrag::motion (GdkEvent* event, bool first_move) _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf); } ostringstream sstr; - sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n"; - sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute(); + sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute(); + sstr << "mouse: " << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n"; show_verbose_cursor_text (sstr.str()); } @@ -3615,6 +3619,198 @@ BBTRulerDrag::aborted (bool moved) } } +TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i) + : Drag (e, i) + , _grab_qn (0.0) + , _grab_tempo (0.0) + , _tempo (0) + , before_state (0) +{ + DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n"); + +} + +void +TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) +{ + Drag::start_grab (event, cursor); + TempoMap& map (_editor->session()->tempo_map()); + _tempo = const_cast (&map.tempo_section_at_frame (raw_grab_frame())); + _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type()); + + ostringstream sstr; + sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n"; + sstr << ">" << fixed << setprecision(3) << _tempo->end_note_types_per_minute(); + show_verbose_cursor_text (sstr.str()); +} + +void +TempoTwistDrag::setup_pointer_frame_offset () +{ + TempoMap& map (_editor->session()->tempo_map()); + const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame())); + const uint32_t divisions = _editor->get_grid_beat_divisions (0); + double beat = 0.0; + + if (divisions > 0) { + beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions); + } else { + /* while it makes some sense for the user to determine the division to 'grab', + grabbing a bar often leads to confusing results wrt the actual tempo section being altered + and the result over steep tempo curves. Use sixteenths. + */ + beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4); + } + + _grab_qn = map.quarter_note_at_beat (beat); + + _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn); + +} + +void +TempoTwistDrag::motion (GdkEvent* event, bool first_move) +{ + TempoMap& map (_editor->session()->tempo_map()); + + if (first_move) { + /* get current state */ + before_state = &map.get_state(); + _editor->begin_reversible_command (_("twist tempo")); + } + + framepos_t pf; + + if (_editor->snap_musical()) { + pf = adjusted_current_frame (event, false); + } else { + pf = adjusted_current_frame (event); + } + + if (ArdourKeyboard::indicates_copy (event->button.state)) { + /* adjust this and the next tempi to match pointer frame */ + double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0)); + + _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.frame_at_quarter_note (_grab_qn), pf); + } + ostringstream sstr; + sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n"; + sstr << ">" << fixed << setprecision(3) << _tempo->end_note_types_per_minute(); + show_verbose_cursor_text (sstr.str()); +} + +void +TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred) +{ + if (!movement_occurred) { + return; + } + + TempoMap& map (_editor->session()->tempo_map()); + + XMLNode &after = map.get_state(); + _editor->session()->add_command(new MementoCommand(map, before_state, &after)); + _editor->commit_reversible_command (); +} + +void +TempoTwistDrag::aborted (bool moved) +{ + if (moved) { + _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version); + } +} + +TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i) + : Drag (e, i) + , _grab_qn (0.0) + , _tempo (0) + , before_state (0) +{ + DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n"); + +} + +void +TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) +{ + Drag::start_grab (event, cursor); + TempoMap& map (_editor->session()->tempo_map()); + _tempo = const_cast (&map.tempo_section_at_frame (raw_grab_frame())); + + ostringstream sstr; + sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n"; + sstr << "mouse: " << fixed << setprecision(3) << map.tempo_at_frame (raw_grab_frame()).note_types_per_minute(); + show_verbose_cursor_text (sstr.str()); +} + +void +TempoEndDrag::setup_pointer_frame_offset () +{ + TempoMap& map (_editor->session()->tempo_map()); + const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame())); + const uint32_t divisions = _editor->get_grid_beat_divisions (0); + double beat = 0.0; + + if (divisions > 0) { + beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions); + } else { + /* while it makes some sense for the user to determine the division to 'grab', + grabbing a bar often leads to confusing results wrt the actual tempo section being altered + and the result over steep tempo curves. Use sixteenths. + */ + beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4); + } + + _grab_qn = map.quarter_note_at_beat (beat); + + _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn); + +} + +void +TempoEndDrag::motion (GdkEvent* event, bool first_move) +{ + TempoMap& map (_editor->session()->tempo_map()); + + if (first_move) { + /* get current state */ + before_state = &map.get_state(); + _editor->begin_reversible_command (_("stretch end tempo")); + } + + + + framepos_t const pf = adjusted_current_frame (event, false); + map.gui_stretch_tempo_end (_tempo, map.frame_at_quarter_note (_grab_qn), pf); + + ostringstream sstr; + sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n"; + sstr << "mouse: " << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute(); + show_verbose_cursor_text (sstr.str()); +} + +void +TempoEndDrag::finished (GdkEvent* event, bool movement_occurred) +{ + if (!movement_occurred) { + return; + } + + TempoMap& map (_editor->session()->tempo_map()); + + XMLNode &after = map.get_state(); + _editor->session()->add_command(new MementoCommand(map, before_state, &after)); + _editor->commit_reversible_command (); +} + +void +TempoEndDrag::aborted (bool moved) +{ + if (moved) { + _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version); + } +} CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s) : Drag (e, &c.track_canvas_item(), false) diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index 711b5095c7..d190c54424 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -792,7 +792,8 @@ private: bool _copy; bool _movable; - double _grab_bpm; + ARDOUR::Tempo _grab_bpm; + double _grab_qn; XMLNode* before_state; }; @@ -823,6 +824,62 @@ private: XMLNode* before_state; }; +/** tempo curve twist drag */ +class TempoTwistDrag : public Drag +{ +public: + TempoTwistDrag (Editor *, ArdourCanvas::Item *); + + void start_grab (GdkEvent *, Gdk::Cursor* c = 0); + void motion (GdkEvent *, bool); + void finished (GdkEvent *, bool); + void aborted (bool); + + bool allow_vertical_autoscroll () const { + return false; + } + + bool y_movement_matters () const { + return true; + } + + void setup_pointer_frame_offset (); + +private: + double _grab_qn; + ARDOUR::Tempo _grab_tempo; + ARDOUR::TempoSection* _tempo; + XMLNode* before_state; +}; + + +/** tempo curve twist drag */ +class TempoEndDrag : public Drag +{ +public: + TempoEndDrag (Editor *, ArdourCanvas::Item *); + + void start_grab (GdkEvent *, Gdk::Cursor* c = 0); + void motion (GdkEvent *, bool); + void finished (GdkEvent *, bool); + void aborted (bool); + + bool allow_vertical_autoscroll () const { + return false; + } + + bool y_movement_matters () const { + return true; + } + + void setup_pointer_frame_offset (); + +private: + double _grab_qn; + ARDOUR::TempoSection* _tempo; + XMLNode* before_state; +}; + /** Drag of the playhead cursor */ class CursorDrag : public Drag { diff --git a/gtk2_ardour/editor_markers.cc b/gtk2_ardour/editor_markers.cc index bdc15f8dea..7918cec6cb 100644 --- a/gtk2_ardour/editor_markers.cc +++ b/gtk2_ardour/editor_markers.cc @@ -1415,11 +1415,11 @@ Editor::toggle_marker_lock_style () } else if (tm) { TempoSection* tsp = &tm->tempo(); - const Tempo tempo (tsp->note_types_per_minute(), tsp->note_type()); const double pulse = tsp->pulse(); const framepos_t frame = tsp->frame(); const TempoSection::Type type = tsp->type(); const PositionLockStyle pls = (tsp->position_lock_style() == AudioTime) ? MusicTime : AudioTime; + const Tempo tempo (tsp->note_types_per_minute(), tsp->note_type(), tsp->end_note_types_per_minute()); begin_reversible_command (_("change tempo lock style")); XMLNode &before = _session->tempo_map().get_state(); @@ -1442,7 +1442,7 @@ Editor::toggle_tempo_type () if (tm) { TempoSection* tsp = &tm->tempo(); - const Tempo tempo (tsp->note_types_per_minute(), tsp->note_type()); + const Tempo tempo (tsp->note_types_per_minute(), tsp->note_type(), tsp->end_note_types_per_minute()); const double pulse = tsp->pulse(); const framepos_t frame = tsp->frame(); const TempoSection::Type type = (tsp->type() == TempoSection::Ramp) ? TempoSection::Constant : TempoSection::Ramp; diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index d5bf07bd78..f22d49a7a9 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -726,8 +726,12 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier) && !ArdourKeyboard::indicates_constraint (event->button.state)) { _drags->set (new CursorDrag (this, *playhead_cursor, false), event); + } else if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) { + _drags->set (new TempoTwistDrag (this, item), event); } else if (ArdourKeyboard::indicates_constraint (event->button.state)) { _drags->set (new BBTRulerDrag (this, item), event); + } else if (ArdourKeyboard::indicates_copy (event->button.state)) { + _drags->set (new TempoEndDrag ( this, item), event); } return true; break; diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index f5bf0ffcb7..21dcce19fd 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -6710,11 +6710,11 @@ Editor::define_one_bar (framepos_t start, framepos_t end) XMLNode& before (_session->tempo_map().get_state()); if (do_global) { - _session->tempo_map().change_initial_tempo (beats_per_minute, t.note_type()); + _session->tempo_map().change_initial_tempo (beats_per_minute, t.note_type(), t.end_note_types_per_minute()); } else if (t.frame() == start) { - _session->tempo_map().change_existing_tempo_at (start, beats_per_minute, t.note_type()); + _session->tempo_map().change_existing_tempo_at (start, beats_per_minute, t.note_type(), t.end_note_types_per_minute()); } else { - const Tempo tempo (beats_per_minute, t.note_type()); + const Tempo tempo (beats_per_minute, t.note_type(), t.end_note_types_per_minute()); _session->tempo_map().add_tempo (tempo, 0.0, start, TempoSection::Constant, AudioTime); } diff --git a/gtk2_ardour/editor_tempodisplay.cc b/gtk2_ardour/editor_tempodisplay.cc index b863cb9901..162179a7b9 100644 --- a/gtk2_ardour/editor_tempodisplay.cc +++ b/gtk2_ardour/editor_tempodisplay.cc @@ -86,6 +86,7 @@ Editor::draw_metric_marks (const Metrics& metrics) char buf[64]; double max_tempo = 0.0; double min_tempo = DBL_MAX; + const TempoSection *prev_ts = 0; remove_metric_marks (); // also clears tempo curves @@ -110,10 +111,18 @@ Editor::draw_metric_marks (const Metrics& metrics) } max_tempo = max (max_tempo, ts->note_types_per_minute()); + max_tempo = max (max_tempo, ts->end_note_types_per_minute()); min_tempo = min (min_tempo, ts->note_types_per_minute()); + min_tempo = min (min_tempo, ts->end_note_types_per_minute()); + uint32_t tc_color = UIConfiguration::instance().color ("tempo curve"); - tempo_curves.push_back (new TempoCurve (*this, *tempo_group, UIConfiguration::instance().color ("tempo curve"), + if (prev_ts && abs (prev_ts->end_note_types_per_minute() - ts->note_types_per_minute()) < 2) { + tc_color = UIConfiguration::instance().color ("location loop"); + } + + tempo_curves.push_back (new TempoCurve (*this, *tempo_group, tc_color, *(const_cast(ts)), ts->frame(), false)); + if (ts->position_lock_style() == MusicTime) { metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker music"), buf, *(const_cast(ts)))); @@ -122,6 +131,8 @@ Editor::draw_metric_marks (const Metrics& metrics) *(const_cast(ts)))); } + prev_ts = ts; + } } @@ -224,7 +235,9 @@ Editor::tempometric_position_changed (const PropertyChange& /*ignored*/) tempo_marker->set_name (buf); max_tempo = max (max_tempo, ts->note_types_per_minute()); + max_tempo = max (max_tempo, ts->end_note_types_per_minute()); min_tempo = min (min_tempo, ts->note_types_per_minute()); + min_tempo = min (min_tempo, ts->end_note_types_per_minute()); } } if ((meter_marker = dynamic_cast (*x)) != 0) { @@ -250,6 +263,11 @@ Editor::tempometric_position_changed (const PropertyChange& /*ignored*/) (*x)->set_min_tempo (min_tempo); ++tmp; if (tmp != tempo_curves.end()) { + if (abs ((*tmp)->tempo().note_types_per_minute() - (*x)->tempo().end_note_types_per_minute()) < 2) { + (*tmp)->set_color_rgba (UIConfiguration::instance().color ("location loop")); + } else { + (*tmp)->set_color_rgba (UIConfiguration::instance().color ("tempo curve")); + } (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame()); } else { (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX); diff --git a/gtk2_ardour/tempo_curve.cc b/gtk2_ardour/tempo_curve.cc index 75ea028933..eb187139db 100644 --- a/gtk2_ardour/tempo_curve.cc +++ b/gtk2_ardour/tempo_curve.cc @@ -175,7 +175,7 @@ void TempoCurve::set_color_rgba (uint32_t c) { _color = c; - _curve->set_fill_color (UIConfiguration::instance().color_mod ("tempo curve", "selection rect")); + _curve->set_fill_color (UIConfiguration::instance().color_mod (c, "selection rect")); _curve->set_outline_color (_color); } diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h index dd2d96e94a..c444788fdc 100644 --- a/libs/ardour/ardour/tempo.h +++ b/libs/ardour/ardour/tempo.h @@ -54,7 +54,9 @@ class LIBARDOUR_API Tempo { * @param type Note Type (default `4': quarter note) */ Tempo (double npm, double type=4.0) // defaulting to quarter note - : _note_types_per_minute (npm), _note_type(type) {} + : _note_types_per_minute (npm), _note_type (type), _end_note_types_per_minute (npm) {} + Tempo (double start_npm, double type, double end_npm) + : _note_types_per_minute (start_npm), _note_type (type), _end_note_types_per_minute (end_npm) {} double note_types_per_minute () const { return _note_types_per_minute; } double note_types_per_minute (double note_type) const { return (_note_types_per_minute / _note_type) * note_type; } @@ -63,6 +65,14 @@ class LIBARDOUR_API Tempo { double quarter_notes_per_minute () const { return note_types_per_minute (4.0); } double pulses_per_minute () const { return note_types_per_minute (1.0); } + + double end_note_types_per_minute () const { return _end_note_types_per_minute; } + double end_note_types_per_minute (double note_type) const { return (_end_note_types_per_minute / _note_type) * note_type; } + void set_end_note_types_per_minute (double npm) { _end_note_types_per_minute = npm; } + + double end_quarter_notes_per_minute () const { return end_note_types_per_minute (4.0); } + double end_pulses_per_minute () const { return end_note_types_per_minute (1.0); } + /** audio samples per note type. * if you want an instantaneous value for this, use TempoMap::frames_per_quarter_note_at() instead. * @param sr samplerate @@ -81,6 +91,7 @@ class LIBARDOUR_API Tempo { protected: double _note_types_per_minute; double _note_type; + double _end_note_types_per_minute; }; /** Meter, or time signature (beats per bar, and which note type is a beat). */ @@ -189,8 +200,8 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { Constant, }; - TempoSection (const double& pulse, const double& minute, double qpm, double note_type, Type tempo_type, PositionLockStyle pls, framecnt_t sr) - : MetricSection (pulse, minute, pls, true, sr), Tempo (qpm, note_type), _type (tempo_type), _c (0.0), _active (true), _locked_to_meter (false) {} + TempoSection (const double& pulse, const double& minute, Tempo tempo, Type tempo_type, PositionLockStyle pls, framecnt_t sr) + : MetricSection (pulse, minute, pls, true, sr), Tempo (tempo), _type (tempo_type), _c (0.0), _active (true), _locked_to_meter (false) {} TempoSection (const XMLNode&, const framecnt_t sample_rate); @@ -226,6 +237,7 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { framepos_t frame_at_pulse (const double& pulse) const; Timecode::BBT_Time legacy_bbt () { return _legacy_bbt; } + bool legacy_end () { return _legacy_end; } private: @@ -258,6 +270,7 @@ class LIBARDOUR_API TempoSection : public MetricSection, public Tempo { bool _active; bool _locked_to_meter; Timecode::BBT_Time _legacy_bbt; + bool _legacy_end; }; typedef std::list Metrics; @@ -329,7 +342,7 @@ class LIBARDOUR_API TempoMap : public PBD::StatefulDestructible BBTPoint (const MeterSection& m, const Tempo& t, framepos_t f, uint32_t b, uint32_t e, double func_c) - : frame (f), meter (m.divisions_per_bar(), m.note_divisor()), tempo (t.note_types_per_minute(), t.note_type()), c (func_c), bar (b), beat (e) {} + : frame (f), meter (m.divisions_per_bar(), m.note_divisor()), tempo (t.note_types_per_minute(), t.note_type(), t.end_note_types_per_minute()), c (func_c), bar (b), beat (e) {} Timecode::BBT_Time bbt() const { return Timecode::BBT_Time (bar, beat, 0); } operator Timecode::BBT_Time() const { return bbt(); } @@ -407,8 +420,8 @@ class LIBARDOUR_API TempoMap : public PBD::StatefulDestructible Metrics::const_iterator metrics_end() { return _metrics.end(); } - void change_existing_tempo_at (framepos_t, double bpm, double note_type); - void change_initial_tempo (double bpm, double note_type); + void change_existing_tempo_at (framepos_t, double bpm, double note_type, double end_ntpm); + void change_initial_tempo (double ntpm, double note_type, double end_ntpm); void insert_time (framepos_t, framecnt_t); bool remove_time (framepos_t where, framecnt_t amount); //returns true if anything was moved @@ -488,14 +501,18 @@ class LIBARDOUR_API TempoMap : public PBD::StatefulDestructible void gui_set_tempo_position (TempoSection*, const framepos_t& frame, const int& sub_num); void gui_set_meter_position (MeterSection*, const framepos_t& frame); - bool gui_change_tempo (TempoSection*, const Tempo& bpm); - void gui_stretch_tempo (TempoSection* tempo, const framepos_t& frame, const framepos_t& end_frame); + bool gui_change_tempo (TempoSection*, const Tempo& bpm, bool change_end); + void gui_stretch_tempo (TempoSection* tempo, const framepos_t frame, const framepos_t end_frame); + void gui_stretch_tempo_end (TempoSection* tempo, const framepos_t frame, const framepos_t end_frame); + bool gui_twist_tempi (TempoSection* first, const Tempo& bpm, const framepos_t frame, const framepos_t end_frame); std::pair predict_tempo_position (TempoSection* section, const Timecode::BBT_Time& bbt); bool can_solve_bbt (TempoSection* section, const Timecode::BBT_Time& bbt); + std::pair ntpm_minmax () { return _ntpm_minmax; } PBD::Signal1 MetricPositionChanged; void fix_legacy_session(); + void fix_legacy_end_session(); private: diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index 84a2610a44..ac94644c3e 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -1477,7 +1477,7 @@ LuaBindings::common (lua_State* L) .endClass () .beginClass ("Tempo") - .addConstructor () + .addConstructor () .addFunction ("note_type", &Tempo::note_type) .addFunction ("note_types_per_minute", (double (Tempo::*)() const)&Tempo::note_types_per_minute) .addFunction ("quarter_notes_per_minute", &Tempo::quarter_notes_per_minute) diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc index 4f3899078f..6c0032a673 100644 --- a/libs/ardour/tempo.cc +++ b/libs/ardour/tempo.cc @@ -45,7 +45,7 @@ using Timecode::BBT_Time; /* _default tempo is 4/4 qtr=120 */ Meter TempoMap::_default_meter (4.0, 4.0); -Tempo TempoMap::_default_tempo (120.0, 4.0); +Tempo TempoMap::_default_tempo (120.0, 4.0, 120.0); framepos_t MetricSection::frame_at_minute (const double& time) const @@ -71,7 +71,7 @@ Meter::frames_per_grid (const Tempo& tempo, framecnt_t sr) const The return value IS NOT interpretable in terms of "beats". */ - return (60.0 * sr) / (tempo.note_types_per_minute() * (_note_type/tempo.note_type())); + return (60.0 * sr) / (tempo.note_types_per_minute() * (_note_type / tempo.note_type())); } double @@ -90,6 +90,7 @@ TempoSection::TempoSection (const XMLNode& node, framecnt_t sample_rate) , _c (0.0) , _active (true) , _locked_to_meter (false) + , _legacy_end (false) { XMLProperty const * prop; LocaleGuard lg; @@ -143,7 +144,17 @@ TempoSection::TempoSection (const XMLNode& node, framecnt_t sample_rate) if (sscanf (prop->value().c_str(), "%lf", &_note_type) != 1 || _note_type < 1.0) { error << _("TempoSection XML node has an illegal \"note-type\" value") << endmsg; throw failed_constructor(); + } } + + /* XX replace old end-beats-per-minute name with note-types-per-minute */ + if ((prop = node.property ("end-beats-per-minute")) != 0) { + if (sscanf (prop->value().c_str(), "%lf", &_end_note_types_per_minute) != 1 || _end_note_types_per_minute < 0.0) { + info << _("TempoSection XML node has an illegal \"in-beats-per-minute\" value") << endmsg; + //throw failed_constructor(); + _end_note_types_per_minute = _note_types_per_minute; + _legacy_end = true; + } } if ((prop = node.property ("movable")) == 0) { @@ -207,6 +218,8 @@ TempoSection::get_state() const root->add_property ("beats-per-minute", buf); snprintf (buf, sizeof (buf), "%lf", _note_type); root->add_property ("note-type", buf); + snprintf (buf, sizeof (buf), "%lf", _end_note_types_per_minute); + root->add_property ("end-beats-per-minute", buf); snprintf (buf, sizeof (buf), "%s", !initial()?"yes":"no"); root->add_property ("movable", buf); snprintf (buf, sizeof (buf), "%s", active()?"yes":"no"); @@ -234,7 +247,7 @@ TempoSection::tempo_at_minute (const double& m) const return Tempo (note_types_per_minute(), note_type()); } - return Tempo (_tempo_at_time (m - minute()), _note_type); + return Tempo (_tempo_at_time (m - minute()), _note_type, _end_note_types_per_minute); } /** returns the session relative minute where the supplied tempo in note types per minute occurs. @@ -274,7 +287,7 @@ TempoSection::tempo_at_pulse (const double& p) const return Tempo (note_types_per_minute(), note_type()); } - return Tempo (_tempo_at_pulse (p - pulse()), _note_type); + return Tempo (_tempo_at_pulse (p - pulse()), _note_type, _end_note_types_per_minute); } /** returns the whole-note pulse where a tempo in note types per minute occurs. @@ -748,7 +761,7 @@ TempoMap::TempoMap (framecnt_t fr) _frame_rate = fr; BBT_Time start (1, 1, 0); - TempoSection *t = new TempoSection (0.0, 0.0, _default_tempo.note_types_per_minute(), _default_tempo.note_type(), TempoSection::Ramp, AudioTime, fr); + TempoSection *t = new TempoSection (0.0, 0.0, _default_tempo, TempoSection::Ramp, AudioTime, fr); MeterSection *m = new MeterSection (0.0, 0.0, 0.0, start, _default_meter.divisions_per_bar(), _default_meter.note_divisor(), AudioTime, fr); t->set_initial (true); @@ -1075,11 +1088,29 @@ TempoMap::add_tempo (const Tempo& tempo, const double& pulse, const framepos_t& } TempoSection* ts = 0; + TempoSection* prev_tempo = 0; { Glib::Threads::RWLock::WriterLock lm (lock); ts = add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true); - } + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + if ((*i)->is_tempo()) { + TempoSection* const this_t = static_cast (*i); + + bool const ipm = ts->position_lock_style() == MusicTime; + bool const lm = ts->locked_to_meter(); + if ((ipm && this_t->pulse() == ts->pulse()) || (!ipm && this_t->frame() == ts->frame()) + || (lm && this_t->pulse() == ts->pulse())) { + if (prev_tempo && prev_tempo->type() == TempoSection::Ramp) { + prev_tempo->set_end_note_types_per_minute (ts->note_types_per_minute()); + } + break; + } + prev_tempo = this_t; + } + } + recompute_map (_metrics); + } PropertyChanged (PropertyChange ()); @@ -1095,6 +1126,7 @@ TempoMap::replace_tempo (TempoSection& ts, const Tempo& tempo, const double& pul } const bool locked_to_meter = ts.locked_to_meter(); + TempoSection* new_ts = 0; { Glib::Threads::RWLock::WriterLock lm (lock); @@ -1109,8 +1141,31 @@ TempoMap::replace_tempo (TempoSection& ts, const Tempo& tempo, const double& pul } } else { remove_tempo_locked (ts); - add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true, locked_to_meter); + new_ts = add_tempo_locked (tempo, pulse, minute_at_frame (frame), type, pls, true, locked_to_meter); + + if (new_ts && new_ts->type() == TempoSection::Constant) { + new_ts->set_end_note_types_per_minute (new_ts->note_types_per_minute()); + } else { + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + + if ((*i)->is_tempo()) { + TempoSection* const this_t = static_cast (*i); + + bool const ipm = new_ts->position_lock_style() == MusicTime; + bool const lm = new_ts->locked_to_meter(); + if ((ipm && this_t->pulse() > new_ts->pulse()) || (!ipm && this_t->frame() > new_ts->frame()) + || (lm && this_t->pulse() > new_ts->pulse())) { + //if (prev_tempo && prev_tempo->type() == TempoSection::Ramp) { + new_ts->set_end_note_types_per_minute (this_t->note_types_per_minute()); + //} + break; + } + //prev_tempo = this_t; + } + } + } } + } else { first.set_type (type); first.set_pulse (0.0); @@ -1120,9 +1175,9 @@ TempoMap::replace_tempo (TempoSection& ts, const Tempo& tempo, const double& pul { /* cannot move the first tempo section */ *static_cast(&first) = tempo; - recompute_map (_metrics); } } + recompute_map (_metrics); } PropertyChanged (PropertyChange ()); @@ -1132,25 +1187,20 @@ TempoSection* TempoMap::add_tempo_locked (const Tempo& tempo, double pulse, double minute , TempoSection::Type type, PositionLockStyle pls, bool recompute, bool locked_to_meter) { - TempoSection* t = new TempoSection (pulse, minute, tempo.note_types_per_minute(), tempo.note_type(), type, pls, _frame_rate); + TempoSection* t = new TempoSection (pulse, minute, tempo, type, pls, _frame_rate); t->set_locked_to_meter (locked_to_meter); - bool solved = false; do_insert (t); if (recompute) { if (pls == AudioTime) { - solved = solve_map_minute (_metrics, t, t->minute()); + solve_map_minute (_metrics, t, t->minute()); } else { - solved = solve_map_pulse (_metrics, t, t->pulse()); + solve_map_pulse (_metrics, t, t->pulse()); } recompute_meters (_metrics); } - if (!solved && recompute) { - recompute_map (_metrics); - } - return t; } @@ -1261,9 +1311,9 @@ TempoMap::add_meter_locked (const Meter& meter, double beat, const BBT_Time& whe } void -TempoMap::change_initial_tempo (double note_types_per_minute, double note_type) +TempoMap::change_initial_tempo (double note_types_per_minute, double note_type, double end_note_types_per_minute) { - Tempo newtempo (note_types_per_minute, note_type); + Tempo newtempo (note_types_per_minute, note_type, end_note_types_per_minute); TempoSection* t; for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { @@ -1283,9 +1333,9 @@ TempoMap::change_initial_tempo (double note_types_per_minute, double note_type) } void -TempoMap::change_existing_tempo_at (framepos_t where, double note_types_per_minute, double note_type) +TempoMap::change_existing_tempo_at (framepos_t where, double note_types_per_minute, double note_type, double end_ntpm) { - Tempo newtempo (note_types_per_minute, note_type); + Tempo newtempo (note_types_per_minute, note_type, end_ntpm); TempoSection* prev; TempoSection* first; @@ -1433,14 +1483,14 @@ TempoMap::recompute_tempi (Metrics& metrics) } if (prev_t) { if (t->position_lock_style() == AudioTime) { - prev_t->set_c (prev_t->compute_c_minute (t->note_types_per_minute(), t->minute())); + prev_t->set_c (prev_t->compute_c_minute (prev_t->end_note_types_per_minute(), t->minute())); if (!t->locked_to_meter()) { - t->set_pulse (prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute())); + t->set_pulse (prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute())); } } else { - prev_t->set_c (prev_t->compute_c_pulse (t->note_types_per_minute(), t->pulse())); - t->set_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())); + prev_t->set_c (prev_t->compute_c_pulse (prev_t->end_note_types_per_minute(), t->pulse())); + t->set_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())); } } @@ -1745,7 +1795,7 @@ TempoMap::tempo_at_minute_locked (const Metrics& metrics, const double& minute) } } - return Tempo (prev_t->note_types_per_minute(), prev_t->note_type()); + return Tempo (prev_t->note_types_per_minute(), prev_t->note_type(), prev_t->end_note_types_per_minute()); } /** returns the frame at which the supplied tempo occurs, or @@ -1819,7 +1869,7 @@ TempoMap::tempo_at_pulse_locked (const Metrics& metrics, const double& pulse) co } } - return Tempo (prev_t->note_types_per_minute(), prev_t->note_type()); + return Tempo (prev_t->note_types_per_minute(), prev_t->note_type(), prev_t->end_note_types_per_minute()); } double @@ -2570,7 +2620,7 @@ TempoMap::check_solved (const Metrics& metrics) const } /* precision check ensures tempo and frames align.*/ - if (t->frame() != frame_at_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse()))) { + if (t->frame() != frame_at_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse()))) { if (!t->locked_to_meter()) { return false; } @@ -2681,20 +2731,20 @@ TempoMap::solve_map_minute (Metrics& imaginary, TempoSection* section, const dou if (prev_t && !section_prev && ((sml && tlm && t->pulse() > section->pulse()) || (!tlm && t->minute() > minute))) { section_prev = prev_t; - section_prev->set_c (section_prev->compute_c_minute (section->note_types_per_minute(), minute)); + section_prev->set_c (section_prev->compute_c_minute (section_prev->end_note_types_per_minute(), minute)); if (!section->locked_to_meter()) { - section->set_pulse (section_prev->pulse_at_ntpm (section->note_types_per_minute(), minute)); + section->set_pulse (section_prev->pulse_at_ntpm (section_prev->end_note_types_per_minute(), minute)); } prev_t = section; } if (t->position_lock_style() == MusicTime) { - prev_t->set_c (prev_t->compute_c_pulse (t->note_types_per_minute(), t->pulse())); - t->set_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())); + prev_t->set_c (prev_t->compute_c_pulse (prev_t->end_note_types_per_minute(), t->pulse())); + t->set_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())); } else { - prev_t->set_c (prev_t->compute_c_minute (t->note_types_per_minute(), t->minute())); + prev_t->set_c (prev_t->compute_c_minute (prev_t->end_note_types_per_minute(), t->minute())); if (!t->locked_to_meter()) { - t->set_pulse (prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute())); + t->set_pulse (prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute())); } } } @@ -2751,12 +2801,12 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub } if (t->position_lock_style() == MusicTime) { - prev_t->set_c (prev_t->compute_c_pulse (t->note_types_per_minute(), t->pulse())); - t->set_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())); + prev_t->set_c (prev_t->compute_c_pulse (prev_t->end_note_types_per_minute(), t->pulse())); + t->set_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())); } else { - prev_t->set_c (prev_t->compute_c_minute (t->note_types_per_minute(), t->minute())); + prev_t->set_c (prev_t->compute_c_minute (prev_t->end_note_types_per_minute(), t->minute())); if (!t->locked_to_meter()) { - t->set_pulse (prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute())); + t->set_pulse (prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute())); } } } @@ -2765,8 +2815,8 @@ TempoMap::solve_map_pulse (Metrics& imaginary, TempoSection* section, const doub } if (section_prev) { - section_prev->set_c (section_prev->compute_c_pulse (section->note_types_per_minute(), pulse)); - section->set_minute (section_prev->minute_at_ntpm (section->note_types_per_minute(), pulse)); + section_prev->set_c (section_prev->compute_c_pulse (section_prev->end_note_types_per_minute(), pulse)); + section->set_minute (section_prev->minute_at_ntpm (section_prev->end_note_types_per_minute(), pulse)); } #if (0) @@ -3338,18 +3388,35 @@ TempoMap::gui_set_meter_position (MeterSection* ms, const framepos_t& frame) } bool -TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm) +TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm, bool change_end) { 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_note_types_per_minute (bpm.note_types_per_minute()); + + if (change_end && tempo_copy->type() == TempoSection::Constant) { + tempo_copy->set_end_note_types_per_minute (bpm.note_types_per_minute()); + tempo_copy->set_note_types_per_minute (bpm.note_types_per_minute()); + } else if (change_end) { + tempo_copy->set_end_note_types_per_minute (bpm.note_types_per_minute()); + } else { + tempo_copy->set_note_types_per_minute (bpm.note_types_per_minute()); + } + recompute_tempi (future_map); if (check_solved (future_map)) { - ts->set_note_types_per_minute (bpm.note_types_per_minute()); + if (change_end && ts->type() == TempoSection::Constant) { + ts->set_end_note_types_per_minute (bpm.note_types_per_minute()); + ts->set_note_types_per_minute (bpm.note_types_per_minute()); + } else if (change_end) { + ts->set_end_note_types_per_minute (bpm.note_types_per_minute()); + } else { + ts->set_note_types_per_minute (bpm.note_types_per_minute()); + } + recompute_map (_metrics); can_solve = true; } @@ -3363,11 +3430,12 @@ TempoMap::gui_change_tempo (TempoSection* ts, const Tempo& bpm) if (can_solve) { MetricPositionChanged (PropertyChange ()); // Emit Signal } + return can_solve; } void -TempoMap::gui_stretch_tempo (TempoSection* ts, const framepos_t& frame, const framepos_t& end_frame) +TempoMap::gui_stretch_tempo (TempoSection* ts, const framepos_t frame, const framepos_t end_frame) { /* Ts (future prev_t) Tnext @@ -3387,105 +3455,237 @@ TempoMap::gui_stretch_tempo (TempoSection* ts, const framepos_t& frame, const fr } 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; - assert (prev_t); - - if (prev_t->pulse() > 0.0) { - prev_to_prev_t = const_cast(&tempo_section_at_minute_locked (future_map, minute_at_frame (prev_t->frame() - 1))); + if (!prev_t) { + return; } - TempoSection* next_t = 0; - for (Metrics::iterator i = future_map.begin(); i != future_map.end(); ++i) { - TempoSection* t = 0; - if ((*i)->is_tempo()) { - t = static_cast (*i); - 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_minute (minute_at_frame (frame)); - const double end_pulse = prev_t->pulse_at_minute (minute_at_frame (end_frame)); + framepos_t const min_dframe = 2; double new_bpm; - if (prev_t->type() == TempoSection::Constant || prev_t->c() == 0.0) { + if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) { - if (prev_t->position_lock_style() == MusicTime) { - if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { - if (frame > prev_to_prev_t->frame() + min_dframe && (frame + prev_t_frame_contribution) > prev_to_prev_t->frame() + min_dframe) { - - new_bpm = prev_t->note_types_per_minute() * ((frame - prev_to_prev_t->frame()) - / (double) ((frame + prev_t_frame_contribution) - prev_to_prev_t->frame())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } else { - /* prev to prev is irrelevant */ - - if (start_pulse > prev_t->pulse() && end_pulse > prev_t->pulse()) { - new_bpm = prev_t->note_types_per_minute() * ((start_pulse - prev_t->pulse()) / (end_pulse - prev_t->pulse())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } - } else { - /* AudioTime */ - if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) { - if (frame > prev_to_prev_t->frame() + min_dframe && end_frame > prev_to_prev_t->frame() + min_dframe) { - - new_bpm = prev_t->note_types_per_minute() * ((frame - prev_to_prev_t->frame()) - / (double) ((end_frame) - prev_to_prev_t->frame())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } else { - /* prev_to_prev_t is irrelevant */ - - if (frame > prev_t->frame() + min_dframe && end_frame > prev_t->frame() + min_dframe) { - new_bpm = prev_t->note_types_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame())); - } else { - new_bpm = prev_t->note_types_per_minute(); - } - } - } + new_bpm = prev_t->note_types_per_minute() * ((frame - prev_t->frame()) + / (double) (end_frame - prev_t->frame())); } else { + new_bpm = prev_t->note_types_per_minute(); + } - double frame_ratio = 1.0; - double pulse_ratio = 1.0; - const double pulse_pos = frame; + std::cout << "new bpm : " << new_bpm << std::endl; + new_bpm = min (new_bpm, (double) 1000.0); - if (prev_to_prev_t) { - if (pulse_pos > prev_to_prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_to_prev_t->frame() + min_dframe) { - frame_ratio = (((pulse_pos - fr_off) - prev_to_prev_t->frame()) / (double) ((pulse_pos) - prev_to_prev_t->frame())); - } - if (end_pulse > prev_to_prev_t->pulse() && start_pulse > prev_to_prev_t->pulse()) { - pulse_ratio = ((start_pulse - prev_to_prev_t->pulse()) / (end_pulse - prev_to_prev_t->pulse())); - } + /* 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) { + goto out; + } + + if (prev_t && prev_t->type() == TempoSection::Ramp) { + prev_t->set_note_types_per_minute (new_bpm); + } else { + prev_t->set_end_note_types_per_minute (new_bpm); + prev_t->set_note_types_per_minute (new_bpm); + } + + recompute_tempi (future_map); + recompute_meters (future_map); + + if (check_solved (future_map)) { + if (prev_t && prev_t->type() == TempoSection::Ramp) { + ts->set_note_types_per_minute (new_bpm); } else { - if (pulse_pos > prev_t->frame() + min_dframe && (pulse_pos - fr_off) > prev_t->frame() + min_dframe) { - frame_ratio = (((pulse_pos - fr_off) - prev_t->frame()) / (double) ((pulse_pos) - prev_t->frame())); - } - pulse_ratio = (start_pulse / end_pulse); + ts->set_end_note_types_per_minute (new_bpm); + ts->set_note_types_per_minute (new_bpm); } - new_bpm = prev_t->note_types_per_minute() * (pulse_ratio * frame_ratio); + recompute_tempi (_metrics); + recompute_meters (_metrics); + } + } + + MetricPositionChanged (PropertyChange ()); // Emit Signal + +out: + Metrics::const_iterator d = future_map.begin(); + while (d != future_map.end()) { + delete (*d); + ++d; + } + +} +void +TempoMap::gui_stretch_tempo_end (TempoSection* ts, const framepos_t frame, const framepos_t end_frame) +{ + /* + Ts (future prev_t) Tnext + | | + | [drag^] | + |----------|---------- + e_f qn_beats(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* next_t = 0; + for (Metrics::const_iterator i = future_map.begin(); i != future_map.end(); ++i) { + if ((*i)->is_tempo() && (*i)->minute() > prev_t->minute()) { + next_t = static_cast (*i); + break; + } + } + + if (!next_t) { + return; + } +*/ + 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() * ((frame - prev_t->frame()) + / (double) (end_frame - prev_t->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; + } + + if (prev_t && prev_t->type() == TempoSection::Ramp) { + prev_t->set_end_note_types_per_minute (new_bpm); + } else { + if (prev_t) { + prev_t->set_note_types_per_minute (new_bpm); + prev_t->set_end_note_types_per_minute (prev_t->note_types_per_minute()); + } + } + + recompute_tempi (future_map); + recompute_meters (future_map); + + if (check_solved (future_map)) { + if (prev_t && prev_t->type() == TempoSection::Ramp) { + ts->set_end_note_types_per_minute (new_bpm); + } else { + if (prev_t) { + ts->set_end_note_types_per_minute (prev_t->note_types_per_minute()); + ts->set_note_types_per_minute (prev_t->note_types_per_minute()); + } + } + recompute_tempi (_metrics); + recompute_meters (_metrics); + } + } + + MetricPositionChanged (PropertyChange ()); // Emit Signal + +out: + Metrics::const_iterator d = future_map.begin(); + while (d != future_map.end()) { + delete (*d); + ++d; + } + +} +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(&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 (*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 (*i); + break; + } + } + + if (!next_to_next_t) { + std::cout << "no next to next t" << std::endl; + 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_pos = tempo_copy->frame(); + framepos_t old_next_pos = next_t->frame(); + double old_next_minute = next_t->minute(); + framepos_t old_next_to_next_pos = next_to_next_t->frame(); + 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 > prev_to_prev_t->frame() + min_dframe && (frame + tempo_copy_frame_contribution) > prev_to_prev_t->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. @@ -3493,17 +3693,95 @@ TempoMap::gui_stretch_tempo (TempoSection* ts, const framepos_t& frame, const fr which is an entirely different thing to just being too low. */ if (new_bpm < 0.5) { - return; + return false; } + new_bpm = min (new_bpm, (double) 1000.0); - prev_t->set_note_types_per_minute (new_bpm); + + 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); - recompute_meters (future_map); if (check_solved (future_map)) { + + if (!next_t) { + return false; + } ts->set_note_types_per_minute (new_bpm); - recompute_tempi (_metrics); - recompute_meters (_metrics); + 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 > prev_to_prev_t->frame() + min_dframe && end_frame > prev_to_prev_t->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 (*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->frame() - old_next_pos) / (double) (old_next_to_next_pos - old_next_pos); + + copy_frame_ratio = ((old_tc_pos - next_t->frame()) / (double) (old_tc_pos - old_next_pos)); + + } else { + //next_frame_ratio = (((next_to_next_pos - fr_off) - next_t->frame()) / (double) ((next_to_next_pos) - next_t->frame())); + //next_pulse_ratio = (start_pulse / end_pulse); + } + + 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; + + next_t->set_note_types_per_minute (new_next_bpm); + tempo_copy->set_end_note_types_per_minute (new_copy_end_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 (*i); + break; + } + } + + if (!next_t) { + return false; + } + 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; + } } } @@ -3512,10 +3790,12 @@ TempoMap::gui_stretch_tempo (TempoSection* ts, const framepos_t& frame, const fr delete (*d); ++d; } + if (can_solve) { + MetricPositionChanged (PropertyChange ()); // Emit Signal + } - MetricPositionChanged (PropertyChange ()); // Emit Signal + return can_solve; } - /** 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. * @@ -4203,6 +4483,32 @@ TempoMap::fix_legacy_session () } } } +void +TempoMap::fix_legacy_end_session () +{ + TempoSection* prev_t = 0; + + for (Metrics::iterator i = _metrics.begin(); i != _metrics.end(); ++i) { + TempoSection* t; + + if ((t = dynamic_cast(*i)) != 0) { + + if (!t->active()) { + continue; + } + + if (prev_t) { + if (prev_t->type() == TempoSection::Ramp) { + prev_t->set_end_note_types_per_minute (t->note_types_per_minute()); + } else { + prev_t->set_end_note_types_per_minute (prev_t->note_types_per_minute()); + } + } + + prev_t = t; + } + } +} XMLNode& TempoMap::get_state () @@ -4279,6 +4585,12 @@ TempoMap::set_state (const XMLNode& node, int /*version*/) fix_legacy_session(); break; } + + if (t->legacy_end()) { + fix_legacy_end_session(); + break; + } + break; } } @@ -4337,19 +4649,20 @@ TempoMap::dump (std::ostream& o) const for (Metrics::const_iterator i = _metrics.begin(); i != _metrics.end(); ++i) { if ((t = dynamic_cast(*i)) != 0) { - o << "Tempo @ " << *i << t->note_types_per_minute() << " BPM (pulse = 1/" << t->note_type() + o << "Tempo @ " << *i << " start : " << t->note_types_per_minute() << " end : " << t->end_note_types_per_minute() << " BPM (pulse = 1/" << t->note_type() << " type= " << enum_2_string (t->type()) << ") " << " at pulse= " << t->pulse() << " minute= " << t->minute() << " frame= " << t->frame() << " (initial? " << t->initial() << ')' << " pos lock: " << enum_2_string (t->position_lock_style()) << std::endl; if (prev_t) { - o << " current : " << t->note_types_per_minute() + o << " current start : " << t->note_types_per_minute() + << " current end : " << t->end_note_types_per_minute() << " | " << t->pulse() << " | " << t->frame() << " | " << t->minute() << std::endl; o << " previous : " << prev_t->note_types_per_minute() << " | " << prev_t->pulse() << " | " << prev_t->frame() << " | " << prev_t->minute() << std::endl; o << " calculated : " << prev_t->tempo_at_pulse (t->pulse()) - << " | " << prev_t->pulse_at_ntpm (t->note_types_per_minute(), t->minute()) - << " | " << frame_at_minute (prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse())) - << " | " << prev_t->minute_at_ntpm (t->note_types_per_minute(), t->pulse()) << std::endl; + << " | " << prev_t->pulse_at_ntpm (prev_t->end_note_types_per_minute(), t->minute()) + << " | " << frame_at_minute (prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse())) + << " | " << prev_t->minute_at_ntpm (prev_t->end_note_types_per_minute(), t->pulse()) << std::endl; } prev_t = t; } else if ((m = dynamic_cast(*i)) != 0) {