summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornick_m <mainsbridge@gmail.com>2017-02-25 05:09:16 +1100
committerRobin Gareus <robin@gareus.org>2017-02-27 20:16:10 +0100
commitccb51a8ddbecd2fe3d6847d85b3a4ba1f55d3ef1 (patch)
tree0dad2071b3da73ced18cfb75cc1ea0e40e1fc838
parentdd72d2bf6584571b88fb383752dcb0dd892a034a (diff)
rework tempo editing.
most changes are due to a new design where tempo discontinuities at the beginning of a ramped section may be set. this allows easier mapping of live performance, especially in the common case of a ramped ritard before the beginning of a new section. feature summary: holding constraint modifier (shift) while dragging the BBT ruler area drags the tempo lines by changing the start tempo (as before) holding copy modifier (control) while dragging the BBT ruler area drags the tempo lines by changing the end tempo (ahem. not quite there) dragging a tempo mark while holding constraint (shift) will change the previous end tempo to match the marker position *worth trying*. holding constraint and copy modifier (control + shift) while dragging the BBT ruler area attempts to'pinch' or twist the surrounding tempi sp that later ones are not repositioned (currently suffereng from rounding errors)
-rw-r--r--gtk2_ardour/editor_canvas_events.cc53
-rw-r--r--gtk2_ardour/editor_drag.cc228
-rw-r--r--gtk2_ardour/editor_drag.h59
-rw-r--r--gtk2_ardour/editor_markers.cc4
-rw-r--r--gtk2_ardour/editor_mouse.cc4
-rw-r--r--gtk2_ardour/editor_ops.cc6
-rw-r--r--gtk2_ardour/editor_tempodisplay.cc20
-rw-r--r--gtk2_ardour/tempo_curve.cc2
-rw-r--r--libs/ardour/ardour/tempo.h33
-rw-r--r--libs/ardour/luabindings.cc2
-rw-r--r--libs/ardour/tempo.cc573
11 files changed, 820 insertions, 164 deletions
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<TempoMap>(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<TempoMarker*> (_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<TempoSection*> (&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<TempoSection*> (&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<TempoMap>(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<TempoSection*> (&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<TempoMap>(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<TempoSection*>(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<TempoSection*>(ts))));
@@ -122,6 +131,8 @@ Editor::draw_metric_marks (const Metrics& metrics)
*(const_cast<TempoSection*>(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<MeterMarker*> (*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<MetricSection*> 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<double, framepos_t> predict_tempo_position (TempoSection* section, const Timecode::BBT_Time& bbt);
bool can_solve_bbt (TempoSection* section, const Timecode::BBT_Time& bbt);
+ std::pair<double, double> ntpm_minmax () { return _ntpm_minmax; }
PBD::Signal1<void,const PBD::PropertyChange&> 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> ("Tempo")
- .addConstructor <void (*) (double, double)> ()
+ .addConstructor <void (*) (double, double, double)> ()
.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<TempoSection*> (*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<TempoSection*> (*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<Tempo*>(&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<TempoSection*>(&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<TempoSection*> (*i);
- if (t->frame() > ts->frame()) {
- next_t = t;
- break;
- }
- }
- }
/* minimum allowed measurement distance in frames */
- const framepos_t min_dframe = 2;
+ 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->note_types_per_minute() * ((frame - prev_t->frame())
+ / (double) (end_frame - prev_t->frame()));
+ } else {
+ new_bpm = prev_t->note_types_per_minute();
+ }
+
+ std::cout << "new bpm : " << new_bpm << std::endl;
+ new_bpm = min (new_bpm, (double) 1000.0);
- /* 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.
+ /* 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.
*/
- 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());
+ if (new_bpm < 0.5) {
+ goto out;
}
- const frameoffset_t prev_t_frame_contribution = fr_off - (contribution * (double) fr_off);
+ 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);
+ }
- 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));
+ recompute_tempi (future_map);
+ recompute_meters (future_map);
- double new_bpm;
+ if (check_solved (future_map)) {
+ if (prev_t && prev_t->type() == TempoSection::Ramp) {
+ ts->set_note_types_per_minute (new_bpm);
+ } else {
+ ts->set_end_note_types_per_minute (new_bpm);
+ ts->set_note_types_per_minute (new_bpm);
+ }
+ recompute_tempi (_metrics);
+ recompute_meters (_metrics);
+ }
+ }
- if (prev_t->type() == TempoSection::Constant || prev_t->c() == 0.0) {
+ MetricPositionChanged (PropertyChange ()); // Emit Signal
- 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) {
+out:
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
- 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 */
+}
+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)
+ */
- 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) {
+ Metrics future_map;
- 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 */
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
- 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();
- }
- }
+ 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<TempoSection*> (*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();
+ }
- double frame_ratio = 1.0;
- double pulse_ratio = 1.0;
- const double pulse_pos = frame;
+ 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()));
- }
+ 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 (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()));
+ 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());
}
- pulse_ratio = (start_pulse / end_pulse);
}
- 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;
+ }
+
+}
+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) {
+ 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<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->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<TempoSection*> (*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<TempoSection*>(*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<const TempoSection*>(*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<const MeterSection*>(*i)) != 0) {