From 86b0268e8be554e9286aebd544757fc13fe76dac Mon Sep 17 00:00:00 2001 From: nick_m Date: Sun, 8 May 2016 03:03:12 +1000 Subject: Tempo ramps - add visualtempo curve, dragging bbt or music rulers with constraint modifier dilates previous tempo. --- gtk2_ardour/editor.h | 5 + gtk2_ardour/editor_canvas_events.cc | 6 ++ gtk2_ardour/editor_drag.cc | 63 ++++++++++++ gtk2_ardour/editor_drag.h | 25 +++++ gtk2_ardour/editor_items.h | 1 + gtk2_ardour/editor_mouse.cc | 10 +- gtk2_ardour/editor_tempodisplay.cc | 70 +++++++++++-- gtk2_ardour/enums.cc | 1 + gtk2_ardour/public_editor.h | 2 + gtk2_ardour/tempo_curve.cc | 194 ++++++++++++++++++++++++++++++++++++ gtk2_ardour/tempo_curve.h | 74 ++++++++++++++ gtk2_ardour/wscript | 1 + libs/ardour/ardour/tempo.h | 1 + libs/ardour/tempo.cc | 135 ++++++++++++++++++++++++- libs/canvas/curve.cc | 2 +- 15 files changed, 576 insertions(+), 14 deletions(-) create mode 100644 gtk2_ardour/tempo_curve.cc create mode 100644 gtk2_ardour/tempo_curve.h 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 Marks; Marks metric_marks; + typedef std::list 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 @@ -1013,6 +1013,12 @@ Editor::canvas_tempo_marker_event (GdkEvent *event, ArdourCanvas::Item* item, Te return typed_event (item, event, TempoMarkerItem); } +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*/) { 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(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(ms)))); } else if ((ts = dynamic_cast(*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(ts)), ts->frame(), false)); metric_marks.push_back (new TempoMarker (*this, *tempo_group, UIConfiguration::instance().color ("tempo marker"), buf, *(const_cast(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 (*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 (*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 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(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 +#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 + +#include "i18n.h" + +PBD::Signal1 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 +#include + +#include + +#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 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(&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(&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(&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 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); } -- cgit v1.2.3