summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornick_m <mainsbridge@gmail.com>2016-05-08 03:03:12 +1000
committernick_m <mainsbridge@gmail.com>2016-05-27 23:38:16 +1000
commit86b0268e8be554e9286aebd544757fc13fe76dac (patch)
tree959f2fb0dbe33a738e15caf8186a56a58beace56
parent652a59b3178b0d0905f8d9610500a71f0924edc0 (diff)
Tempo ramps - add visualtempo curve, dragging bbt or music rulers with constraint modifier dilates previous tempo.
-rw-r--r--gtk2_ardour/editor.h5
-rw-r--r--gtk2_ardour/editor_canvas_events.cc6
-rw-r--r--gtk2_ardour/editor_drag.cc63
-rw-r--r--gtk2_ardour/editor_drag.h25
-rw-r--r--gtk2_ardour/editor_items.h1
-rw-r--r--gtk2_ardour/editor_mouse.cc10
-rw-r--r--gtk2_ardour/editor_tempodisplay.cc70
-rw-r--r--gtk2_ardour/enums.cc1
-rw-r--r--gtk2_ardour/public_editor.h2
-rw-r--r--gtk2_ardour/tempo_curve.cc194
-rw-r--r--gtk2_ardour/tempo_curve.h74
-rw-r--r--gtk2_ardour/wscript1
-rw-r--r--libs/ardour/ardour/tempo.h1
-rw-r--r--libs/ardour/tempo.cc135
-rw-r--r--libs/canvas/curve.cc2
15 files changed, 576 insertions, 14 deletions
diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index f5eaf0aebb..6057326ab3 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -60,6 +60,7 @@
#include "editor_items.h"
#include "region_selection.h"
#include "selection_memento.h"
+#include "tempo_curve.h"
namespace Gtkmm2ext {
class Bindings;
@@ -1565,6 +1566,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
bool canvas_stream_view_event (GdkEvent* event,ArdourCanvas::Item*, RouteTimeAxisView*);
bool canvas_marker_event (GdkEvent* event,ArdourCanvas::Item*, ArdourMarker*);
bool canvas_tempo_marker_event (GdkEvent* event,ArdourCanvas::Item*, TempoMarker*);
+ bool canvas_tempo_curve_event (GdkEvent* event,ArdourCanvas::Item*, TempoCurve*);
bool canvas_meter_marker_event (GdkEvent* event,ArdourCanvas::Item*, MeterMarker*);
bool canvas_automation_track_event(GdkEvent* event, ArdourCanvas::Item*, AutomationTimeAxisView*);
bool canvas_note_event (GdkEvent* event, ArdourCanvas::Item *);
@@ -1696,6 +1698,9 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
typedef std::list<ArdourMarker*> Marks;
Marks metric_marks;
+ typedef std::list<TempoCurve*> Curves;
+ Curves tempo_curves;
+
void remove_metric_marks ();
void draw_metric_marks (const ARDOUR::Metrics& metrics);
diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc
index 8788717bd1..22c701673e 100644
--- a/gtk2_ardour/editor_canvas_events.cc
+++ b/gtk2_ardour/editor_canvas_events.cc
@@ -1014,6 +1014,12 @@ Editor::canvas_tempo_marker_event (GdkEvent *event, ArdourCanvas::Item* item, Te
}
bool
+Editor::canvas_tempo_curve_event (GdkEvent *event, ArdourCanvas::Item* item, TempoCurve* /*marker*/)
+{
+ return typed_event (item, event, TempoCurveItem);
+}
+
+bool
Editor::canvas_meter_marker_event (GdkEvent *event, ArdourCanvas::Item* item, MeterMarker* /*marker*/)
{
return typed_event (item, event, MeterMarkerItem);
diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc
index a686789253..52b5ab6d1a 100644
--- a/gtk2_ardour/editor_drag.cc
+++ b/gtk2_ardour/editor_drag.cc
@@ -3476,6 +3476,69 @@ TempoMarkerDrag::aborted (bool moved)
}
}
+BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
+ : Drag (e, i)
+ , before_state (0)
+{
+ DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
+
+}
+
+void
+BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+ Drag::start_grab (event, cursor);
+ show_verbose_cursor_time (adjusted_current_frame (event));
+}
+
+void
+BBTRulerDrag::setup_pointer_frame_offset ()
+{
+ _pointer_frame_offset = 0;
+}
+
+void
+BBTRulerDrag::motion (GdkEvent* event, bool first_move)
+{
+ if (first_move) {
+ TempoMap& map (_editor->session()->tempo_map());
+ /* get current state */
+ before_state = &map.get_state();
+ _editor->begin_reversible_command (_("dilate tempo"));
+ }
+
+ framepos_t const pf = adjusted_current_frame (event, false);
+
+ if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::constraint_modifier())) {
+ /* adjust previous tempo to match pointer frame */
+ _editor->session()->tempo_map().gui_dilate_tempo (last_pointer_frame(), pf);
+ }
+ show_verbose_cursor_time (pf);
+}
+
+void
+BBTRulerDrag::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
+BBTRulerDrag::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)
, _cursor (c)
diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h
index caee8ad6ba..5b5ca6d432 100644
--- a/gtk2_ardour/editor_drag.h
+++ b/gtk2_ardour/editor_drag.h
@@ -741,6 +741,31 @@ private:
XMLNode* before_state;
};
+/** BBT Ruler drag */
+class BBTRulerDrag : public Drag
+{
+public:
+ BBTRulerDrag (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 false;
+ }
+
+ void setup_pointer_frame_offset ();
+
+private:
+
+ XMLNode* before_state;
+};
/** Drag of the playhead cursor */
class CursorDrag : public Drag
diff --git a/gtk2_ardour/editor_items.h b/gtk2_ardour/editor_items.h
index 743a93b865..79a94df340 100644
--- a/gtk2_ardour/editor_items.h
+++ b/gtk2_ardour/editor_items.h
@@ -36,6 +36,7 @@ enum ItemType {
GainLineItem,
AutomationLineItem,
MeterMarkerItem,
+ TempoCurveItem,
TempoMarkerItem,
MeterBarItem,
TempoBarItem,
diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc
index 4a4bfe1e1e..03974dbd5f 100644
--- a/gtk2_ardour/editor_mouse.cc
+++ b/gtk2_ardour/editor_mouse.cc
@@ -711,13 +711,17 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
case MarkerBarItem:
case TempoBarItem:
+ case TempoCurveItem:
case MeterBarItem:
case TimecodeRulerItem:
case SamplesRulerItem:
case MinsecRulerItem:
case BBTRulerItem:
- if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+ if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)
+ && !Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::constraint_modifier())) {
_drags->set (new CursorDrag (this, *playhead_cursor, false), event);
+ } else if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::constraint_modifier())) {
+ _drags->set (new BBTRulerDrag (this, item), event);
}
return true;
break;
@@ -1333,7 +1337,6 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
case RegionItem:
show_region_properties ();
break;
-
case TempoMarkerItem: {
ArdourMarker* marker;
TempoMarker* tempo_marker;
@@ -1438,6 +1441,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
case TransportMarkerBarItem:
case CdMarkerBarItem:
case TempoBarItem:
+ case TempoCurveItem:
case MeterBarItem:
case VideoBarItem:
case TimecodeRulerItem:
@@ -1547,8 +1551,8 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
mouse_add_new_marker (where, true);
}
return true;
-
case TempoBarItem:
+ case TempoCurveItem:
if (!_dragging_playhead) {
snap_to_with_modifier (where, event);
mouse_add_new_tempo_event (where);
diff --git a/gtk2_ardour/editor_tempodisplay.cc b/gtk2_ardour/editor_tempodisplay.cc
index 127a258c56..0b133e3051 100644
--- a/gtk2_ardour/editor_tempodisplay.cc
+++ b/gtk2_ardour/editor_tempodisplay.cc
@@ -69,6 +69,11 @@ Editor::remove_metric_marks ()
delete_when_idle (*x);
}
metric_marks.clear ();
+
+ for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ++x) {
+ delete (*x);
+ }
+ tempo_curves.clear ();
}
void
@@ -78,6 +83,8 @@ Editor::draw_metric_marks (const Metrics& metrics)
const MeterSection *ms;
const TempoSection *ts;
char buf[64];
+ double max_tempo = 0.0;
+ double min_tempo = DBL_MAX;
remove_metric_marks ();
@@ -89,15 +96,36 @@ Editor::draw_metric_marks (const Metrics& metrics)
*(const_cast<MeterSection*>(ms))));
} else if ((ts = dynamic_cast<const TempoSection*>(*i)) != 0) {
if (UIConfiguration::instance().get_allow_non_quarter_pulse()) {
- snprintf (buf, sizeof (buf), "%.2f/%.0f", ts->beats_per_minute(), ts->note_type());
+ snprintf (buf, sizeof (buf), "%.3f/%.0f", ts->beats_per_minute(), ts->note_type());
} else {
- snprintf (buf, sizeof (buf), "%.2f", ts->beats_per_minute());
+ snprintf (buf, sizeof (buf), "%.3f", ts->beats_per_minute());
+ }
+ if (ts->beats_per_minute() > max_tempo) {
+ max_tempo = ts->beats_per_minute();
+ }
+ if (ts->beats_per_minute() < min_tempo) {
+ min_tempo = ts->beats_per_minute();
}
+ tempo_curves.push_back (new TempoCurve (*this, *tempo_group, UIConfiguration::instance().color ("range drag rect"),
+ *(const_cast<TempoSection*>(ts)), ts->frame(), false));
metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker"), buf,
*(const_cast<TempoSection*>(ts))));
+
}
}
+ for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
+ Curves::iterator tmp = x;
+ (*x)->set_max_tempo (max_tempo);
+ (*x)->set_min_tempo (min_tempo);
+ ++tmp;
+ if (tmp != tempo_curves.end()) {
+ (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
+ } else {
+ (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
+ }
+ ++x;
+ }
}
@@ -122,6 +150,12 @@ Editor::tempo_map_changed (const PropertyChange& /*ignored*/)
update_tempo_based_rulers (grid);
}
+struct CurveComparator {
+ bool operator() (TempoCurve const * a, TempoCurve const * b) {
+ return a->position() < b->position();
+ }
+};
+
void
Editor::marker_position_changed ()
{
@@ -138,14 +172,21 @@ Editor::marker_position_changed ()
MeterMarker* meter_marker;
const TempoSection *ts;
const MeterSection *ms;
-
+ double max_tempo = 0.0;
+ double min_tempo = DBL_MAX;
for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
if ((tempo_marker = dynamic_cast<TempoMarker*> (*x)) != 0) {
if ((ts = &tempo_marker->tempo()) != 0) {
tempo_marker->set_position (ts->frame ());
char buf[64];
- snprintf (buf, sizeof (buf), "%.2f", ts->beats_per_minute());
+ snprintf (buf, sizeof (buf), "%.3f", ts->beats_per_minute());
tempo_marker->set_name (buf);
+ if (ts->beats_per_minute() > max_tempo) {
+ max_tempo = ts->beats_per_minute();
+ }
+ if (ts->beats_per_minute() < min_tempo) {
+ min_tempo = ts->beats_per_minute();
+ }
}
}
if ((meter_marker = dynamic_cast<MeterMarker*> (*x)) != 0) {
@@ -154,6 +195,20 @@ Editor::marker_position_changed ()
}
}
}
+ tempo_curves.sort (CurveComparator());
+ for (Curves::iterator x = tempo_curves.begin(); x != tempo_curves.end(); ) {
+ Curves::iterator tmp = x;
+ (*x)->set_max_tempo (max_tempo);
+ (*x)->set_min_tempo (min_tempo);
+ ++tmp;
+ if (tmp != tempo_curves.end()) {
+ (*x)->set_position ((*x)->tempo().frame(), (*tmp)->tempo().frame());
+ } else {
+ (*x)->set_position ((*x)->tempo().frame(), UINT32_MAX);
+ }
+ ++x;
+ }
+
std::vector<TempoMap::BBTPoint> grid;
compute_current_bbt_points (grid, leftmost_frame, leftmost_frame + current_page_samples());
draw_measures (grid);
@@ -251,9 +306,6 @@ Editor::mouse_add_new_meter_event (framepos_t frame)
TempoMap& map(_session->tempo_map());
MeterDialog meter_dialog (map, frame, _("add"));
- //this causes compiz to display no border..
- //meter_dialog.signal_realize().connect (sigc::bind (sigc::ptr_fun (set_decoration), &meter_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)));
-
switch (meter_dialog.run ()) {
case RESPONSE_ACCEPT:
break;
@@ -271,11 +323,13 @@ Editor::mouse_add_new_meter_event (framepos_t frame)
begin_reversible_command (_("add meter mark"));
XMLNode &before = map.get_state();
+
if (meter_dialog.get_lock_style() == MusicTime) {
map.add_meter (Meter (bpb, note_type), map.bbt_to_beats (requested), requested);
} else {
- map.add_meter (Meter (bpb, note_type), frame, meter_dialog.get_lock_style());
+ map.add_meter (Meter (bpb, note_type), map.frame_time (requested), map.bbt_to_beats (requested), requested);
}
+
_session->add_command(new MementoCommand<TempoMap>(map, &before, &map.get_state()));
commit_reversible_command ();
diff --git a/gtk2_ardour/enums.cc b/gtk2_ardour/enums.cc
index 4cae1f5db4..4f8a348a91 100644
--- a/gtk2_ardour/enums.cc
+++ b/gtk2_ardour/enums.cc
@@ -150,6 +150,7 @@ setup_gtk_ardour_enums ()
REGISTER_ENUM (GainLineItem);
REGISTER_ENUM (AutomationLineItem);
REGISTER_ENUM (MeterMarkerItem);
+ REGISTER_ENUM (TempoCurveItem);
REGISTER_ENUM (TempoMarkerItem);
REGISTER_ENUM (MeterBarItem);
REGISTER_ENUM (TempoBarItem);
diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h
index 6c2e5c6f0a..c583ff67fb 100644
--- a/gtk2_ardour/public_editor.h
+++ b/gtk2_ardour/public_editor.h
@@ -79,6 +79,7 @@ class PluginUIWindow;
class RegionView;
class RouteTimeAxisView;
class Selection;
+class TempoCurve;
class TempoMarker;
class TimeAxisView;
class TimeAxisViewItem;
@@ -346,6 +347,7 @@ class PublicEditor : public Gtkmm2ext::Tabbable {
virtual bool canvas_marker_event (GdkEvent* event, ArdourCanvas::Item*, ArdourMarker*) = 0;
virtual bool canvas_videotl_bar_event (GdkEvent* event, ArdourCanvas::Item*) = 0;
virtual bool canvas_tempo_marker_event (GdkEvent* event, ArdourCanvas::Item*, TempoMarker*) = 0;
+ virtual bool canvas_tempo_curve_event (GdkEvent* event, ArdourCanvas::Item*, TempoCurve*) = 0;
virtual bool canvas_meter_marker_event (GdkEvent* event, ArdourCanvas::Item*, MeterMarker*) = 0;
virtual bool canvas_automation_track_event(GdkEvent* event, ArdourCanvas::Item*, AutomationTimeAxisView*) = 0;
diff --git a/gtk2_ardour/tempo_curve.cc b/gtk2_ardour/tempo_curve.cc
new file mode 100644
index 0000000000..b340ee0145
--- /dev/null
+++ b/gtk2_ardour/tempo_curve.cc
@@ -0,0 +1,194 @@
+#include <sigc++/bind.h>
+#include "ardour/tempo.h"
+
+#include "canvas/rectangle.h"
+#include "canvas/container.h"
+#include "canvas/curve.h"
+#include "canvas/polygon.h"
+#include "canvas/canvas.h"
+#include "canvas/debug.h"
+
+#include "ui_config.h"
+
+#include "tempo_curve.h"
+#include "public_editor.h"
+#include "utils.h"
+#include "rgb_macros.h"
+
+#include <gtkmm2ext/utils.h>
+
+#include "i18n.h"
+
+PBD::Signal1<void,TempoCurve*> TempoCurve::CatchDeletion;
+
+TempoCurve::TempoCurve (PublicEditor& ed, ArdourCanvas::Container& parent, guint32 rgba, ARDOUR::TempoSection& temp, framepos_t frame, bool handle_events)
+
+ : editor (ed)
+ , _parent (&parent)
+ , _shown (false)
+ , _curve (0)
+ , _color (rgba)
+ , _max_tempo (temp.beats_per_minute())
+ , _tempo (temp)
+
+
+{
+ const double MH = 12.0;
+ const double M3 = std::max(1.f, rintf(3.f * UIConfiguration::instance().get_ui_scale()));
+ const double M6 = std::max(2.f, rintf(6.f * UIConfiguration::instance().get_ui_scale()));
+
+ points = new ArdourCanvas::Points ();
+ points->push_back (ArdourCanvas::Duple (0.0, 0.0));
+ points->push_back (ArdourCanvas::Duple (1.0, 0.0));
+ points->push_back (ArdourCanvas::Duple (1.0, MH));
+ points->push_back (ArdourCanvas::Duple (0.0, MH));
+
+ frame_position = frame;
+ unit_position = editor.sample_to_pixel (frame);
+
+ group = new ArdourCanvas::Container (&parent, ArdourCanvas::Duple (unit_position, 0));
+#ifdef CANVAS_DEBUG
+ group->name = string_compose ("Marker::group for %1", _tempo.beats_per_minute());
+#endif
+
+ _background = new ArdourCanvas::Rectangle (group);
+#ifdef CANVAS_DEBUG
+ _background->name = string_compose ("Marker::_background for %1", _tempo.beats_per_minute());
+#endif
+ _background->set_x0 (0.0);
+ _background->set_x1 (ArdourCanvas::COORD_MAX);
+ _background->set_outline_what (ArdourCanvas::Rectangle::What(0));
+ _curve = new ArdourCanvas::Curve (group);
+#ifdef CANVAS_DEBUG
+ _curve->name = string_compose ("Marker::_background for %1", _tempo.beats_per_minute());
+#endif
+ _curve->set_fill_mode (ArdourCanvas::Curve::Inside);
+ _curve->set_points_per_segment (32);
+ _curve->set (*points);
+
+ set_color_rgba (rgba);
+
+ editor.ZoomChanged.connect (sigc::mem_fun (*this, &TempoCurve::reposition));
+
+ /* events will be handled by both the group and the mark itself, so
+ * make sure they can both be used to lookup this object.
+ */
+
+ group->set_data ("marker", this);
+
+ if (handle_events) {
+ //group->Event.connect (sigc::bind (sigc::mem_fun (editor, &PublicEditor::canvas_marker_event), group, this));
+ }
+ set_position (_tempo.frame(), UINT32_MAX);
+ _curve->Event.connect (sigc::bind (sigc::mem_fun (editor, &PublicEditor::canvas_tempo_curve_event), group, this));
+ _background->Event.connect (sigc::bind (sigc::mem_fun (editor, &PublicEditor::canvas_tempo_curve_event), group, this));
+
+}
+
+TempoCurve::~TempoCurve ()
+{
+ CatchDeletion (this); /* EMIT SIGNAL */
+
+ /* destroying the parent group destroys its contents, namely any polygons etc. that we added */
+ delete group;
+}
+
+void TempoCurve::reparent(ArdourCanvas::Container & parent)
+{
+ group->reparent (&parent);
+ _parent = &parent;
+}
+
+void
+TempoCurve::canvas_height_set (double h)
+{
+ _canvas_height = h;
+}
+
+ArdourCanvas::Item&
+TempoCurve::the_item() const
+{
+ return *group;
+}
+
+void
+TempoCurve::set_position (framepos_t frame, framepos_t end_frame)
+{
+ const double height = 12.0;
+ points->clear();
+ unit_position = editor.sample_to_pixel (frame);
+ group->set_x_position (unit_position);
+ frame_position = frame;
+ _end_frame = end_frame;
+ _background->set_x0 (0.0);
+ _background->set_x1 (editor.sample_to_pixel (end_frame - frame));
+ const double tempo_delta = max (10.0, _max_tempo - _min_tempo);
+ double max_y = 0.0;
+ points = new ArdourCanvas::Points ();
+ if (end_frame == UINT32_MAX) {
+ _curve->set_fill_mode (ArdourCanvas::Curve::None);
+ const double tempo_at = _tempo.tempo_at_frame (frame, editor.session()->frame_rate()) * _tempo.note_type();
+ const double y2_pos = (height + 2.0) - (((tempo_at - _min_tempo) / (tempo_delta)) * height);
+ max_y = y2_pos;
+ points->push_back (ArdourCanvas::Duple (0.0, y2_pos));
+ points->push_back (ArdourCanvas::Duple (ArdourCanvas::COORD_MAX - 5.0, y2_pos));
+
+ } else {
+ _curve->set_fill_mode (ArdourCanvas::Curve::Inside);
+ const framepos_t frame_step = (end_frame - frame) / 32;
+ framepos_t current_frame = frame;
+ while (current_frame < end_frame) {
+ const double tempo_at = _tempo.tempo_at_frame (current_frame, editor.session()->frame_rate()) * _tempo.note_type();
+ const double y2_pos = (height + 2.0) - (((tempo_at - _min_tempo) / (tempo_delta)) * height);
+
+ points->push_back (ArdourCanvas::Duple (editor.sample_to_pixel (current_frame - frame), y2_pos));
+ max_y = max (y2_pos, max_y);
+ current_frame += frame_step;
+ }
+ }
+
+ /* the background fills the gap between the bottom of the curve and the time bar */
+ _background->set_y0 (max_y + 1.0);
+ _background->set_y1 (height + 2.0);
+
+ if (max_y == height + 2.0) {
+ _background->hide();
+ } else {
+ _background->show();
+ }
+
+ _curve->set (*points);
+}
+
+void
+TempoCurve::reposition ()
+{
+ set_position (frame_position, _end_frame);
+}
+
+void
+TempoCurve::show ()
+{
+ _shown = true;
+
+ group->show ();
+}
+
+void
+TempoCurve::hide ()
+{
+ _shown = false;
+
+ group->hide ();
+}
+
+void
+TempoCurve::set_color_rgba (uint32_t c)
+{
+ _color = c;
+ _curve->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
+ _curve->set_outline_color (_color);
+
+ _background->set_fill (true);
+ _background->set_fill_color (UIConfiguration::instance().color_mod ("selection rect", "selection rect"));
+}
diff --git a/gtk2_ardour/tempo_curve.h b/gtk2_ardour/tempo_curve.h
new file mode 100644
index 0000000000..be378078c7
--- /dev/null
+++ b/gtk2_ardour/tempo_curve.h
@@ -0,0 +1,74 @@
+#ifndef __gtk_ardour_tempo_curve_h__
+#define __gtk_ardour_tempo_curve_h__
+
+#include <string>
+#include <glib.h>
+
+#include <sigc++/signal.h>
+
+#include "ardour/ardour.h"
+#include "pbd/signals.h"
+
+#include "canvas/fwd.h"
+#include "canvas/types.h"
+
+namespace ARDOUR {
+ class TempoSection;
+}
+class PublicEditor;
+
+class TempoCurve : public sigc::trackable
+{
+ public:
+ TempoCurve (PublicEditor& editor, ArdourCanvas::Container &, guint32 rgba, ARDOUR::TempoSection& temp, framepos_t frame, bool handle_events);
+ ~TempoCurve ();
+
+ static PBD::Signal1<void,TempoCurve*> CatchDeletion;
+
+ static void setup_sizes (const double timebar_height);
+
+ ArdourCanvas::Item& the_item() const;
+ void canvas_height_set (double);
+
+ void set_position (framepos_t lower, framepos_t upper);
+ void set_color_rgba (uint32_t rgba);
+ framepos_t position() const { return frame_position; }
+
+ ArdourCanvas::Container * get_parent() { return _parent; }
+ void reparent (ArdourCanvas::Container & parent);
+
+ void hide ();
+ void show ();
+
+ ARDOUR::TempoSection& tempo () { return _tempo; }
+
+ void set_max_tempo (const double& max) { _max_tempo = max; }
+ void set_min_tempo (const double& min) { _min_tempo = min; }
+
+protected:
+ PublicEditor& editor;
+
+ ArdourCanvas::Container* _parent;
+ ArdourCanvas::Container *group;
+ ArdourCanvas::Points *points;
+ ArdourCanvas::Rectangle* _background;
+ ArdourCanvas::Curve* _curve;
+
+ double unit_position;
+ framepos_t frame_position;
+ framepos_t _end_frame;
+ bool _shown;
+ double _canvas_height;
+ uint32_t _color;
+
+ void reposition ();
+private:
+ double _max_tempo;
+ double _min_tempo;
+ /* disallow copy construction */
+ TempoCurve (TempoCurve const &);
+ TempoCurve & operator= (TempoCurve const &);
+ ARDOUR::TempoSection& _tempo;
+
+};
+#endif /* __gtk_ardour_tempo_curve_h__ */
diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript
index a51a7db1ee..f2973b249d 100644
--- a/gtk2_ardour/wscript
+++ b/gtk2_ardour/wscript
@@ -241,6 +241,7 @@ gtk2_ardour_sources = [
'strip_silence_dialog.cc',
'sys_ex.cc',
'tape_region_view.cc',
+ 'tempo_curve.cc',
'tempo_dialog.cc',
'tempo_lines.cc',
'theme_manager.cc',
diff --git a/libs/ardour/ardour/tempo.h b/libs/ardour/ardour/tempo.h
index 6a56c194a4..ba28e8a44a 100644
--- a/libs/ardour/ardour/tempo.h
+++ b/libs/ardour/ardour/tempo.h
@@ -399,6 +399,7 @@ class LIBARDOUR_API TempoMap : public PBD::StatefulDestructible
void gui_move_meter (MeterSection*, const Timecode::BBT_Time& bbt);
bool gui_change_tempo (TempoSection*, const Tempo& bpm);
void gui_dilate_tempo (MeterSection*, const framepos_t& frame);
+ void gui_dilate_tempo (const framepos_t& frame, const framepos_t& end_frame);
bool can_solve_bbt (TempoSection* section, const Timecode::BBT_Time& bbt);
diff --git a/libs/ardour/tempo.cc b/libs/ardour/tempo.cc
index 3227278875..7fd41b12c1 100644
--- a/libs/ardour/tempo.cc
+++ b/libs/ardour/tempo.cc
@@ -2654,6 +2654,10 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
Metrics future_map;
TempoSection* ts = 0;
+ if (frame <= first_meter().frame()) {
+ return;
+ }
+
if (ms->position_lock_style() == AudioTime) {
/* disabled for now due to faked tempo locked to meter pulse */
return;
@@ -2678,8 +2682,8 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
constant to constant is straightforward, as the tempo prev to prev_t has constant slope.
*/
double contribution = 0.0;
- frameoffset_t frame_contribution = 0.0;
- frameoffset_t prev_t_frame_contribution = 0.0;
+ frameoffset_t frame_contribution = 0;
+ frameoffset_t prev_t_frame_contribution = fr_off;
if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
/* prev to prev_t's position will remain constant in terms of frame and pulse. lets use frames. */
@@ -2778,6 +2782,133 @@ TempoMap::gui_dilate_tempo (MeterSection* ms, const framepos_t& frame)
MetricPositionChanged (); // Emit Signal
}
+void
+TempoMap::gui_dilate_tempo (const framepos_t& frame, const framepos_t& end_frame)
+{
+ Metrics future_map;
+ TempoSection* ts = 0;
+
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (lock);
+ ts = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, frame - 1));
+ if (!ts) {
+ return;
+ }
+ 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;
+ double new_bpm = 0.0;
+
+ if (prev_t && prev_t->pulse() > 0.0) {
+ prev_to_prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (future_map, prev_t->frame() - 1));
+ }
+
+ /* 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;
+ frameoffset_t frame_contribution = 0;
+ frameoffset_t prev_t_frame_contribution = fr_off;
+
+ if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ /* prev to prev_t's position will remain constant in terms of frame and pulse. lets use frames. */
+ contribution = (prev_t->frame() - prev_to_prev_t->frame()) / (double) (frame - prev_to_prev_t->frame());
+ frame_contribution = contribution * (double) fr_off;
+ prev_t_frame_contribution -= frame_contribution;
+ }
+
+ if (prev_t->type() == TempoSection::Constant || prev_t->c_func() == 0.0) {
+
+ if (prev_t->position_lock_style() == MusicTime) {
+ if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
+ / (double) (frame + prev_t_frame_contribution - prev_t->frame()));
+
+ } else {
+ /* prev to prev is irrelevant */
+ const double meter_pulse = prev_t->pulse_at_frame (frame, _frame_rate);
+ const double frame_pulse = prev_t->pulse_at_frame (end_frame, _frame_rate);
+
+ if (frame_pulse != prev_t->pulse()) {
+ new_bpm = prev_t->beats_per_minute() * ((meter_pulse - prev_t->pulse()) / (frame_pulse - prev_t->pulse()));
+ } else {
+ new_bpm = prev_t->beats_per_minute();
+ }
+ }
+ } else {
+ /* AudioTime */
+ if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
+ / (double) (frame + prev_t_frame_contribution - prev_t->frame()));
+ } else {
+ /* prev_to_prev_t is irrelevant */
+
+ if (end_frame != prev_t->frame()) {
+ new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame()) / (double) (end_frame - prev_t->frame()));
+ } else {
+ new_bpm = prev_t->beats_per_minute();
+ }
+ }
+ }
+ } else if (prev_t->c_func() < 0.0) {
+ if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ new_bpm = prev_t->tempo_at_frame (prev_t->frame() + frame_contribution, _frame_rate) * (double) prev_t->note_type();
+ } else {
+ /* prev_to_prev_t is irrelevant */
+ new_bpm = prev_t->tempo_at_frame (prev_t->frame() + fr_off, _frame_rate) * (double) prev_t->note_type();
+
+ }
+ const double end_minute = ((end_frame - prev_t->frame()) / (double) _frame_rate) / 60.0;
+ const double end_tempo = prev_t->tempo_at_frame (end_frame, _frame_rate);
+ const double future_c = log (end_tempo / (new_bpm / (double) prev_t->note_type())) / end_minute;
+
+ /* limits - a bit clunky, but meh */
+ if (future_c > -20.1 && future_c < 20.1) {
+ new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
+ / (double) ((frame + prev_t_frame_contribution) - prev_t->frame()));
+ }
+
+ } else if (prev_t->c_func() > 0.0) {
+ if (prev_to_prev_t && prev_to_prev_t->type() == TempoSection::Ramp) {
+ new_bpm = prev_t->tempo_at_frame (prev_t->frame() - frame_contribution, _frame_rate) * (double) prev_t->note_type();
+ } else {
+ /* prev_to_prev_t is irrelevant */
+ new_bpm = prev_t->tempo_at_frame (prev_t->frame() - fr_off, _frame_rate) * (double) prev_t->note_type();
+ }
+ const double end_minute = ((end_frame - prev_t->frame()) / (double) _frame_rate) / 60.0;
+ const double end_tempo = prev_t->tempo_at_frame (end_frame, _frame_rate);
+ const double future_c = log (end_tempo / (new_bpm / (double) prev_t->note_type())) / end_minute;
+
+ /* limits - a bit clunky, but meh */
+ if (future_c > -20.1 && future_c < 20.1) {
+ new_bpm = prev_t->beats_per_minute() * ((frame - prev_t->frame())
+ / (double) ((frame + prev_t_frame_contribution) - prev_t->frame()));
+ }
+ }
+
+ prev_t->set_beats_per_minute (new_bpm);
+ recompute_tempos (future_map);
+ recompute_meters (future_map);
+
+ if (check_solved (future_map, true)) {
+
+ prev_t = const_cast<TempoSection*>(&tempo_section_at_locked (_metrics, frame - 1));
+ prev_t->set_beats_per_minute (new_bpm);
+ recompute_tempos (_metrics);
+ recompute_meters (_metrics);
+ }
+ }
+
+ Metrics::const_iterator d = future_map.begin();
+ while (d != future_map.end()) {
+ delete (*d);
+ ++d;
+ }
+
+ MetricPositionChanged (); // Emit Signal
+}
+
framecnt_t
TempoMap::bbt_duration_at (framepos_t pos, const BBT_Time& bbt, int dir)
{
diff --git a/libs/canvas/curve.cc b/libs/canvas/curve.cc
index 280a3e3aaa..e136a4f5d0 100644
--- a/libs/canvas/curve.cc
+++ b/libs/canvas/curve.cc
@@ -194,7 +194,7 @@ Curve::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
window_space = item_to_window (Duple (samples[left].x, samples[left].y));
context->move_to (window_space.x, window_space.y);
for (uint32_t idx = left + 1; idx < right; ++idx) {
- window_space = item_to_window (Duple (samples[idx].x, samples[idx].y));
+ window_space = item_to_window (Duple (samples[idx].x, samples[idx].y), false);
context->line_to (window_space.x, window_space.y);
}