summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk2_ardour/editor_ops.cc.orig8224
-rw-r--r--gtk2_ardour/midi_region_view.cc.orig4428
-rw-r--r--gtk2_ardour/midi_region_view.cc.rej11
-rw-r--r--gtk2_ardour/out5182
-rw-r--r--libs/ardour/ardour/midi_model.h.orig334
-rw-r--r--libs/ardour/ardour/midi_source.h.orig258
-rw-r--r--libs/ardour/midi_source.cc.orig580
-rw-r--r--libs/evoral/evoral/Beats.hpp.orig247
-rw-r--r--libs/evoral/test/BeatsTest.cpp170
-rw-r--r--libs/evoral/test/BeatsTest.hpp22
-rw-r--r--libs/evoral/wscript.orig168
-rw-r--r--nutemp/t0
-rw-r--r--nutemp/t.cc1243
-rw-r--r--nutemp/t.h402
14 files changed, 21269 insertions, 0 deletions
diff --git a/gtk2_ardour/editor_ops.cc.orig b/gtk2_ardour/editor_ops.cc.orig
new file mode 100644
index 0000000000..0155015b1b
--- /dev/null
+++ b/gtk2_ardour/editor_ops.cc.orig
@@ -0,0 +1,8224 @@
+/*
+ Copyright (C) 2000-2004 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/* Note: public Editor methods are documented in public_editor.h */
+
+#include <unistd.h>
+
+#include <cstdlib>
+#include <cmath>
+#include <string>
+#include <limits>
+#include <map>
+#include <set>
+
+#include <gtkmm/messagedialog.h>
+
+#include "pbd/error.h"
+#include "pbd/basename.h"
+#include "pbd/pthread_utils.h"
+#include "pbd/memento_command.h"
+#include "pbd/unwind.h"
+#include "pbd/whitespace.h"
+#include "pbd/stateful_diff_command.h"
+
+#include "gtkmm2ext/utils.h"
+
+#include "widgets/choice.h"
+#include "widgets/popup.h"
+#include "widgets/prompter.h"
+
+#include "ardour/audio_track.h"
+#include "ardour/audioregion.h"
+#include "ardour/boost_debug.h"
+#include "ardour/dB.h"
+#include "ardour/location.h"
+#include "ardour/midi_region.h"
+#include "ardour/midi_track.h"
+#include "ardour/operations.h"
+#include "ardour/playlist_factory.h"
+#include "ardour/profile.h"
+#include "ardour/quantize.h"
+#include "ardour/legatize.h"
+#include "ardour/region_factory.h"
+#include "ardour/reverse.h"
+#include "ardour/session.h"
+#include "ardour/session_playlists.h"
+#include "ardour/strip_silence.h"
+#include "ardour/transient_detector.h"
+#include "ardour/transpose.h"
+#include "ardour/vca_manager.h"
+
+#include "canvas/canvas.h"
+
+#include "actions.h"
+#include "audio_region_view.h"
+#include "audio_streamview.h"
+#include "audio_time_axis.h"
+#include "automation_region_view.h"
+#include "automation_time_axis.h"
+#include "control_point.h"
+#include "debug.h"
+#include "editing.h"
+#include "editor.h"
+#include "editor_cursors.h"
+#include "editor_drag.h"
+#include "editor_regions.h"
+#include "editor_routes.h"
+#include "gui_thread.h"
+#include "insert_remove_time_dialog.h"
+#include "interthread_progress_window.h"
+#include "item_counts.h"
+#include "keyboard.h"
+#include "midi_region_view.h"
+#include "mixer_ui.h"
+#include "mixer_strip.h"
+#include "mouse_cursors.h"
+#include "normalize_dialog.h"
+#include "note.h"
+#include "paste_context.h"
+#include "patch_change_dialog.h"
+#include "quantize_dialog.h"
+#include "region_gain_line.h"
+#include "rgb_macros.h"
+#include "route_time_axis.h"
+#include "selection.h"
+#include "selection_templates.h"
+#include "streamview.h"
+#include "strip_silence_dialog.h"
+#include "time_axis_view.h"
+#include "timers.h"
+#include "transpose_dialog.h"
+#include "transform_dialog.h"
+#include "ui_config.h"
+#include "vca_time_axis.h"
+
+#include "pbd/i18n.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+using namespace ArdourWidgets;
+using namespace Editing;
+using Gtkmm2ext::Keyboard;
+
+/***********************************************************************
+ Editor operations
+ ***********************************************************************/
+
+void
+Editor::undo (uint32_t n)
+{
+ if (_session && _session->actively_recording()) {
+ /* no undo allowed while recording. Session will check also,
+ but we don't even want to get to that.
+ */
+ return;
+ }
+
+ if (_drags->active ()) {
+ _drags->abort ();
+ }
+
+ if (_session) {
+ _session->undo (n);
+ if (_session->undo_depth() == 0) {
+ undo_action->set_sensitive(false);
+ }
+ redo_action->set_sensitive(true);
+ begin_selection_op_history ();
+ }
+}
+
+void
+Editor::redo (uint32_t n)
+{
+ if (_session && _session->actively_recording()) {
+ /* no redo allowed while recording. Session will check also,
+ but we don't even want to get to that.
+ */
+ return;
+ }
+
+ if (_drags->active ()) {
+ _drags->abort ();
+ }
+
+ if (_session) {
+ _session->redo (n);
+ if (_session->redo_depth() == 0) {
+ redo_action->set_sensitive(false);
+ }
+ undo_action->set_sensitive(true);
+ begin_selection_op_history ();
+ }
+}
+
+void
+Editor::split_regions_at (MusicFrame where, RegionSelection& regions, bool snap_frame)
+{
+ bool frozen = false;
+
+ RegionSelection pre_selected_regions = selection->regions;
+ bool working_on_selection = !pre_selected_regions.empty();
+
+ list<boost::shared_ptr<Playlist> > used_playlists;
+ list<RouteTimeAxisView*> used_trackviews;
+
+ if (regions.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("split"));
+
+ // if splitting a single region, and snap-to is using
+ // region boundaries, don't pay attention to them
+
+ if (regions.size() == 1) {
+ switch (_snap_type) {
+ case SnapToRegionStart:
+ case SnapToRegionSync:
+ case SnapToRegionEnd:
+ break;
+ default:
+ if (snap_frame) {
+ snap_to (where);
+ }
+ }
+ } else {
+ if (snap_frame) {
+ snap_to (where);
+ }
+
+ frozen = true;
+ EditorFreeze(); /* Emit Signal */
+ }
+
+ for (RegionSelection::iterator a = regions.begin(); a != regions.end(); ) {
+
+ RegionSelection::iterator tmp;
+
+ /* XXX this test needs to be more complicated, to make sure we really
+ have something to split.
+ */
+
+ if (!(*a)->region()->covers (where.frame)) {
+ ++a;
+ continue;
+ }
+
+ tmp = a;
+ ++tmp;
+
+ boost::shared_ptr<Playlist> pl = (*a)->region()->playlist();
+
+ if (!pl) {
+ a = tmp;
+ continue;
+ }
+
+ if (!pl->frozen()) {
+ /* we haven't seen this playlist before */
+
+ /* remember used playlists so we can thaw them later */
+ used_playlists.push_back(pl);
+
+ TimeAxisView& tv = (*a)->get_time_axis_view();
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&tv);
+ if (rtv) {
+ used_trackviews.push_back (rtv);
+ }
+ pl->freeze();
+ }
+
+
+ if (pl) {
+ pl->clear_changes ();
+ pl->split_region ((*a)->region(), where);
+ _session->add_command (new StatefulDiffCommand (pl));
+ }
+
+ a = tmp;
+ }
+
+ latest_regionviews.clear ();
+
+ vector<sigc::connection> region_added_connections;
+
+ for (list<RouteTimeAxisView*>::iterator i = used_trackviews.begin(); i != used_trackviews.end(); ++i) {
+ region_added_connections.push_back ((*i)->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view)));
+ }
+
+ while (used_playlists.size() > 0) {
+ list <boost::shared_ptr<Playlist > >::iterator i = used_playlists.begin();
+ (*i)->thaw();
+ used_playlists.pop_front();
+ }
+
+ for (vector<sigc::connection>::iterator c = region_added_connections.begin(); c != region_added_connections.end(); ++c) {
+ (*c).disconnect ();
+ }
+
+ if (frozen){
+ EditorThaw(); /* Emit Signal */
+ }
+
+ if (working_on_selection) {
+ // IFF we were working on selected regions, try to reinstate the other region selections that existed before the freeze/thaw.
+
+ RegionSelectionAfterSplit rsas = Config->get_region_selection_after_split();
+ /* There are three classes of regions that we might want selected after
+ splitting selected regions:
+ - regions selected before the split operation, and unaffected by it
+ - newly-created regions before the split
+ - newly-created regions after the split
+ */
+
+ if (rsas & Existing) {
+ // region selections that existed before the split.
+ selection->add ( pre_selected_regions );
+ }
+
+ for (RegionSelection::iterator ri = latest_regionviews.begin(); ri != latest_regionviews.end(); ri++) {
+ if ((*ri)->region()->position() < where.frame) {
+ // new regions created before the split
+ if (rsas & NewlyCreatedLeft) {
+ selection->add (*ri);
+ }
+ } else {
+ // new regions created after the split
+ if (rsas & NewlyCreatedRight) {
+ selection->add (*ri);
+ }
+ }
+ }
+ } else {
+ if( working_on_selection ) {
+ selection->add (latest_regionviews); //these are the new regions created after the split
+ }
+ }
+
+ commit_reversible_command ();
+}
+
+/** Move one extreme of the current range selection. If more than one range is selected,
+ * the start of the earliest range or the end of the latest range is moved.
+ *
+ * @param move_end true to move the end of the current range selection, false to move
+ * the start.
+ * @param next true to move the extreme to the next region boundary, false to move to
+ * the previous.
+ */
+void
+Editor::move_range_selection_start_or_end_to_region_boundary (bool move_end, bool next)
+{
+ if (selection->time.start() == selection->time.end_frame()) {
+ return;
+ }
+
+ framepos_t start = selection->time.start ();
+ framepos_t end = selection->time.end_frame ();
+
+ /* the position of the thing we may move */
+ framepos_t pos = move_end ? end : start;
+ int dir = next ? 1 : -1;
+
+ /* so we don't find the current region again */
+ if (dir > 0 || pos > 0) {
+ pos += dir;
+ }
+
+ framepos_t const target = get_region_boundary (pos, dir, true, false);
+ if (target < 0) {
+ return;
+ }
+
+ if (move_end) {
+ end = target;
+ } else {
+ start = target;
+ }
+
+ if (end < start) {
+ return;
+ }
+
+ begin_reversible_selection_op (_("alter selection"));
+ selection->set_preserving_all_ranges (start, end);
+ commit_reversible_selection_op ();
+}
+
+bool
+Editor::nudge_forward_release (GdkEventButton* ev)
+{
+ if (ev->state & Keyboard::PrimaryModifier) {
+ nudge_forward (false, true);
+ } else {
+ nudge_forward (false, false);
+ }
+ return false;
+}
+
+bool
+Editor::nudge_backward_release (GdkEventButton* ev)
+{
+ if (ev->state & Keyboard::PrimaryModifier) {
+ nudge_backward (false, true);
+ } else {
+ nudge_backward (false, false);
+ }
+ return false;
+}
+
+
+void
+Editor::nudge_forward (bool next, bool force_playhead)
+{
+ framepos_t distance;
+ framepos_t next_distance;
+
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!force_playhead && !rs.empty()) {
+
+ begin_reversible_command (_("nudge regions forward"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ distance = get_nudge_distance (r->position(), next_distance);
+
+ if (next) {
+ distance = next_distance;
+ }
+
+ r->clear_changes ();
+ r->set_position (r->position() + distance);
+ _session->add_command (new StatefulDiffCommand (r));
+ }
+
+ commit_reversible_command ();
+
+
+ } else if (!force_playhead && !selection->markers.empty()) {
+
+ bool is_start;
+ bool in_command = false;
+ const int32_t divisions = get_grid_music_divisions (0);
+
+ for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) {
+
+ Location* loc = find_location_from_marker ((*i), is_start);
+
+ if (loc) {
+
+ XMLNode& before (loc->get_state());
+
+ if (is_start) {
+ distance = get_nudge_distance (loc->start(), next_distance);
+ if (next) {
+ distance = next_distance;
+ }
+ if (max_framepos - distance > loc->start() + loc->length()) {
+ loc->set_start (loc->start() + distance, false, true, divisions);
+ } else {
+ loc->set_start (max_framepos - loc->length(), false, true, divisions);
+ }
+ } else {
+ distance = get_nudge_distance (loc->end(), next_distance);
+ if (next) {
+ distance = next_distance;
+ }
+ if (max_framepos - distance > loc->end()) {
+ loc->set_end (loc->end() + distance, false, true, divisions);
+ } else {
+ loc->set_end (max_framepos, false, true, divisions);
+ }
+ if (loc->is_session_range()) {
+ _session->set_end_is_free (false);
+ }
+ }
+ if (!in_command) {
+ begin_reversible_command (_("nudge location forward"));
+ in_command = true;
+ }
+ XMLNode& after (loc->get_state());
+ _session->add_command (new MementoCommand<Location>(*loc, &before, &after));
+ }
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+ } else {
+ distance = get_nudge_distance (playhead_cursor->current_frame (), next_distance);
+ _session->request_locate (playhead_cursor->current_frame () + distance);
+ }
+}
+
+void
+Editor::nudge_backward (bool next, bool force_playhead)
+{
+ framepos_t distance;
+ framepos_t next_distance;
+
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!force_playhead && !rs.empty()) {
+
+ begin_reversible_command (_("nudge regions backward"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ distance = get_nudge_distance (r->position(), next_distance);
+
+ if (next) {
+ distance = next_distance;
+ }
+
+ r->clear_changes ();
+
+ if (r->position() > distance) {
+ r->set_position (r->position() - distance);
+ } else {
+ r->set_position (0);
+ }
+ _session->add_command (new StatefulDiffCommand (r));
+ }
+
+ commit_reversible_command ();
+
+ } else if (!force_playhead && !selection->markers.empty()) {
+
+ bool is_start;
+ bool in_command = false;
+
+ for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) {
+
+ Location* loc = find_location_from_marker ((*i), is_start);
+
+ if (loc) {
+
+ XMLNode& before (loc->get_state());
+
+ if (is_start) {
+ distance = get_nudge_distance (loc->start(), next_distance);
+ if (next) {
+ distance = next_distance;
+ }
+ if (distance < loc->start()) {
+ loc->set_start (loc->start() - distance, false, true, get_grid_music_divisions(0));
+ } else {
+ loc->set_start (0, false, true, get_grid_music_divisions(0));
+ }
+ } else {
+ distance = get_nudge_distance (loc->end(), next_distance);
+
+ if (next) {
+ distance = next_distance;
+ }
+
+ if (distance < loc->end() - loc->length()) {
+ loc->set_end (loc->end() - distance, false, true, get_grid_music_divisions(0));
+ } else {
+ loc->set_end (loc->length(), false, true, get_grid_music_divisions(0));
+ }
+ if (loc->is_session_range()) {
+ _session->set_end_is_free (false);
+ }
+ }
+ if (!in_command) {
+ begin_reversible_command (_("nudge location forward"));
+ in_command = true;
+ }
+ XMLNode& after (loc->get_state());
+ _session->add_command (new MementoCommand<Location>(*loc, &before, &after));
+ }
+ }
+ if (in_command) {
+ commit_reversible_command ();
+ }
+
+ } else {
+
+ distance = get_nudge_distance (playhead_cursor->current_frame (), next_distance);
+
+ if (playhead_cursor->current_frame () > distance) {
+ _session->request_locate (playhead_cursor->current_frame () - distance);
+ } else {
+ _session->goto_start();
+ }
+ }
+}
+
+void
+Editor::nudge_forward_capture_offset ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("nudge forward"));
+
+ framepos_t const distance = _session->worst_output_latency();
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ r->clear_changes ();
+ r->set_position (r->position() + distance);
+ _session->add_command(new StatefulDiffCommand (r));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::nudge_backward_capture_offset ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("nudge backward"));
+
+ framepos_t const distance = _session->worst_output_latency();
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ r->clear_changes ();
+
+ if (r->position() > distance) {
+ r->set_position (r->position() - distance);
+ } else {
+ r->set_position (0);
+ }
+ _session->add_command(new StatefulDiffCommand (r));
+ }
+
+ commit_reversible_command ();
+}
+
+struct RegionSelectionPositionSorter {
+ bool operator() (RegionView* a, RegionView* b) {
+ return a->region()->position() < b->region()->position();
+ }
+};
+
+void
+Editor::sequence_regions ()
+{
+ framepos_t r_end;
+ framepos_t r_end_prev;
+
+ int iCount=0;
+
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+ rs.sort(RegionSelectionPositionSorter());
+
+ if (!rs.empty()) {
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ r->clear_changes();
+
+ if(r->locked())
+ {
+ continue;
+ }
+ if(r->position_locked())
+ {
+ continue;
+ }
+ if(iCount>0)
+ {
+ r_end_prev=r_end;
+ r->set_position(r_end_prev);
+ }
+
+ if (!in_command) {
+ begin_reversible_command (_("sequence regions"));
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (r));
+
+ r_end=r->position() + r->length();
+
+ iCount++;
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+ }
+}
+
+
+/* DISPLAY MOTION */
+
+void
+Editor::move_to_start ()
+{
+ _session->goto_start ();
+}
+
+void
+Editor::move_to_end ()
+{
+
+ _session->request_locate (_session->current_end_frame());
+}
+
+void
+Editor::build_region_boundary_cache ()
+{
+ framepos_t pos = 0;
+ vector<RegionPoint> interesting_points;
+ boost::shared_ptr<Region> r;
+ TrackViewList tracks;
+ bool at_end = false;
+
+ region_boundary_cache.clear ();
+
+ if (_session == 0) {
+ return;
+ }
+
+ bool maybe_first_frame = false;
+
+ switch (_snap_type) {
+ case SnapToRegionStart:
+ interesting_points.push_back (Start);
+ maybe_first_frame = true;
+ break;
+ case SnapToRegionEnd:
+ interesting_points.push_back (End);
+ break;
+ case SnapToRegionSync:
+ interesting_points.push_back (SyncPoint);
+ break;
+ case SnapToRegionBoundary:
+ interesting_points.push_back (Start);
+ interesting_points.push_back (End);
+ maybe_first_frame = true;
+ break;
+ default:
+ fatal << string_compose (_("build_region_boundary_cache called with snap_type = %1"), _snap_type) << endmsg;
+ abort(); /*NOTREACHED*/
+ return;
+ }
+
+ TimeAxisView *ontrack = 0;
+ TrackViewList tlist;
+
+ if (!selection->tracks.empty()) {
+ tlist = selection->tracks.filter_to_unique_playlists ();
+ } else {
+ tlist = track_views.filter_to_unique_playlists ();
+ }
+
+ if (maybe_first_frame) {
+ TrackViewList::const_iterator i;
+ for (i = tlist.begin(); i != tlist.end(); ++i) {
+ boost::shared_ptr<Playlist> pl = (*i)->playlist();
+ if (pl && pl->count_regions_at (0)) {
+ region_boundary_cache.push_back (0);
+ break;
+ }
+ }
+ }
+
+ while (pos < _session->current_end_frame() && !at_end) {
+
+ framepos_t rpos;
+ framepos_t lpos = max_framepos;
+
+ for (vector<RegionPoint>::iterator p = interesting_points.begin(); p != interesting_points.end(); ++p) {
+
+ if ((r = find_next_region (pos, *p, 1, tlist, &ontrack)) == 0) {
+ if (*p == interesting_points.back()) {
+ at_end = true;
+ }
+ /* move to next point type */
+ continue;
+ }
+
+ switch (*p) {
+ case Start:
+ rpos = r->first_frame();
+ break;
+
+ case End:
+ rpos = r->last_frame();
+ break;
+
+ case SyncPoint:
+ rpos = r->sync_position ();
+ break;
+
+ default:
+ break;
+ }
+
+ if (rpos < lpos) {
+ lpos = rpos;
+ }
+
+ /* prevent duplicates, but we don't use set<> because we want to be able
+ to sort later.
+ */
+
+ vector<framepos_t>::iterator ri;
+
+ for (ri = region_boundary_cache.begin(); ri != region_boundary_cache.end(); ++ri) {
+ if (*ri == rpos) {
+ break;
+ }
+ }
+
+ if (ri == region_boundary_cache.end()) {
+ region_boundary_cache.push_back (rpos);
+ }
+ }
+
+ pos = lpos + 1;
+ }
+
+ /* finally sort to be sure that the order is correct */
+
+ sort (region_boundary_cache.begin(), region_boundary_cache.end());
+}
+
+boost::shared_ptr<Region>
+Editor::find_next_region (framepos_t frame, RegionPoint point, int32_t dir, TrackViewList& tracks, TimeAxisView **ontrack)
+{
+ TrackViewList::iterator i;
+ framepos_t closest = max_framepos;
+ boost::shared_ptr<Region> ret;
+ framepos_t rpos = 0;
+
+ framepos_t track_frame;
+ RouteTimeAxisView *rtav;
+
+ for (i = tracks.begin(); i != tracks.end(); ++i) {
+
+ framecnt_t distance;
+ boost::shared_ptr<Region> r;
+
+ track_frame = frame;
+
+ if ((r = (*i)->find_next_region (track_frame, point, dir)) == 0) {
+ continue;
+ }
+
+ switch (point) {
+ case Start:
+ rpos = r->first_frame ();
+ break;
+
+ case End:
+ rpos = r->last_frame ();
+ break;
+
+ case SyncPoint:
+ rpos = r->sync_position ();
+ break;
+ }
+
+ if (rpos > frame) {
+ distance = rpos - frame;
+ } else {
+ distance = frame - rpos;
+ }
+
+ if (distance < closest) {
+ closest = distance;
+ if (ontrack != 0)
+ *ontrack = (*i);
+ ret = r;
+ }
+ }
+
+ return ret;
+}
+
+framepos_t
+Editor::find_next_region_boundary (framepos_t pos, int32_t dir, const TrackViewList& tracks)
+{
+ framecnt_t distance = max_framepos;
+ framepos_t current_nearest = -1;
+
+ for (TrackViewList::const_iterator i = tracks.begin(); i != tracks.end(); ++i) {
+ framepos_t contender;
+ framecnt_t d;
+
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
+
+ if (!rtv) {
+ continue;
+ }
+
+ if ((contender = rtv->find_next_region_boundary (pos, dir)) < 0) {
+ continue;
+ }
+
+ d = ::llabs (pos - contender);
+
+ if (d < distance) {
+ current_nearest = contender;
+ distance = d;
+ }
+ }
+
+ return current_nearest;
+}
+
+framepos_t
+Editor::get_region_boundary (framepos_t pos, int32_t dir, bool with_selection, bool only_onscreen)
+{
+ framepos_t target;
+ TrackViewList tvl;
+
+ if (with_selection && Config->get_region_boundaries_from_selected_tracks()) {
+
+ if (!selection->tracks.empty()) {
+
+ target = find_next_region_boundary (pos, dir, selection->tracks);
+
+ } else {
+
+ if (only_onscreen || Config->get_region_boundaries_from_onscreen_tracks()) {
+ get_onscreen_tracks (tvl);
+ target = find_next_region_boundary (pos, dir, tvl);
+ } else {
+ target = find_next_region_boundary (pos, dir, track_views);
+ }
+ }
+
+ } else {
+
+ if (only_onscreen || Config->get_region_boundaries_from_onscreen_tracks()) {
+ get_onscreen_tracks (tvl);
+ target = find_next_region_boundary (pos, dir, tvl);
+ } else {
+ target = find_next_region_boundary (pos, dir, track_views);
+ }
+ }
+
+ return target;
+}
+
+void
+Editor::cursor_to_region_boundary (bool with_selection, int32_t dir)
+{
+ framepos_t pos = playhead_cursor->current_frame ();
+ framepos_t target;
+
+ if (!_session) {
+ return;
+ }
+
+ // so we don't find the current region again..
+ if (dir > 0 || pos > 0) {
+ pos += dir;
+ }
+
+ if ((target = get_region_boundary (pos, dir, with_selection, false)) < 0) {
+ return;
+ }
+
+ _session->request_locate (target);
+}
+
+void
+Editor::cursor_to_next_region_boundary (bool with_selection)
+{
+ cursor_to_region_boundary (with_selection, 1);
+}
+
+void
+Editor::cursor_to_previous_region_boundary (bool with_selection)
+{
+ cursor_to_region_boundary (with_selection, -1);
+}
+
+void
+Editor::cursor_to_region_point (EditorCursor* cursor, RegionPoint point, int32_t dir)
+{
+ boost::shared_ptr<Region> r;
+ framepos_t pos = cursor->current_frame ();
+
+ if (!_session) {
+ return;
+ }
+
+ TimeAxisView *ontrack = 0;
+
+ // so we don't find the current region again..
+ if (dir>0 || pos>0)
+ pos+=dir;
+
+ if (!selection->tracks.empty()) {
+
+ r = find_next_region (pos, point, dir, selection->tracks, &ontrack);
+
+ } else if (clicked_axisview) {
+
+ TrackViewList t;
+ t.push_back (clicked_axisview);
+
+ r = find_next_region (pos, point, dir, t, &ontrack);
+
+ } else {
+
+ r = find_next_region (pos, point, dir, track_views, &ontrack);
+ }
+
+ if (r == 0) {
+ return;
+ }
+
+ switch (point) {
+ case Start:
+ pos = r->first_frame ();
+ break;
+
+ case End:
+ pos = r->last_frame ();
+ break;
+
+ case SyncPoint:
+ pos = r->sync_position ();
+ break;
+ }
+
+ if (cursor == playhead_cursor) {
+ _session->request_locate (pos);
+ } else {
+ cursor->set_position (pos);
+ }
+}
+
+void
+Editor::cursor_to_next_region_point (EditorCursor* cursor, RegionPoint point)
+{
+ cursor_to_region_point (cursor, point, 1);
+}
+
+void
+Editor::cursor_to_previous_region_point (EditorCursor* cursor, RegionPoint point)
+{
+ cursor_to_region_point (cursor, point, -1);
+}
+
+void
+Editor::cursor_to_selection_start (EditorCursor *cursor)
+{
+ framepos_t pos = 0;
+
+ switch (mouse_mode) {
+ case MouseObject:
+ if (!selection->regions.empty()) {
+ pos = selection->regions.start();
+ }
+ break;
+
+ case MouseRange:
+ if (!selection->time.empty()) {
+ pos = selection->time.start ();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ if (cursor == playhead_cursor) {
+ _session->request_locate (pos);
+ } else {
+ cursor->set_position (pos);
+ }
+}
+
+void
+Editor::cursor_to_selection_end (EditorCursor *cursor)
+{
+ framepos_t pos = 0;
+
+ switch (mouse_mode) {
+ case MouseObject:
+ if (!selection->regions.empty()) {
+ pos = selection->regions.end_frame();
+ }
+ break;
+
+ case MouseRange:
+ if (!selection->time.empty()) {
+ pos = selection->time.end_frame ();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ if (cursor == playhead_cursor) {
+ _session->request_locate (pos);
+ } else {
+ cursor->set_position (pos);
+ }
+}
+
+void
+Editor::selected_marker_to_region_boundary (bool with_selection, int32_t dir)
+{
+ framepos_t target;
+ Location* loc;
+ bool ignored;
+
+ if (!_session) {
+ return;
+ }
+
+ if (selection->markers.empty()) {
+ framepos_t mouse;
+ bool ignored;
+
+ if (!mouse_frame (mouse, ignored)) {
+ return;
+ }
+
+ add_location_mark (mouse);
+ }
+
+ if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) {
+ return;
+ }
+
+ framepos_t pos = loc->start();
+
+ // so we don't find the current region again..
+ if (dir > 0 || pos > 0) {
+ pos += dir;
+ }
+
+ if ((target = get_region_boundary (pos, dir, with_selection, false)) < 0) {
+ return;
+ }
+
+ loc->move_to (target, 0);
+}
+
+void
+Editor::selected_marker_to_next_region_boundary (bool with_selection)
+{
+ selected_marker_to_region_boundary (with_selection, 1);
+}
+
+void
+Editor::selected_marker_to_previous_region_boundary (bool with_selection)
+{
+ selected_marker_to_region_boundary (with_selection, -1);
+}
+
+void
+Editor::selected_marker_to_region_point (RegionPoint point, int32_t dir)
+{
+ boost::shared_ptr<Region> r;
+ framepos_t pos;
+ Location* loc;
+ bool ignored;
+
+ if (!_session || selection->markers.empty()) {
+ return;
+ }
+
+ if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) {
+ return;
+ }
+
+ TimeAxisView *ontrack = 0;
+
+ pos = loc->start();
+
+ // so we don't find the current region again..
+ if (dir>0 || pos>0)
+ pos+=dir;
+
+ if (!selection->tracks.empty()) {
+
+ r = find_next_region (pos, point, dir, selection->tracks, &ontrack);
+
+ } else {
+
+ r = find_next_region (pos, point, dir, track_views, &ontrack);
+ }
+
+ if (r == 0) {
+ return;
+ }
+
+ switch (point) {
+ case Start:
+ pos = r->first_frame ();
+ break;
+
+ case End:
+ pos = r->last_frame ();
+ break;
+
+ case SyncPoint:
+ pos = r->adjust_to_sync (r->first_frame());
+ break;
+ }
+
+ loc->move_to (pos, 0);
+}
+
+void
+Editor::selected_marker_to_next_region_point (RegionPoint point)
+{
+ selected_marker_to_region_point (point, 1);
+}
+
+void
+Editor::selected_marker_to_previous_region_point (RegionPoint point)
+{
+ selected_marker_to_region_point (point, -1);
+}
+
+void
+Editor::selected_marker_to_selection_start ()
+{
+ framepos_t pos = 0;
+ Location* loc;
+ bool ignored;
+
+ if (!_session || selection->markers.empty()) {
+ return;
+ }
+
+ if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) {
+ return;
+ }
+
+ switch (mouse_mode) {
+ case MouseObject:
+ if (!selection->regions.empty()) {
+ pos = selection->regions.start();
+ }
+ break;
+
+ case MouseRange:
+ if (!selection->time.empty()) {
+ pos = selection->time.start ();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ loc->move_to (pos, 0);
+}
+
+void
+Editor::selected_marker_to_selection_end ()
+{
+ framepos_t pos = 0;
+ Location* loc;
+ bool ignored;
+
+ if (!_session || selection->markers.empty()) {
+ return;
+ }
+
+ if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) {
+ return;
+ }
+
+ switch (mouse_mode) {
+ case MouseObject:
+ if (!selection->regions.empty()) {
+ pos = selection->regions.end_frame();
+ }
+ break;
+
+ case MouseRange:
+ if (!selection->time.empty()) {
+ pos = selection->time.end_frame ();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ loc->move_to (pos, 0);
+}
+
+void
+Editor::scroll_playhead (bool forward)
+{
+ framepos_t pos = playhead_cursor->current_frame ();
+ framecnt_t delta = (framecnt_t) floor (current_page_samples() / 0.8);
+
+ if (forward) {
+ if (pos == max_framepos) {
+ return;
+ }
+
+ if (pos < max_framepos - delta) {
+ pos += delta ;
+ } else {
+ pos = max_framepos;
+ }
+
+ } else {
+
+ if (pos == 0) {
+ return;
+ }
+
+ if (pos > delta) {
+ pos -= delta;
+ } else {
+ pos = 0;
+ }
+ }
+
+ _session->request_locate (pos);
+}
+
+void
+Editor::cursor_align (bool playhead_to_edit)
+{
+ if (!_session) {
+ return;
+ }
+
+ if (playhead_to_edit) {
+
+ if (selection->markers.empty()) {
+ return;
+ }
+
+ _session->request_locate (selection->markers.front()->position(), _session->transport_rolling());
+
+ } else {
+ const int32_t divisions = get_grid_music_divisions (0);
+ /* move selected markers to playhead */
+
+ for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) {
+ bool ignored;
+
+ Location* loc = find_location_from_marker (*i, ignored);
+
+ if (loc->is_mark()) {
+ loc->set_start (playhead_cursor->current_frame (), false, true, divisions);
+ } else {
+ loc->set (playhead_cursor->current_frame (),
+ playhead_cursor->current_frame () + loc->length(), true, divisions);
+ }
+ }
+ }
+}
+
+void
+Editor::scroll_backward (float pages)
+{
+ framepos_t const one_page = (framepos_t) rint (_visible_canvas_width * samples_per_pixel);
+ framepos_t const cnt = (framepos_t) floor (pages * one_page);
+
+ framepos_t frame;
+ if (leftmost_frame < cnt) {
+ frame = 0;
+ } else {
+ frame = leftmost_frame - cnt;
+ }
+
+ reset_x_origin (frame);
+}
+
+void
+Editor::scroll_forward (float pages)
+{
+ framepos_t const one_page = (framepos_t) rint (_visible_canvas_width * samples_per_pixel);
+ framepos_t const cnt = (framepos_t) floor (pages * one_page);
+
+ framepos_t frame;
+ if (max_framepos - cnt < leftmost_frame) {
+ frame = max_framepos - cnt;
+ } else {
+ frame = leftmost_frame + cnt;
+ }
+
+ reset_x_origin (frame);
+}
+
+void
+Editor::scroll_tracks_down ()
+{
+ double vert_value = vertical_adjustment.get_value() + vertical_adjustment.get_page_size();
+ if (vert_value > vertical_adjustment.get_upper() - _visible_canvas_height) {
+ vert_value = vertical_adjustment.get_upper() - _visible_canvas_height;
+ }
+
+ vertical_adjustment.set_value (vert_value);
+}
+
+void
+Editor::scroll_tracks_up ()
+{
+ vertical_adjustment.set_value (vertical_adjustment.get_value() - vertical_adjustment.get_page_size());
+}
+
+void
+Editor::scroll_tracks_down_line ()
+{
+ double vert_value = vertical_adjustment.get_value() + 60;
+
+ if (vert_value > vertical_adjustment.get_upper() - _visible_canvas_height) {
+ vert_value = vertical_adjustment.get_upper() - _visible_canvas_height;
+ }
+
+ vertical_adjustment.set_value (vert_value);
+}
+
+void
+Editor::scroll_tracks_up_line ()
+{
+ reset_y_origin (vertical_adjustment.get_value() - 60);
+}
+
+void
+Editor::select_topmost_track ()
+{
+ const double top_of_trackviews = vertical_adjustment.get_value();
+ for (TrackViewList::iterator t = track_views.begin(); t != track_views.end(); ++t) {
+ if ((*t)->hidden()) {
+ continue;
+ }
+ std::pair<TimeAxisView*,double> res = (*t)->covers_y_position (top_of_trackviews);
+ if (res.first) {
+ selection->set (*t);
+ break;
+ }
+ }
+}
+
+bool
+Editor::scroll_down_one_track (bool skip_child_views)
+{
+ TrackViewList::reverse_iterator next = track_views.rend();
+ const double top_of_trackviews = vertical_adjustment.get_value();
+
+ for (TrackViewList::reverse_iterator t = track_views.rbegin(); t != track_views.rend(); ++t) {
+ if ((*t)->hidden()) {
+ continue;
+ }
+
+ /* If this is the upper-most visible trackview, we want to display
+ * the one above it (next)
+ *
+ * Note that covers_y_position() is recursive and includes child views
+ */
+ std::pair<TimeAxisView*,double> res = (*t)->covers_y_position (top_of_trackviews);
+
+ if (res.first) {
+ if (skip_child_views) {
+ break;
+ }
+ /* automation lane (one level, non-recursive)
+ *
+ * - if no automation lane exists -> move to next tack
+ * - if the first (here: bottom-most) matches -> move to next tack
+ * - if no y-axis match is found -> the current track is at the top
+ * -> move to last (here: top-most) automation lane
+ */
+ TimeAxisView::Children kids = (*t)->get_child_list();
+ TimeAxisView::Children::reverse_iterator nkid = kids.rend();
+
+ for (TimeAxisView::Children::reverse_iterator ci = kids.rbegin(); ci != kids.rend(); ++ci) {
+ if ((*ci)->hidden()) {
+ continue;
+ }
+
+ std::pair<TimeAxisView*,double> dev;
+ dev = (*ci)->covers_y_position (top_of_trackviews);
+ if (dev.first) {
+ /* some automation lane is currently at the top */
+ if (ci == kids.rbegin()) {
+ /* first (bottom-most) autmation lane is at the top.
+ * -> move to next track
+ */
+ nkid = kids.rend();
+ }
+ break;
+ }
+ nkid = ci;
+ }
+
+ if (nkid != kids.rend()) {
+ ensure_time_axis_view_is_visible (**nkid, true);
+ return true;
+ }
+ break;
+ }
+ next = t;
+ }
+
+ /* move to the track below the first one that covers the */
+
+ if (next != track_views.rend()) {
+ ensure_time_axis_view_is_visible (**next, true);
+ return true;
+ }
+
+ return false;
+}
+
+bool
+Editor::scroll_up_one_track (bool skip_child_views)
+{
+ TrackViewList::iterator prev = track_views.end();
+ double top_of_trackviews = vertical_adjustment.get_value ();
+
+ for (TrackViewList::iterator t = track_views.begin(); t != track_views.end(); ++t) {
+
+ if ((*t)->hidden()) {
+ continue;
+ }
+
+ /* find the trackview at the top of the trackview group
+ *
+ * Note that covers_y_position() is recursive and includes child views
+ */
+ std::pair<TimeAxisView*,double> res = (*t)->covers_y_position (top_of_trackviews);
+
+ if (res.first) {
+ if (skip_child_views) {
+ break;
+ }
+ /* automation lane (one level, non-recursive)
+ *
+ * - if no automation lane exists -> move to prev tack
+ * - if no y-axis match is found -> the current track is at the top -> move to prev track
+ * (actually last automation lane of previous track, see below)
+ * - if first (top-most) lane is at the top -> move to this track
+ * - else move up one lane
+ */
+ TimeAxisView::Children kids = (*t)->get_child_list();
+ TimeAxisView::Children::iterator pkid = kids.end();
+
+ for (TimeAxisView::Children::iterator ci = kids.begin(); ci != kids.end(); ++ci) {
+ if ((*ci)->hidden()) {
+ continue;
+ }
+
+ std::pair<TimeAxisView*,double> dev;
+ dev = (*ci)->covers_y_position (top_of_trackviews);
+ if (dev.first) {
+ /* some automation lane is currently at the top */
+ if (ci == kids.begin()) {
+ /* first (top-most) autmation lane is at the top.
+ * jump directly to this track's top
+ */
+ ensure_time_axis_view_is_visible (**t, true);
+ return true;
+ }
+ else if (pkid != kids.end()) {
+ /* some other automation lane is at the top.
+ * move up to prev automation lane.
+ */
+ ensure_time_axis_view_is_visible (**pkid, true);
+ return true;
+ }
+ assert(0); // not reached
+ break;
+ }
+ pkid = ci;
+ }
+ break;
+ }
+
+ prev = t;
+ }
+
+ if (prev != track_views.end()) {
+ // move to bottom-most automation-lane of the previous track
+ TimeAxisView::Children kids = (*prev)->get_child_list();
+ TimeAxisView::Children::reverse_iterator pkid = kids.rend();
+ if (!skip_child_views) {
+ // find the last visible lane
+ for (TimeAxisView::Children::reverse_iterator ci = kids.rbegin(); ci != kids.rend(); ++ci) {
+ if (!(*ci)->hidden()) {
+ pkid = ci;
+ break;
+ }
+ }
+ }
+ if (pkid != kids.rend()) {
+ ensure_time_axis_view_is_visible (**pkid, true);
+ } else {
+ ensure_time_axis_view_is_visible (**prev, true);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void
+Editor::scroll_left_step ()
+{
+ framepos_t xdelta = (current_page_samples() / 8);
+
+ if (leftmost_frame > xdelta) {
+ reset_x_origin (leftmost_frame - xdelta);
+ } else {
+ reset_x_origin (0);
+ }
+}
+
+
+void
+Editor::scroll_right_step ()
+{
+ framepos_t xdelta = (current_page_samples() / 8);
+
+ if (max_framepos - xdelta > leftmost_frame) {
+ reset_x_origin (leftmost_frame + xdelta);
+ } else {
+ reset_x_origin (max_framepos - current_page_samples());
+ }
+}
+
+void
+Editor::scroll_left_half_page ()
+{
+ framepos_t xdelta = (current_page_samples() / 2);
+ if (leftmost_frame > xdelta) {
+ reset_x_origin (leftmost_frame - xdelta);
+ } else {
+ reset_x_origin (0);
+ }
+}
+
+void
+Editor::scroll_right_half_page ()
+{
+ framepos_t xdelta = (current_page_samples() / 2);
+ if (max_framepos - xdelta > leftmost_frame) {
+ reset_x_origin (leftmost_frame + xdelta);
+ } else {
+ reset_x_origin (max_framepos - current_page_samples());
+ }
+}
+
+/* ZOOM */
+
+void
+Editor::tav_zoom_step (bool coarser)
+{
+ DisplaySuspender ds;
+
+ TrackViewList* ts;
+
+ if (selection->tracks.empty()) {
+ ts = &track_views;
+ } else {
+ ts = &selection->tracks;
+ }
+
+ for (TrackViewList::iterator i = ts->begin(); i != ts->end(); ++i) {
+ TimeAxisView *tv = (static_cast<TimeAxisView*>(*i));
+ tv->step_height (coarser);
+ }
+}
+
+void
+Editor::tav_zoom_smooth (bool coarser, bool force_all)
+{
+ DisplaySuspender ds;
+
+ TrackViewList* ts;
+
+ if (selection->tracks.empty() || force_all) {
+ ts = &track_views;
+ } else {
+ ts = &selection->tracks;
+ }
+
+ for (TrackViewList::iterator i = ts->begin(); i != ts->end(); ++i) {
+ TimeAxisView *tv = (static_cast<TimeAxisView*>(*i));
+ uint32_t h = tv->current_height ();
+
+ if (coarser) {
+ if (h > 5) {
+ h -= 5; // pixels
+ if (h >= TimeAxisView::preset_height (HeightSmall)) {
+ tv->set_height (h);
+ }
+ }
+ } else {
+ tv->set_height (h + 5);
+ }
+ }
+}
+
+void
+Editor::temporal_zoom_step_mouse_focus_scale (bool zoom_out, double scale)
+{
+ Editing::ZoomFocus temp_focus = zoom_focus;
+ zoom_focus = Editing::ZoomFocusMouse;
+ temporal_zoom_step_scale (zoom_out, scale);
+ zoom_focus = temp_focus;
+}
+
+void
+Editor::temporal_zoom_step_mouse_focus (bool zoom_out)
+{
+ temporal_zoom_step_mouse_focus_scale (zoom_out, 2.0);
+}
+
+void
+Editor::temporal_zoom_step (bool zoom_out)
+{
+ temporal_zoom_step_scale (zoom_out, 2.0);
+}
+
+void
+Editor::temporal_zoom_step_scale (bool zoom_out, double scale)
+{
+ ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_step, zoom_out, scale)
+
+ framecnt_t nspp = samples_per_pixel;
+
+ if (zoom_out) {
+ nspp *= scale;
+ if (nspp == samples_per_pixel) {
+ nspp *= 2.0;
+ }
+ } else {
+ nspp /= scale;
+ if (nspp == samples_per_pixel) {
+ nspp /= 2.0;
+ }
+ }
+
+ // ToDo: encapsulate all of this into something like editor::get_session_extents() or editor::leftmost(), rightmost()
+ {
+ //ToDo: also incorporate automation regions (in case the session has no audio/midi but is just used for automating plugins or the like)
+
+ //calculate the extents of all regions in every playlist
+ framecnt_t session_extent_start = 0;
+ framecnt_t session_extent_end = 0;
+ {
+ boost::shared_ptr<RouteList> rl = _session->get_routes();
+ for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) {
+ boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*r);
+ if (tr) {
+ boost::shared_ptr<Playlist> pl = tr->playlist();
+ if (pl) {
+ pair<framepos_t, framepos_t> e;
+ e = pl->get_extent();
+ if (e.first < session_extent_start) {
+ session_extent_start = e.first;
+ }
+ if (e.second > session_extent_end) {
+ session_extent_end = e.second;
+ }
+ }
+ }
+ }
+ }
+ framecnt_t session_extents = session_extent_end - session_extent_start;
+
+ //in a session with no regions, use the start/end markers to set max zoom
+ framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
+ if ( session_length > session_extents )
+ session_extents = session_length;
+
+ //in a session with no regions or start/end markers, use 2 minutes to set max zoom
+ framecnt_t const min_length = _session->nominal_frame_rate()*60*2;
+ if ( min_length > session_extents )
+ session_extents = min_length;
+
+ //convert to samples-per-pixel and limit our zoom to this value
+ framecnt_t session_extents_pp = session_extents / _visible_canvas_width;
+ if (nspp > session_extents_pp)
+ nspp = session_extents_pp;
+ }
+
+ temporal_zoom (nspp);
+}
+
+void
+Editor::temporal_zoom (framecnt_t fpp)
+{
+ if (!_session) {
+ return;
+ }
+
+ framepos_t current_page = current_page_samples();
+ framepos_t current_leftmost = leftmost_frame;
+ framepos_t current_rightmost;
+ framepos_t current_center;
+ framepos_t new_page_size;
+ framepos_t half_page_size;
+ framepos_t leftmost_after_zoom = 0;
+ framepos_t where;
+ bool in_track_canvas;
+ bool use_mouse_frame = true;
+ framecnt_t nfpp;
+ double l;
+
+ if (fpp == samples_per_pixel) {
+ return;
+ }
+
+ // Imposing an arbitrary limit to zoom out as too much zoom out produces
+ // segfaults for lack of memory. If somebody decides this is not high enough I
+ // believe it can be raisen to higher values but some limit must be in place.
+ //
+ // This constant represents 1 day @ 48kHz on a 1600 pixel wide display
+ // all of which is used for the editor track displays. The whole day
+ // would be 4147200000 samples, so 2592000 samples per pixel.
+
+ nfpp = min (fpp, (framecnt_t) 2592000);
+ nfpp = max ((framecnt_t) 1, nfpp);
+
+ new_page_size = (framepos_t) floor (_visible_canvas_width * nfpp);
+ half_page_size = new_page_size / 2;
+
+ switch (zoom_focus) {
+ case ZoomFocusLeft:
+ leftmost_after_zoom = current_leftmost;
+ break;
+
+ case ZoomFocusRight:
+ current_rightmost = leftmost_frame + current_page;
+ if (current_rightmost < new_page_size) {
+ leftmost_after_zoom = 0;
+ } else {
+ leftmost_after_zoom = current_rightmost - new_page_size;
+ }
+ break;
+
+ case ZoomFocusCenter:
+ current_center = current_leftmost + (current_page/2);
+ if (current_center < half_page_size) {
+ leftmost_after_zoom = 0;
+ } else {
+ leftmost_after_zoom = current_center - half_page_size;
+ }
+ break;
+
+ case ZoomFocusPlayhead:
+ /* centre playhead */
+ l = playhead_cursor->current_frame () - (new_page_size * 0.5);
+
+ if (l < 0) {
+ leftmost_after_zoom = 0;
+ } else if (l > max_framepos) {
+ leftmost_after_zoom = max_framepos - new_page_size;
+ } else {
+ leftmost_after_zoom = (framepos_t) l;
+ }
+ break;
+
+ case ZoomFocusMouse:
+ /* try to keep the mouse over the same point in the display */
+
+ if (_drags->active()) {
+ where = _drags->current_pointer_frame ();
+ } else if (!mouse_frame (where, in_track_canvas)) {
+ use_mouse_frame = false;
+ }
+
+ if (use_mouse_frame) {
+ l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where);
+
+ if (l < 0) {
+ leftmost_after_zoom = 0;
+ } else if (l > max_framepos) {
+ leftmost_after_zoom = max_framepos - new_page_size;
+ } else {
+ leftmost_after_zoom = (framepos_t) l;
+ }
+ } else {
+ /* use playhead instead */
+ where = playhead_cursor->current_frame ();
+
+ if (where < half_page_size) {
+ leftmost_after_zoom = 0;
+ } else {
+ leftmost_after_zoom = where - half_page_size;
+ }
+ }
+ break;
+
+ case ZoomFocusEdit:
+ /* try to keep the edit point in the same place */
+ where = get_preferred_edit_position ();
+
+ if (where > 0) {
+
+ double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where);
+
+ if (l < 0) {
+ leftmost_after_zoom = 0;
+ } else if (l > max_framepos) {
+ leftmost_after_zoom = max_framepos - new_page_size;
+ } else {
+ leftmost_after_zoom = (framepos_t) l;
+ }
+
+ } else {
+ /* edit point not defined */
+ return;
+ }
+ break;
+
+ }
+
+ // leftmost_after_zoom = min (leftmost_after_zoom, _session->current_end_frame());
+
+ reposition_and_zoom (leftmost_after_zoom, nfpp);
+}
+
+void
+Editor::calc_extra_zoom_edges(framepos_t &start, framepos_t &end)
+{
+ /* this func helps make sure we leave a little space
+ at each end of the editor so that the zoom doesn't fit the region
+ precisely to the screen.
+ */
+
+ GdkScreen* screen = gdk_screen_get_default ();
+ const gint pixwidth = gdk_screen_get_width (screen);
+ const gint mmwidth = gdk_screen_get_width_mm (screen);
+ const double pix_per_mm = (double) pixwidth/ (double) mmwidth;
+ const double one_centimeter_in_pixels = pix_per_mm * 10.0;
+
+ const framepos_t range = end - start;
+ const framecnt_t new_fpp = (framecnt_t) ceil ((double) range / (double) _visible_canvas_width);
+ const framepos_t extra_samples = (framepos_t) floor (one_centimeter_in_pixels * new_fpp);
+
+ if (start > extra_samples) {
+ start -= extra_samples;
+ } else {
+ start = 0;
+ }
+
+ if (max_framepos - extra_samples > end) {
+ end += extra_samples;
+ } else {
+ end = max_framepos;
+ }
+}
+
+bool
+Editor::get_selection_extents (framepos_t &start, framepos_t &end) const
+{
+ start = max_framepos;
+ end = 0;
+ bool ret = true;
+
+ //ToDo: if notes are selected, set extents to that selection
+
+ //ToDo: if control points are selected, set extents to that selection
+
+ if ( !selection->regions.empty() ) {
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+
+ if ((*i)->region()->position() < start) {
+ start = (*i)->region()->position();
+ }
+
+ if ((*i)->region()->last_frame() + 1 > end) {
+ end = (*i)->region()->last_frame() + 1;
+ }
+ }
+
+ } else if (!selection->time.empty()) {
+ start = selection->time.start();
+ end = selection->time.end_frame();
+ } else
+ ret = false; //no selection found
+
+ //range check
+ if ((start == 0 && end == 0) || end < start) {
+ ret = false;
+ }
+
+ return ret;
+}
+
+
+void
+Editor::temporal_zoom_selection (Editing::ZoomAxis axes)
+{
+ if (!selection) return;
+
+ //ToDo: if notes are selected, zoom to that
+
+ //ToDo: if control points are selected, zoom to that
+
+ if (axes == Horizontal || axes == Both) {
+
+ framepos_t start, end;
+ if (get_selection_extents (start, end)) {
+ calc_extra_zoom_edges (start, end);
+ temporal_zoom_by_frame (start, end);
+ }
+ }
+
+ if (axes == Vertical || axes == Both) {
+ fit_selection ();
+ }
+}
+
+void
+Editor::temporal_zoom_session ()
+{
+ ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_session)
+
+ if (_session) {
+ framecnt_t start = _session->current_start_frame();
+ framecnt_t end = _session->current_end_frame();
+
+ if (_session->actively_recording () ) {
+ framepos_t cur = playhead_cursor->current_frame ();
+ if (cur > end) {
+ /* recording beyond the end marker; zoom out
+ * by 5 seconds more so that if 'follow
+ * playhead' is active we don't immediately
+ * scroll.
+ */
+ end = cur + _session->frame_rate() * 5;
+ }
+ }
+
+ if ((start == 0 && end == 0) || end < start) {
+ return;
+ }
+
+ calc_extra_zoom_edges(start, end);
+
+ temporal_zoom_by_frame (start, end);
+ }
+}
+
+void
+Editor::temporal_zoom_by_frame (framepos_t start, framepos_t end)
+{
+ if (!_session) return;
+
+ if ((start == 0 && end == 0) || end < start) {
+ return;
+ }
+
+ framepos_t range = end - start;
+
+ const framecnt_t new_fpp = (framecnt_t) ceil ((double) range / (double) _visible_canvas_width);
+
+ framepos_t new_page = range;
+ framepos_t middle = (framepos_t) floor ((double) start + ((double) range / 2.0f));
+ framepos_t new_leftmost = (framepos_t) floor ((double) middle - ((double) new_page / 2.0f));
+
+ if (new_leftmost > middle) {
+ new_leftmost = 0;
+ }
+
+ if (new_leftmost < 0) {
+ new_leftmost = 0;
+ }
+
+ reposition_and_zoom (new_leftmost, new_fpp);
+}
+
+void
+Editor::temporal_zoom_to_frame (bool coarser, framepos_t frame)
+{
+ if (!_session) {
+ return;
+ }
+
+ framecnt_t range_before = frame - leftmost_frame;
+ framecnt_t new_spp;
+
+ if (coarser) {
+ if (samples_per_pixel <= 1) {
+ new_spp = 2;
+ } else {
+ new_spp = samples_per_pixel + (samples_per_pixel/2);
+ }
+ range_before += range_before/2;
+ } else {
+ if (samples_per_pixel >= 1) {
+ new_spp = samples_per_pixel - (samples_per_pixel/2);
+ } else {
+ /* could bail out here since we cannot zoom any finer,
+ but leave that to the equality test below
+ */
+ new_spp = samples_per_pixel;
+ }
+
+ range_before -= range_before/2;
+ }
+
+ if (new_spp == samples_per_pixel) {
+ return;
+ }
+
+ /* zoom focus is automatically taken as @param frame when this
+ method is used.
+ */
+
+ framepos_t new_leftmost = frame - (framepos_t)range_before;
+
+ if (new_leftmost > frame) {
+ new_leftmost = 0;
+ }
+
+ if (new_leftmost < 0) {
+ new_leftmost = 0;
+ }
+
+ reposition_and_zoom (new_leftmost, new_spp);
+}
+
+
+bool
+Editor::choose_new_marker_name(string &name) {
+
+ if (!UIConfiguration::instance().get_name_new_markers()) {
+ /* don't prompt user for a new name */
+ return true;
+ }
+
+ Prompter dialog (true);
+
+ dialog.set_prompt (_("New Name:"));
+
+ dialog.set_title (_("New Location Marker"));
+
+ dialog.set_name ("MarkNameWindow");
+ dialog.set_size_request (250, -1);
+ dialog.set_position (Gtk::WIN_POS_MOUSE);
+
+ dialog.add_button (Stock::OK, RESPONSE_ACCEPT);
+ dialog.set_initial_text (name);
+
+ dialog.show ();
+
+ switch (dialog.run ()) {
+ case RESPONSE_ACCEPT:
+ break;
+ default:
+ return false;
+ }
+
+ dialog.get_result(name);
+ return true;
+
+}
+
+
+void
+Editor::add_location_from_selection ()
+{
+ string rangename;
+
+ if (selection->time.empty()) {
+ return;
+ }
+
+ if (_session == 0 || clicked_axisview == 0) {
+ return;
+ }
+
+ framepos_t start = selection->time[clicked_selection].start;
+ framepos_t end = selection->time[clicked_selection].end;
+
+ _session->locations()->next_available_name(rangename,"selection");
+ Location *location = new Location (*_session, start, end, rangename, Location::IsRangeMarker, get_grid_music_divisions(0));
+
+ begin_reversible_command (_("add marker"));
+
+ XMLNode &before = _session->locations()->get_state();
+ _session->locations()->add (location, true);
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+
+ commit_reversible_command ();
+}
+
+void
+Editor::add_location_mark (framepos_t where)
+{
+ string markername;
+
+ select_new_marker = true;
+
+ _session->locations()->next_available_name(markername,"mark");
+ if (!choose_new_marker_name(markername)) {
+ return;
+ }
+ Location *location = new Location (*_session, where, where, markername, Location::IsMark, get_grid_music_divisions (0));
+ begin_reversible_command (_("add marker"));
+
+ XMLNode &before = _session->locations()->get_state();
+ _session->locations()->add (location, true);
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+
+ commit_reversible_command ();
+}
+
+void
+Editor::set_session_start_from_playhead ()
+{
+ if (!_session)
+ return;
+
+ Location* loc;
+ if ((loc = _session->locations()->session_range_location()) == 0) { //should never happen
+ _session->set_session_extents ( _session->audible_frame(), _session->audible_frame() );
+ } else {
+ XMLNode &before = loc->get_state();
+
+ _session->set_session_extents ( _session->audible_frame(), loc->end() );
+
+ XMLNode &after = loc->get_state();
+
+ begin_reversible_command (_("Set session start"));
+
+ _session->add_command (new MementoCommand<Location>(*loc, &before, &after));
+
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::set_session_end_from_playhead ()
+{
+ if (!_session)
+ return;
+
+ Location* loc;
+ if ((loc = _session->locations()->session_range_location()) == 0) { //should never happen
+ _session->set_session_extents ( _session->audible_frame(), _session->audible_frame() );
+ } else {
+ XMLNode &before = loc->get_state();
+
+ _session->set_session_extents ( loc->start(), _session->audible_frame() );
+
+ XMLNode &after = loc->get_state();
+
+ begin_reversible_command (_("Set session start"));
+
+ _session->add_command (new MementoCommand<Location>(*loc, &before, &after));
+
+ commit_reversible_command ();
+ }
+
+ _session->set_end_is_free (false);
+}
+
+
+void
+Editor::toggle_location_at_playhead_cursor ()
+{
+ if (!do_remove_location_at_playhead_cursor())
+ {
+ add_location_from_playhead_cursor();
+ }
+}
+
+void
+Editor::add_location_from_playhead_cursor ()
+{
+ add_location_mark (_session->audible_frame());
+}
+
+bool
+Editor::do_remove_location_at_playhead_cursor ()
+{
+ bool removed = false;
+ if (_session) {
+ //set up for undo
+ XMLNode &before = _session->locations()->get_state();
+
+ //find location(s) at this time
+ Locations::LocationList locs;
+ _session->locations()->find_all_between (_session->audible_frame(), _session->audible_frame()+1, locs, Location::Flags(0));
+ for (Locations::LocationList::iterator i = locs.begin(); i != locs.end(); ++i) {
+ if ((*i)->is_mark()) {
+ _session->locations()->remove (*i);
+ removed = true;
+ }
+ }
+
+ //store undo
+ if (removed) {
+ begin_reversible_command (_("remove marker"));
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+ commit_reversible_command ();
+ }
+ }
+ return removed;
+}
+
+void
+Editor::remove_location_at_playhead_cursor ()
+{
+ do_remove_location_at_playhead_cursor ();
+}
+
+/** Add a range marker around each selected region */
+void
+Editor::add_locations_from_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+ bool commit = false;
+
+ XMLNode &before = _session->locations()->get_state();
+
+ for (RegionSelection::iterator i = rs.begin (); i != rs.end (); ++i) {
+
+ boost::shared_ptr<Region> region = (*i)->region ();
+
+ Location *location = new Location (*_session, region->position(), region->last_frame(), region->name(), Location::IsRangeMarker, 0);
+
+ _session->locations()->add (location, true);
+ commit = true;
+ }
+
+ if (commit) {
+ begin_reversible_command (selection->regions.size () > 1 ? _("add markers") : _("add marker"));
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+ commit_reversible_command ();
+ }
+}
+
+/** Add a single range marker around all selected regions */
+void
+Editor::add_location_from_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ XMLNode &before = _session->locations()->get_state();
+
+ string markername;
+
+ if (rs.size() > 1) {
+ _session->locations()->next_available_name(markername, "regions");
+ } else {
+ RegionView* rv = *(rs.begin());
+ boost::shared_ptr<Region> region = rv->region();
+ markername = region->name();
+ }
+
+ if (!choose_new_marker_name(markername)) {
+ return;
+ }
+
+ // single range spanning all selected
+ Location *location = new Location (*_session, selection->regions.start(), selection->regions.end_frame(), markername, Location::IsRangeMarker, 0);
+ _session->locations()->add (location, true);
+
+ begin_reversible_command (_("add marker"));
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+ commit_reversible_command ();
+}
+
+/* MARKS */
+
+void
+Editor::jump_forward_to_mark ()
+{
+ if (!_session) {
+ return;
+ }
+
+ framepos_t pos = _session->locations()->first_mark_after (playhead_cursor->current_frame());
+
+ if (pos < 0) {
+ return;
+ }
+
+ _session->request_locate (pos, _session->transport_rolling());
+}
+
+void
+Editor::jump_backward_to_mark ()
+{
+ if (!_session) {
+ return;
+ }
+
+ framepos_t pos = _session->locations()->first_mark_before (playhead_cursor->current_frame());
+
+ //handle the case where we are rolling, and we're less than one-half second past the mark, we want to go to the prior mark...
+ if ( _session->transport_rolling() ) {
+ if ( (playhead_cursor->current_frame() - pos) < _session->frame_rate()/2 ) {
+ framepos_t prior = _session->locations()->first_mark_before ( pos );
+ pos = prior;
+ }
+ }
+
+ if (pos < 0) {
+ return;
+ }
+
+ _session->request_locate (pos, _session->transport_rolling());
+}
+
+void
+Editor::set_mark ()
+{
+ framepos_t const pos = _session->audible_frame ();
+
+ string markername;
+ _session->locations()->next_available_name (markername, "mark");
+
+ if (!choose_new_marker_name (markername)) {
+ return;
+ }
+
+ _session->locations()->add (new Location (*_session, pos, 0, markername, Location::IsMark, 0), true);
+}
+
+void
+Editor::clear_markers ()
+{
+ if (_session) {
+ begin_reversible_command (_("clear markers"));
+
+ XMLNode &before = _session->locations()->get_state();
+ _session->locations()->clear_markers ();
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::clear_ranges ()
+{
+ if (_session) {
+ begin_reversible_command (_("clear ranges"));
+
+ XMLNode &before = _session->locations()->get_state();
+
+ _session->locations()->clear_ranges ();
+
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::clear_locations ()
+{
+ begin_reversible_command (_("clear locations"));
+
+ XMLNode &before = _session->locations()->get_state();
+ _session->locations()->clear ();
+ XMLNode &after = _session->locations()->get_state();
+ _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
+
+ commit_reversible_command ();
+}
+
+void
+Editor::unhide_markers ()
+{
+ for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
+ Location *l = (*i).first;
+ if (l->is_hidden() && l->is_mark()) {
+ l->set_hidden(false, this);
+ }
+ }
+}
+
+void
+Editor::unhide_ranges ()
+{
+ for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) {
+ Location *l = (*i).first;
+ if (l->is_hidden() && l->is_range_marker()) {
+ l->set_hidden(false, this);
+ }
+ }
+}
+
+/* INSERT/REPLACE */
+
+void
+Editor::insert_region_list_selection (float times)
+{
+ RouteTimeAxisView *tv = 0;
+ boost::shared_ptr<Playlist> playlist;
+
+ if (clicked_routeview != 0) {
+ tv = clicked_routeview;
+ } else if (!selection->tracks.empty()) {
+ if ((tv = dynamic_cast<RouteTimeAxisView*>(selection->tracks.front())) == 0) {
+ return;
+ }
+ } else if (entered_track != 0) {
+ if ((tv = dynamic_cast<RouteTimeAxisView*>(entered_track)) == 0) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ if ((playlist = tv->playlist()) == 0) {
+ return;
+ }
+
+ boost::shared_ptr<Region> region = _regions->get_single_selection ();
+ if (region == 0) {
+ return;
+ }
+
+ begin_reversible_command (_("insert region"));
+ playlist->clear_changes ();
+ playlist->add_region ((RegionFactory::create (region, true)), get_preferred_edit_position(), times);
+ if (Config->get_edit_mode() == Ripple)
+ playlist->ripple (get_preferred_edit_position(), region->length() * times, boost::shared_ptr<Region>());
+
+ _session->add_command(new StatefulDiffCommand (playlist));
+ commit_reversible_command ();
+}
+
+/* BUILT-IN EFFECTS */
+
+void
+Editor::reverse_selection ()
+{
+
+}
+
+/* GAIN ENVELOPE EDITING */
+
+void
+Editor::edit_envelope ()
+{
+}
+
+/* PLAYBACK */
+
+void
+Editor::transition_to_rolling (bool fwd)
+{
+ if (!_session) {
+ return;
+ }
+
+ if (_session->config.get_external_sync()) {
+ switch (Config->get_sync_source()) {
+ case Engine:
+ break;
+ default:
+ /* transport controlled by the master */
+ return;
+ }
+ }
+
+ if (_session->is_auditioning()) {
+ _session->cancel_audition ();
+ return;
+ }
+
+ _session->request_transport_speed (fwd ? 1.0f : -1.0f);
+}
+
+void
+Editor::play_from_start ()
+{
+ _session->request_locate (_session->current_start_frame(), true);
+}
+
+void
+Editor::play_from_edit_point ()
+{
+ _session->request_locate (get_preferred_edit_position(), true);
+}
+
+void
+Editor::play_from_edit_point_and_return ()
+{
+ framepos_t start_frame;
+ framepos_t return_frame;
+
+ start_frame = get_preferred_edit_position ( EDIT_IGNORE_PHEAD );
+
+ if (_session->transport_rolling()) {
+ _session->request_locate (start_frame, false);
+ return;
+ }
+
+ /* don't reset the return frame if its already set */
+
+ if ((return_frame = _session->requested_return_frame()) < 0) {
+ return_frame = _session->audible_frame();
+ }
+
+ if (start_frame >= 0) {
+ _session->request_roll_at_and_return (start_frame, return_frame);
+ }
+}
+
+void
+Editor::play_selection ()
+{
+ framepos_t start, end;
+ if (!get_selection_extents ( start, end))
+ return;
+
+ AudioRange ar (start, end, 0);
+ list<AudioRange> lar;
+ lar.push_back (ar);
+
+ _session->request_play_range (&lar, true);
+}
+
+
+void
+Editor::maybe_locate_with_edit_preroll (framepos_t location)
+{
+ if ( _session->transport_rolling() || !UIConfiguration::instance().get_follow_edits() || _session->config.get_external_sync() )
+ return;
+
+ location -= _session->preroll_samples (location);
+
+ //don't try to locate before the beginning of time
+ if (location < 0) {
+ location = 0;
+ }
+
+ //if follow_playhead is on, keep the playhead on the screen
+ if ( _follow_playhead )
+ if ( location < leftmost_frame )
+ location = leftmost_frame;
+
+ _session->request_locate( location );
+}
+
+void
+Editor::play_with_preroll ()
+{
+ framepos_t start, end;
+ if ( UIConfiguration::instance().get_follow_edits() && get_selection_extents ( start, end) ) {
+ const framepos_t preroll = _session->preroll_samples (start);
+
+ framepos_t ret = start;
+
+ if (start > preroll) {
+ start = start - preroll;
+ }
+
+ end = end + preroll; //"post-roll"
+
+ AudioRange ar (start, end, 0);
+ list<AudioRange> lar;
+ lar.push_back (ar);
+
+ _session->request_play_range (&lar, true);
+ _session->set_requested_return_frame (ret); //force auto-return to return to range start, without the preroll
+ } else {
+ framepos_t ph = playhead_cursor->current_frame ();
+ const framepos_t preroll = _session->preroll_samples (ph);
+ framepos_t start;
+ if (ph > preroll) {
+ start = ph - preroll;
+ } else {
+ start = 0;
+ }
+ _session->request_locate (start, true);
+ _session->set_requested_return_frame (ph); //force auto-return to return to playhead location, without the preroll
+ }
+}
+
+void
+Editor::rec_with_preroll ()
+{
+ framepos_t ph = playhead_cursor->current_frame ();
+ framepos_t preroll = _session->preroll_samples (ph);
+ _session->request_preroll_record_trim (ph, preroll);
+}
+
+void
+Editor::rec_with_count_in ()
+{
+ _session->request_count_in_record ();
+}
+
+void
+Editor::play_location (Location& location)
+{
+ if (location.start() <= location.end()) {
+ return;
+ }
+
+ _session->request_bounded_roll (location.start(), location.end());
+}
+
+void
+Editor::loop_location (Location& location)
+{
+ if (location.start() <= location.end()) {
+ return;
+ }
+
+ Location* tll;
+
+ if ((tll = transport_loop_location()) != 0) {
+ tll->set (location.start(), location.end());
+
+ // enable looping, reposition and start rolling
+ _session->request_locate (tll->start(), true);
+ _session->request_play_loop (true);
+ }
+}
+
+void
+Editor::do_layer_operation (LayerOperation op)
+{
+ if (selection->regions.empty ()) {
+ return;
+ }
+
+ bool const multiple = selection->regions.size() > 1;
+ switch (op) {
+ case Raise:
+ if (multiple) {
+ begin_reversible_command (_("raise regions"));
+ } else {
+ begin_reversible_command (_("raise region"));
+ }
+ break;
+
+ case RaiseToTop:
+ if (multiple) {
+ begin_reversible_command (_("raise regions to top"));
+ } else {
+ begin_reversible_command (_("raise region to top"));
+ }
+ break;
+
+ case Lower:
+ if (multiple) {
+ begin_reversible_command (_("lower regions"));
+ } else {
+ begin_reversible_command (_("lower region"));
+ }
+ break;
+
+ case LowerToBottom:
+ if (multiple) {
+ begin_reversible_command (_("lower regions to bottom"));
+ } else {
+ begin_reversible_command (_("lower region"));
+ }
+ break;
+ }
+
+ set<boost::shared_ptr<Playlist> > playlists = selection->regions.playlists ();
+ for (set<boost::shared_ptr<Playlist> >::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ (*i)->clear_owned_changes ();
+ }
+
+ for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
+ boost::shared_ptr<Region> r = (*i)->region ();
+ switch (op) {
+ case Raise:
+ r->raise ();
+ break;
+ case RaiseToTop:
+ r->raise_to_top ();
+ break;
+ case Lower:
+ r->lower ();
+ break;
+ case LowerToBottom:
+ r->lower_to_bottom ();
+ }
+ }
+
+ for (set<boost::shared_ptr<Playlist> >::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+ vector<Command*> cmds;
+ (*i)->rdiff (cmds);
+ _session->add_commands (cmds);
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::raise_region ()
+{
+ do_layer_operation (Raise);
+}
+
+void
+Editor::raise_region_to_top ()
+{
+ do_layer_operation (RaiseToTop);
+}
+
+void
+Editor::lower_region ()
+{
+ do_layer_operation (Lower);
+}
+
+void
+Editor::lower_region_to_bottom ()
+{
+ do_layer_operation (LowerToBottom);
+}
+
+/** Show the region editor for the selected regions */
+void
+Editor::show_region_properties ()
+{
+ selection->foreach_regionview (&RegionView::show_region_editor);
+}
+
+/** Show the midi list editor for the selected MIDI regions */
+void
+Editor::show_midi_list_editor ()
+{
+ selection->foreach_midi_regionview (&MidiRegionView::show_list_editor);
+}
+
+void
+Editor::rename_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ ArdourDialog d (_("Rename Region"), true, false);
+ Entry entry;
+ Label label (_("New name:"));
+ HBox hbox;
+
+ hbox.set_spacing (6);
+ hbox.pack_start (label, false, false);
+ hbox.pack_start (entry, true, true);
+
+ d.get_vbox()->set_border_width (12);
+ d.get_vbox()->pack_start (hbox, false, false);
+
+ d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ d.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ d.set_size_request (300, -1);
+
+ entry.set_text (rs.front()->region()->name());
+ entry.select_region (0, -1);
+
+ entry.signal_activate().connect (sigc::bind (sigc::mem_fun (d, &Dialog::response), RESPONSE_OK));
+
+ d.show_all ();
+
+ entry.grab_focus();
+
+ int const ret = d.run();
+
+ d.hide ();
+
+ if (ret != RESPONSE_OK) {
+ return;
+ }
+
+ std::string str = entry.get_text();
+ strip_whitespace_edges (str);
+ if (!str.empty()) {
+ rs.front()->region()->set_name (str);
+ _regions->redisplay ();
+ }
+}
+
+/** Start an audition of the first selected region */
+void
+Editor::play_edit_range ()
+{
+ framepos_t start, end;
+
+ if (get_edit_op_range (start, end)) {
+ _session->request_bounded_roll (start, end);
+ }
+}
+
+void
+Editor::play_selected_region ()
+{
+ framepos_t start = max_framepos;
+ framepos_t end = 0;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ if ((*i)->region()->position() < start) {
+ start = (*i)->region()->position();
+ }
+ if ((*i)->region()->last_frame() + 1 > end) {
+ end = (*i)->region()->last_frame() + 1;
+ }
+ }
+
+ _session->request_bounded_roll (start, end);
+}
+
+void
+Editor::audition_playlist_region_standalone (boost::shared_ptr<Region> region)
+{
+ _session->audition_region (region);
+}
+
+void
+Editor::region_from_selection ()
+{
+ if (clicked_axisview == 0) {
+ return;
+ }
+
+ if (selection->time.empty()) {
+ return;
+ }
+
+ framepos_t start = selection->time[clicked_selection].start;
+ framepos_t end = selection->time[clicked_selection].end;
+
+ TrackViewList tracks = get_tracks_for_range_action ();
+
+ framepos_t selection_cnt = end - start + 1;
+
+ for (TrackSelection::iterator i = tracks.begin(); i != tracks.end(); ++i) {
+ boost::shared_ptr<Region> current;
+ boost::shared_ptr<Playlist> pl;
+ framepos_t internal_start;
+ string new_name;
+
+ if ((pl = (*i)->playlist()) == 0) {
+ continue;
+ }
+
+ if ((current = pl->top_region_at (start)) == 0) {
+ continue;
+ }
+
+ internal_start = start - current->position();
+ RegionFactory::region_name (new_name, current->name(), true);
+
+ PropertyList plist;
+
+ plist.add (ARDOUR::Properties::start, current->start() + internal_start);
+ plist.add (ARDOUR::Properties::length, selection_cnt);
+ plist.add (ARDOUR::Properties::name, new_name);
+ plist.add (ARDOUR::Properties::layer, 0);
+
+ boost::shared_ptr<Region> region (RegionFactory::create (current, plist));
+ }
+}
+
+void
+Editor::create_region_from_selection (vector<boost::shared_ptr<Region> >& new_regions)
+{
+ if (selection->time.empty() || selection->tracks.empty()) {
+ return;
+ }
+
+ framepos_t start, end;
+ if (clicked_selection) {
+ start = selection->time[clicked_selection].start;
+ end = selection->time[clicked_selection].end;
+ } else {
+ start = selection->time.start();
+ end = selection->time.end_frame();
+ }
+
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+ sort_track_selection (ts);
+
+ for (TrackSelection::iterator i = ts.begin(); i != ts.end(); ++i) {
+ boost::shared_ptr<Region> current;
+ boost::shared_ptr<Playlist> playlist;
+ framepos_t internal_start;
+ string new_name;
+
+ if ((playlist = (*i)->playlist()) == 0) {
+ continue;
+ }
+
+ if ((current = playlist->top_region_at(start)) == 0) {
+ continue;
+ }
+
+ internal_start = start - current->position();
+ RegionFactory::region_name (new_name, current->name(), true);
+
+ PropertyList plist;
+
+ plist.add (ARDOUR::Properties::start, current->start() + internal_start);
+ plist.add (ARDOUR::Properties::length, end - start + 1);
+ plist.add (ARDOUR::Properties::name, new_name);
+
+ new_regions.push_back (RegionFactory::create (current, plist));
+ }
+}
+
+void
+Editor::split_multichannel_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ vector< boost::shared_ptr<Region> > v;
+
+ for (list<RegionView*>::iterator x = rs.begin(); x != rs.end(); ++x) {
+ (*x)->region()->separate_by_channel (*_session, v);
+ }
+}
+
+void
+Editor::new_region_from_selection ()
+{
+ region_from_selection ();
+ cancel_selection ();
+}
+
+static void
+add_if_covered (RegionView* rv, const AudioRange* ar, RegionSelection* rs)
+{
+ switch (rv->region()->coverage (ar->start, ar->end - 1)) {
+ // n.b. -1 because AudioRange::end is one past the end, but coverage expects inclusive ranges
+ case Evoral::OverlapNone:
+ break;
+ default:
+ rs->push_back (rv);
+ }
+}
+
+/** Return either:
+ * - selected tracks, or if there are none...
+ * - tracks containing selected regions, or if there are none...
+ * - all tracks
+ * @return tracks.
+ */
+TrackViewList
+Editor::get_tracks_for_range_action () const
+{
+ TrackViewList t;
+
+ if (selection->tracks.empty()) {
+
+ /* use tracks with selected regions */
+
+ RegionSelection rs = selection->regions;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ TimeAxisView* tv = &(*i)->get_time_axis_view();
+
+ if (!t.contains (tv)) {
+ t.push_back (tv);
+ }
+ }
+
+ if (t.empty()) {
+ /* no regions and no tracks: use all tracks */
+ t = track_views;
+ }
+
+ } else {
+
+ t = selection->tracks;
+ }
+
+ return t.filter_to_unique_playlists();
+}
+
+void
+Editor::separate_regions_between (const TimeSelection& ts)
+{
+ bool in_command = false;
+ boost::shared_ptr<Playlist> playlist;
+ RegionSelection new_selection;
+
+ TrackViewList tmptracks = get_tracks_for_range_action ();
+ sort_track_selection (tmptracks);
+
+ for (TrackSelection::iterator i = tmptracks.begin(); i != tmptracks.end(); ++i) {
+
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> ((*i));
+
+ if (!rtv) {
+ continue;
+ }
+
+ if (!rtv->is_track()) {
+ continue;
+ }
+
+ /* no edits to destructive tracks */
+
+ if (rtv->track()->destructive()) {
+ continue;
+ }
+
+ if ((playlist = rtv->playlist()) != 0) {
+
+ playlist->clear_changes ();
+
+ /* XXX need to consider musical time selections here at some point */
+
+ for (list<AudioRange>::const_iterator t = ts.begin(); t != ts.end(); ++t) {
+
+ sigc::connection c = rtv->view()->RegionViewAdded.connect (
+ sigc::mem_fun(*this, &Editor::collect_new_region_view));
+
+ latest_regionviews.clear ();
+
+ playlist->partition ((*t).start, (*t).end, false);
+
+ c.disconnect ();
+
+ if (!latest_regionviews.empty()) {
+
+ rtv->view()->foreach_regionview (sigc::bind (
+ sigc::ptr_fun (add_if_covered),
+ &(*t), &new_selection));
+
+ if (!in_command) {
+ begin_reversible_command (_("separate"));
+ in_command = true;
+ }
+
+ /* pick up changes to existing regions */
+
+ vector<Command*> cmds;
+ playlist->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ /* pick up changes to the playlist itself (adds/removes)
+ */
+
+ _session->add_command(new StatefulDiffCommand (playlist));
+ }
+ }
+ }
+ }
+
+ if (in_command) {
+// selection->set (new_selection);
+
+ commit_reversible_command ();
+ }
+}
+
+struct PlaylistState {
+ boost::shared_ptr<Playlist> playlist;
+ XMLNode* before;
+};
+
+/** Take tracks from get_tracks_for_range_action and cut any regions
+ * on those tracks so that the tracks are empty over the time
+ * selection.
+ */
+void
+Editor::separate_region_from_selection ()
+{
+ /* preferentially use *all* ranges in the time selection if we're in range mode
+ to allow discontiguous operation, since get_edit_op_range() currently
+ returns a single range.
+ */
+
+ if (!selection->time.empty()) {
+
+ separate_regions_between (selection->time);
+
+ } else {
+
+ framepos_t start;
+ framepos_t end;
+
+ if (get_edit_op_range (start, end)) {
+
+ AudioRange ar (start, end, 1);
+ TimeSelection ts;
+ ts.push_back (ar);
+
+ separate_regions_between (ts);
+ }
+ }
+}
+
+void
+Editor::separate_region_from_punch ()
+{
+ Location* loc = _session->locations()->auto_punch_location();
+ if (loc) {
+ separate_regions_using_location (*loc);
+ }
+}
+
+void
+Editor::separate_region_from_loop ()
+{
+ Location* loc = _session->locations()->auto_loop_location();
+ if (loc) {
+ separate_regions_using_location (*loc);
+ }
+}
+
+void
+Editor::separate_regions_using_location (Location& loc)
+{
+ if (loc.is_mark()) {
+ return;
+ }
+
+ AudioRange ar (loc.start(), loc.end(), 1);
+ TimeSelection ts;
+
+ ts.push_back (ar);
+
+ separate_regions_between (ts);
+}
+
+/** Separate regions under the selected region */
+void
+Editor::separate_under_selected_regions ()
+{
+ vector<PlaylistState> playlists;
+
+ RegionSelection rs;
+
+ rs = get_regions_from_selection_and_entered();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("separate region under"));
+
+ list<boost::shared_ptr<Region> > regions_to_remove;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ // we can't just remove the region(s) in this loop because
+ // this removes them from the RegionSelection, and they thus
+ // disappear from underneath the iterator, and the ++i above
+ // SEGVs in a puzzling fashion.
+
+ // so, first iterate over the regions to be removed from rs and
+ // add them to the regions_to_remove list, and then
+ // iterate over the list to actually remove them.
+
+ regions_to_remove.push_back ((*i)->region());
+ }
+
+ for (list<boost::shared_ptr<Region> >::iterator rl = regions_to_remove.begin(); rl != regions_to_remove.end(); ++rl) {
+
+ boost::shared_ptr<Playlist> playlist = (*rl)->playlist();
+
+ if (!playlist) {
+ // is this check necessary?
+ continue;
+ }
+
+ vector<PlaylistState>::iterator i;
+
+ //only take state if this is a new playlist.
+ for (i = playlists.begin(); i != playlists.end(); ++i) {
+ if ((*i).playlist == playlist) {
+ break;
+ }
+ }
+
+ if (i == playlists.end()) {
+
+ PlaylistState before;
+ before.playlist = playlist;
+ before.before = &playlist->get_state();
+ playlist->clear_changes ();
+ playlist->freeze ();
+ playlists.push_back(before);
+ }
+
+ //Partition on the region bounds
+ playlist->partition ((*rl)->first_frame() - 1, (*rl)->last_frame() + 1, true);
+
+ //Re-add region that was just removed due to the partition operation
+ playlist->add_region( (*rl), (*rl)->first_frame() );
+ }
+
+ vector<PlaylistState>::iterator pl;
+
+ for (pl = playlists.begin(); pl != playlists.end(); ++pl) {
+ (*pl).playlist->thaw ();
+ _session->add_command(new MementoCommand<Playlist>(*(*pl).playlist, (*pl).before, &(*pl).playlist->get_state()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::crop_region_to_selection ()
+{
+ if (!selection->time.empty()) {
+
+ begin_reversible_command (_("Crop Regions to Time Selection"));
+ for (std::list<AudioRange>::iterator i = selection->time.begin(); i != selection->time.end(); ++i) {
+ crop_region_to ((*i).start, (*i).end);
+ }
+ commit_reversible_command();
+ } else {
+
+ framepos_t start;
+ framepos_t end;
+
+ if (get_edit_op_range (start, end)) {
+ begin_reversible_command (_("Crop Regions to Edit Range"));
+
+ crop_region_to (start, end);
+
+ commit_reversible_command();
+ }
+ }
+
+}
+
+void
+Editor::crop_region_to (framepos_t start, framepos_t end)
+{
+ vector<boost::shared_ptr<Playlist> > playlists;
+ boost::shared_ptr<Playlist> playlist;
+ TrackViewList ts;
+
+ if (selection->tracks.empty()) {
+ ts = track_views.filter_to_unique_playlists();
+ } else {
+ ts = selection->tracks.filter_to_unique_playlists ();
+ }
+
+ sort_track_selection (ts);
+
+ for (TrackSelection::iterator i = ts.begin(); i != ts.end(); ++i) {
+
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> ((*i));
+
+ if (!rtv) {
+ continue;
+ }
+
+ boost::shared_ptr<Track> t = rtv->track();
+
+ if (t != 0 && ! t->destructive()) {
+
+ if ((playlist = rtv->playlist()) != 0) {
+ playlists.push_back (playlist);
+ }
+ }
+ }
+
+ if (playlists.empty()) {
+ return;
+ }
+
+ framepos_t pos;
+ framepos_t new_start;
+ framepos_t new_end;
+ framecnt_t new_length;
+
+ for (vector<boost::shared_ptr<Playlist> >::iterator i = playlists.begin(); i != playlists.end(); ++i) {
+
+ /* Only the top regions at start and end have to be cropped */
+ boost::shared_ptr<Region> region_at_start = (*i)->top_region_at(start);
+ boost::shared_ptr<Region> region_at_end = (*i)->top_region_at(end);
+
+ vector<boost::shared_ptr<Region> > regions;
+
+ if (region_at_start != 0) {
+ regions.push_back (region_at_start);
+ }
+ if (region_at_end != 0) {
+ regions.push_back (region_at_end);
+ }
+
+ /* now adjust lengths */
+ for (vector<boost::shared_ptr<Region> >::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ pos = (*i)->position();
+ new_start = max (start, pos);
+ if (max_framepos - pos > (*i)->length()) {
+ new_end = pos + (*i)->length() - 1;
+ } else {
+ new_end = max_framepos;
+ }
+ new_end = min (end, new_end);
+ new_length = new_end - new_start + 1;
+
+ (*i)->clear_changes ();
+ (*i)->trim_to (new_start, new_length);
+ _session->add_command (new StatefulDiffCommand (*i));
+ }
+ }
+}
+
+void
+Editor::region_fill_track ()
+{
+ boost::shared_ptr<Playlist> playlist;
+ RegionSelection regions = get_regions_from_selection_and_entered ();
+ RegionSelection foo;
+
+ framepos_t const end = _session->current_end_frame ();
+
+ if (regions.empty () || regions.end_frame () + 1 >= end) {
+ return;
+ }
+
+ framepos_t const start_frame = regions.start ();
+ framepos_t const end_frame = regions.end_frame ();
+ framecnt_t const gap = end_frame - start_frame + 1;
+
+ begin_reversible_command (Operations::region_fill);
+
+ selection->clear_regions ();
+
+ for (RegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) {
+
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ TimeAxisView& tv = (*i)->get_time_axis_view();
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&tv);
+ latest_regionviews.clear ();
+ sigc::connection c = rtv->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
+
+ framepos_t const position = end_frame + (r->first_frame() - start_frame + 1);
+ playlist = (*i)->region()->playlist();
+ playlist->clear_changes ();
+ playlist->duplicate_until (r, position, gap, end);
+ _session->add_command(new StatefulDiffCommand (playlist));
+
+ c.disconnect ();
+
+ foo.insert (foo.end(), latest_regionviews.begin(), latest_regionviews.end());
+ }
+
+ if (!foo.empty()) {
+ selection->set (foo);
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::set_region_sync_position ()
+{
+ set_sync_point (get_preferred_edit_position (), get_regions_from_selection_and_edit_point ());
+}
+
+void
+Editor::set_sync_point (framepos_t where, const RegionSelection& rs)
+{
+ bool in_command = false;
+
+ for (RegionSelection::const_iterator r = rs.begin(); r != rs.end(); ++r) {
+
+ if (!(*r)->region()->covers (where)) {
+ continue;
+ }
+
+ boost::shared_ptr<Region> region ((*r)->region());
+
+ if (!in_command) {
+ begin_reversible_command (_("set sync point"));
+ in_command = true;
+ }
+
+ region->clear_changes ();
+ region->set_sync_position (where);
+ _session->add_command(new StatefulDiffCommand (region));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+/** Remove the sync positions of the selection */
+void
+Editor::remove_region_sync ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("remove region sync"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+
+ (*i)->region()->clear_changes ();
+ (*i)->region()->clear_sync_position ();
+ _session->add_command(new StatefulDiffCommand ((*i)->region()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::naturalize_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ if (rs.size() > 1) {
+ begin_reversible_command (_("move regions to original position"));
+ } else {
+ begin_reversible_command (_("move region to original position"));
+ }
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ (*i)->region()->clear_changes ();
+ (*i)->region()->move_to_natural_position ();
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::align_regions (RegionPoint what)
+{
+ RegionSelection const rs = get_regions_from_selection_and_edit_point ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("align selection"));
+
+ framepos_t const position = get_preferred_edit_position ();
+
+ for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
+ align_region_internal ((*i)->region(), what, position);
+ }
+
+ commit_reversible_command ();
+}
+
+struct RegionSortByTime {
+ bool operator() (const RegionView* a, const RegionView* b) {
+ return a->region()->position() < b->region()->position();
+ }
+};
+
+void
+Editor::align_regions_relative (RegionPoint point)
+{
+ RegionSelection const rs = get_regions_from_selection_and_edit_point ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ framepos_t const position = get_preferred_edit_position ();
+
+ framepos_t distance = 0;
+ framepos_t pos = 0;
+ int dir = 1;
+
+ list<RegionView*> sorted;
+ rs.by_position (sorted);
+
+ boost::shared_ptr<Region> r ((*sorted.begin())->region());
+
+ switch (point) {
+ case Start:
+ pos = position;
+ if (position > r->position()) {
+ distance = position - r->position();
+ } else {
+ distance = r->position() - position;
+ dir = -1;
+ }
+ break;
+
+ case End:
+ if (position > r->last_frame()) {
+ distance = position - r->last_frame();
+ pos = r->position() + distance;
+ } else {
+ distance = r->last_frame() - position;
+ pos = r->position() - distance;
+ dir = -1;
+ }
+ break;
+
+ case SyncPoint:
+ pos = r->adjust_to_sync (position);
+ if (pos > r->position()) {
+ distance = pos - r->position();
+ } else {
+ distance = r->position() - pos;
+ dir = -1;
+ }
+ break;
+ }
+
+ if (pos == r->position()) {
+ return;
+ }
+
+ begin_reversible_command (_("align selection (relative)"));
+
+ /* move first one specially */
+
+ r->clear_changes ();
+ r->set_position (pos);
+ _session->add_command(new StatefulDiffCommand (r));
+
+ /* move rest by the same amount */
+
+ sorted.pop_front();
+
+ for (list<RegionView*>::iterator i = sorted.begin(); i != sorted.end(); ++i) {
+
+ boost::shared_ptr<Region> region ((*i)->region());
+
+ region->clear_changes ();
+
+ if (dir > 0) {
+ region->set_position (region->position() + distance);
+ } else {
+ region->set_position (region->position() - distance);
+ }
+
+ _session->add_command(new StatefulDiffCommand (region));
+
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::align_region (boost::shared_ptr<Region> region, RegionPoint point, framepos_t position)
+{
+ begin_reversible_command (_("align region"));
+ align_region_internal (region, point, position);
+ commit_reversible_command ();
+}
+
+void
+Editor::align_region_internal (boost::shared_ptr<Region> region, RegionPoint point, framepos_t position)
+{
+ region->clear_changes ();
+
+ switch (point) {
+ case SyncPoint:
+ region->set_position (region->adjust_to_sync (position));
+ break;
+
+ case End:
+ if (position > region->length()) {
+ region->set_position (position - region->length());
+ }
+ break;
+
+ case Start:
+ region->set_position (position);
+ break;
+ }
+
+ _session->add_command(new StatefulDiffCommand (region));
+}
+
+void
+Editor::trim_region_front ()
+{
+ trim_region (true);
+}
+
+void
+Editor::trim_region_back ()
+{
+ trim_region (false);
+}
+
+void
+Editor::trim_region (bool front)
+{
+ framepos_t where = get_preferred_edit_position();
+ RegionSelection rs = get_regions_from_selection_and_edit_point ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (front ? _("trim front") : _("trim back"));
+
+ for (list<RegionView*>::const_iterator i = rs.by_layer().begin(); i != rs.by_layer().end(); ++i) {
+ if (!(*i)->region()->locked()) {
+
+ (*i)->region()->clear_changes ();
+
+ if (front) {
+ (*i)->region()->trim_front (where);
+ } else {
+ (*i)->region()->trim_end (where);
+ }
+
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+ }
+
+ commit_reversible_command ();
+}
+
+/** Trim the end of the selected regions to the position of the edit cursor */
+void
+Editor::trim_region_to_loop ()
+{
+ Location* loc = _session->locations()->auto_loop_location();
+ if (!loc) {
+ return;
+ }
+ trim_region_to_location (*loc, _("trim to loop"));
+}
+
+void
+Editor::trim_region_to_punch ()
+{
+ Location* loc = _session->locations()->auto_punch_location();
+ if (!loc) {
+ return;
+ }
+ trim_region_to_location (*loc, _("trim to punch"));
+}
+
+void
+Editor::trim_region_to_location (const Location& loc, const char* str)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+ bool in_command = false;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+ RegionView* rv = (*x);
+
+ /* require region to span proposed trim */
+ switch (rv->region()->coverage (loc.start(), loc.end())) {
+ case Evoral::OverlapInternal:
+ break;
+ default:
+ continue;
+ }
+
+ RouteTimeAxisView* tav = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view());
+ if (!tav) {
+ return;
+ }
+
+ framepos_t start;
+ framepos_t end;
+
+ start = loc.start();
+ end = loc.end();
+
+ rv->region()->clear_changes ();
+ rv->region()->trim_to (start, (end - start));
+
+ if (!in_command) {
+ begin_reversible_command (str);
+ in_command = true;
+ }
+ _session->add_command(new StatefulDiffCommand (rv->region()));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::trim_region_to_previous_region_end ()
+{
+ return trim_to_region(false);
+}
+
+void
+Editor::trim_region_to_next_region_start ()
+{
+ return trim_to_region(true);
+}
+
+void
+Editor::trim_to_region(bool forward)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+ bool in_command = false;
+
+ boost::shared_ptr<Region> next_region;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+
+ AudioRegionView* arv = dynamic_cast<AudioRegionView*> (*x);
+
+ if (!arv) {
+ continue;
+ }
+
+ AudioTimeAxisView* atav = dynamic_cast<AudioTimeAxisView*> (&arv->get_time_axis_view());
+
+ if (!atav) {
+ continue;
+ }
+
+ boost::shared_ptr<Region> region = arv->region();
+ boost::shared_ptr<Playlist> playlist (region->playlist());
+
+ region->clear_changes ();
+
+ if (forward) {
+
+ next_region = playlist->find_next_region (region->first_frame(), Start, 1);
+
+ if (!next_region) {
+ continue;
+ }
+
+ region->trim_end (next_region->first_frame() - 1);
+ arv->region_changed (PropertyChange (ARDOUR::Properties::length));
+ }
+ else {
+
+ next_region = playlist->find_next_region (region->first_frame(), Start, 0);
+
+ if (!next_region) {
+ continue;
+ }
+
+ region->trim_front (next_region->last_frame() + 1);
+ arv->region_changed (ARDOUR::bounds_change);
+ }
+
+ if (!in_command) {
+ begin_reversible_command (_("trim to region"));
+ in_command = true;
+ }
+ _session->add_command(new StatefulDiffCommand (region));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::unfreeze_route ()
+{
+ if (clicked_routeview == 0 || !clicked_routeview->is_track()) {
+ return;
+ }
+
+ clicked_routeview->track()->unfreeze ();
+}
+
+void*
+Editor::_freeze_thread (void* arg)
+{
+ return static_cast<Editor*>(arg)->freeze_thread ();
+}
+
+void*
+Editor::freeze_thread ()
+{
+ /* create event pool because we may need to talk to the session */
+ SessionEvent::create_per_thread_pool ("freeze events", 64);
+ /* create per-thread buffers for process() tree to use */
+ clicked_routeview->audio_track()->freeze_me (*current_interthread_info);
+ current_interthread_info->done = true;
+ return 0;
+}
+
+void
+Editor::freeze_route ()
+{
+ if (!_session) {
+ return;
+ }
+
+ /* stop transport before we start. this is important */
+
+ _session->request_transport_speed (0.0);
+
+ /* wait for just a little while, because the above call is asynchronous */
+
+ Glib::usleep (250000);
+
+ if (clicked_routeview == 0 || !clicked_routeview->is_audio_track()) {
+ return;
+ }
+
+ if (!clicked_routeview->track()->bounceable (clicked_routeview->track()->main_outs(), true)) {
+ MessageDialog d (
+ _("This track/bus cannot be frozen because the signal adds or loses channels before reaching the outputs.\n"
+ "This is typically caused by plugins that generate stereo output from mono input or vice versa.")
+ );
+ d.set_title (_("Cannot freeze"));
+ d.run ();
+ return;
+ }
+
+ if (clicked_routeview->track()->has_external_redirects()) {
+ MessageDialog d (string_compose (_("<b>%1</b>\n\nThis track has at least one send/insert/return as part of its signal flow.\n\n"
+ "Freezing will only process the signal as far as the first send/insert/return."),
+ clicked_routeview->track()->name()), true, MESSAGE_INFO, BUTTONS_NONE, true);
+
+ d.add_button (_("Freeze anyway"), Gtk::RESPONSE_OK);
+ d.add_button (_("Don't freeze"), Gtk::RESPONSE_CANCEL);
+ d.set_title (_("Freeze Limits"));
+
+ int response = d.run ();
+
+ switch (response) {
+ case Gtk::RESPONSE_CANCEL:
+ return;
+ default:
+ break;
+ }
+ }
+
+ InterThreadInfo itt;
+ current_interthread_info = &itt;
+
+ InterthreadProgressWindow ipw (current_interthread_info, _("Freeze"), _("Cancel Freeze"));
+
+ pthread_create_and_store (X_("freezer"), &itt.thread, _freeze_thread, this);
+
+ CursorContext::Handle cursor_ctx = CursorContext::create(*this, _cursors->wait);
+
+ while (!itt.done && !itt.cancel) {
+ gtk_main_iteration ();
+ }
+
+ pthread_join (itt.thread, 0);
+ current_interthread_info = 0;
+}
+
+void
+Editor::bounce_range_selection (bool replace, bool enable_processing)
+{
+ if (selection->time.empty()) {
+ return;
+ }
+
+ TrackSelection views = selection->tracks;
+
+ for (TrackViewList::iterator i = views.begin(); i != views.end(); ++i) {
+
+ if (enable_processing) {
+
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
+
+ if (rtv && rtv->track() && replace && enable_processing && !rtv->track()->bounceable (rtv->track()->main_outs(), false)) {
+ MessageDialog d (
+ _("You can't perform this operation because the processing of the signal "
+ "will cause one or more of the tracks to end up with a region with more channels than this track has inputs.\n\n"
+ "You can do this without processing, which is a different operation.")
+ );
+ d.set_title (_("Cannot bounce"));
+ d.run ();
+ return;
+ }
+ }
+ }
+
+ framepos_t start = selection->time[clicked_selection].start;
+ framepos_t end = selection->time[clicked_selection].end;
+ framepos_t cnt = end - start + 1;
+ bool in_command = false;
+
+ for (TrackViewList::iterator i = views.begin(); i != views.end(); ++i) {
+
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
+
+ if (!rtv) {
+ continue;
+ }
+
+ boost::shared_ptr<Playlist> playlist;
+
+ if ((playlist = rtv->playlist()) == 0) {
+ continue;
+ }
+
+ InterThreadInfo itt;
+
+ playlist->clear_changes ();
+ playlist->clear_owned_changes ();
+
+ boost::shared_ptr<Region> r;
+
+ if (enable_processing) {
+ r = rtv->track()->bounce_range (start, start+cnt, itt, rtv->track()->main_outs(), false);
+ } else {
+ r = rtv->track()->bounce_range (start, start+cnt, itt, boost::shared_ptr<Processor>(), false);
+ }
+
+ if (!r) {
+ continue;
+ }
+
+ if (replace) {
+ list<AudioRange> ranges;
+ ranges.push_back (AudioRange (start, start+cnt, 0));
+ playlist->cut (ranges); // discard result
+ playlist->add_region (r, start);
+ }
+
+ if (!in_command) {
+ begin_reversible_command (_("bounce range"));
+ in_command = true;
+ }
+ vector<Command*> cmds;
+ playlist->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command (new StatefulDiffCommand (playlist));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+/** Delete selected regions, automation points or a time range */
+void
+Editor::delete_ ()
+{
+ //special case: if the user is pointing in the editor/mixer strip, they may be trying to delete a plugin.
+ //we need this because the editor-mixer strip is in the editor window, so it doesn't get the bindings from the mix window
+ bool deleted = false;
+ if ( current_mixer_strip && current_mixer_strip == MixerStrip::entered_mixer_strip() )
+ deleted = current_mixer_strip->delete_processors ();
+
+ if (!deleted)
+ cut_copy (Delete);
+}
+
+/** Cut selected regions, automation points or a time range */
+void
+Editor::cut ()
+{
+ cut_copy (Cut);
+}
+
+/** Copy selected regions, automation points or a time range */
+void
+Editor::copy ()
+{
+ cut_copy (Copy);
+}
+
+
+/** @return true if a Cut, Copy or Clear is possible */
+bool
+Editor::can_cut_copy () const
+{
+ if (!selection->time.empty() || !selection->regions.empty() || !selection->points.empty())
+ return true;
+
+ return false;
+}
+
+
+/** Cut, copy or clear selected regions, automation points or a time range.
+ * @param op Operation (Delete, Cut, Copy or Clear)
+ */
+void
+Editor::cut_copy (CutCopyOp op)
+{
+ /* only cancel selection if cut/copy is successful.*/
+
+ string opname;
+
+ switch (op) {
+ case Delete:
+ opname = _("delete");
+ break;
+ case Cut:
+ opname = _("cut");
+ break;
+ case Copy:
+ opname = _("copy");
+ break;
+ case Clear:
+ opname = _("clear");
+ break;
+ }
+
+ /* if we're deleting something, and the mouse is still pressed,
+ the thing we started a drag for will be gone when we release
+ the mouse button(s). avoid this. see part 2 at the end of
+ this function.
+ */
+
+ if (op == Delete || op == Cut || op == Clear) {
+ if (_drags->active ()) {
+ _drags->abort ();
+ }
+ }
+
+ if ( op != Delete ) { //"Delete" doesn't change copy/paste buf
+ cut_buffer->clear ();
+ }
+
+ if (entered_marker) {
+
+ /* cut/delete op while pointing at a marker */
+
+ bool ignored;
+ Location* loc = find_location_from_marker (entered_marker, ignored);
+
+ if (_session && loc) {
+ entered_marker = NULL;
+ Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::really_remove_marker), loc));
+ }
+
+ _drags->abort ();
+ return;
+ }
+
+ switch (mouse_mode) {
+ case MouseDraw:
+ case MouseContent:
+ begin_reversible_command (opname + ' ' + X_("MIDI"));
+ cut_copy_midi (op);
+ commit_reversible_command ();
+ return;
+ default:
+ break;
+ }
+
+ bool did_edit = false;
+
+ if (!selection->regions.empty() || !selection->points.empty()) {
+ begin_reversible_command (opname + ' ' + _("objects"));
+ did_edit = true;
+
+ if (!selection->regions.empty()) {
+ cut_copy_regions (op, selection->regions);
+
+ if (op == Cut || op == Delete) {
+ selection->clear_regions ();
+ }
+ }
+
+ if (!selection->points.empty()) {
+ cut_copy_points (op);
+
+ if (op == Cut || op == Delete) {
+ selection->clear_points ();
+ }
+ }
+ } else if (selection->time.empty()) {
+ framepos_t start, end;
+ /* no time selection, see if we can get an edit range
+ and use that.
+ */
+ if (get_edit_op_range (start, end)) {
+ selection->set (start, end);
+ }
+ } else if (!selection->time.empty()) {
+ begin_reversible_command (opname + ' ' + _("range"));
+
+ did_edit = true;
+ cut_copy_ranges (op);
+
+ if (op == Cut || op == Delete) {
+ selection->clear_time ();
+ }
+ }
+
+ if (did_edit) {
+ /* reset repeated paste state */
+ paste_count = 0;
+ last_paste_pos = -1;
+ commit_reversible_command ();
+ }
+
+ if (op == Delete || op == Cut || op == Clear) {
+ _drags->abort ();
+ }
+}
+
+
+struct AutomationRecord {
+ AutomationRecord () : state (0) , line(NULL) {}
+ AutomationRecord (XMLNode* s, const AutomationLine* l) : state (s) , line (l) {}
+
+ XMLNode* state; ///< state before any operation
+ const AutomationLine* line; ///< line this came from
+ boost::shared_ptr<Evoral::ControlList> copy; ///< copied events for the cut buffer
+};
+
+struct PointsSelectionPositionSorter {
+ bool operator() (ControlPoint* a, ControlPoint* b) {
+ return (*(a->model()))->when < (*(b->model()))->when;
+ }
+};
+
+/** Cut, copy or clear selected automation points.
+ * @param op Operation (Cut, Copy or Clear)
+ */
+void
+Editor::cut_copy_points (Editing::CutCopyOp op, Evoral::Beats earliest, bool midi)
+{
+ if (selection->points.empty ()) {
+ return;
+ }
+
+ /* XXX: not ideal, as there may be more than one track involved in the point selection */
+ _last_cut_copy_source_track = &selection->points.front()->line().trackview;
+
+ /* Keep a record of the AutomationLists that we end up using in this operation */
+ typedef std::map<boost::shared_ptr<AutomationList>, AutomationRecord> Lists;
+ Lists lists;
+
+ /* user could select points in any order */
+ selection->points.sort(PointsSelectionPositionSorter ());
+
+ /* Go through all selected points, making an AutomationRecord for each distinct AutomationList */
+ for (PointSelection::iterator sel_point = selection->points.begin(); sel_point != selection->points.end(); ++sel_point) {
+ const AutomationLine& line = (*sel_point)->line();
+ const boost::shared_ptr<AutomationList> al = line.the_list();
+ if (lists.find (al) == lists.end ()) {
+ /* We haven't seen this list yet, so make a record for it. This includes
+ taking a copy of its current state, in case this is needed for undo later.
+ */
+ lists[al] = AutomationRecord (&al->get_state (), &line);
+ }
+ }
+
+ if (op == Cut || op == Copy) {
+ /* This operation will involve putting things in the cut buffer, so create an empty
+ ControlList for each of our source lists to put the cut buffer data in.
+ */
+ for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
+ i->second.copy = i->first->create (i->first->parameter (), i->first->descriptor());
+ }
+
+ /* Add all selected points to the relevant copy ControlLists */
+ MusicFrame start (std::numeric_limits<framepos_t>::max(), 0);
+ for (PointSelection::iterator sel_point = selection->points.begin(); sel_point != selection->points.end(); ++sel_point) {
+ boost::shared_ptr<AutomationList> al = (*sel_point)->line().the_list();
+ AutomationList::const_iterator ctrl_evt = (*sel_point)->model ();
+
+ lists[al].copy->fast_simple_add ((*ctrl_evt)->when, (*ctrl_evt)->value);
+ if (midi) {
+ /* Update earliest MIDI start time in beats */
+ earliest = std::min(earliest, Evoral::Beats((*ctrl_evt)->when));
+ } else {
+ /* Update earliest session start time in frames */
+ start.frame = std::min(start.frame, (*sel_point)->line().session_position(ctrl_evt));
+ }
+ }
+
+ /* Snap start time backwards, so copy/paste is snap aligned. */
+ if (midi) {
+ if (earliest == Evoral::Beats::max()) {
+ earliest = Evoral::Beats(); // Weird... don't offset
+ }
+ earliest.round_down_to_beat();
+ } else {
+ if (start.frame == std::numeric_limits<double>::max()) {
+ start.frame = 0; // Weird... don't offset
+ }
+ snap_to(start, RoundDownMaybe);
+ }
+
+ const double line_offset = midi ? earliest.to_double() : start.frame;
+ for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
+ /* Correct this copy list so that it is relative to the earliest
+ start time, so relative ordering between points is preserved
+ when copying from several lists and the paste starts at the
+ earliest copied piece of data. */
+ boost::shared_ptr<Evoral::ControlList> &al_cpy = i->second.copy;
+ for (AutomationList::iterator ctrl_evt = al_cpy->begin(); ctrl_evt != al_cpy->end(); ++ctrl_evt) {
+ (*ctrl_evt)->when -= line_offset;
+ }
+
+ /* And add it to the cut buffer */
+ cut_buffer->add (al_cpy);
+ }
+ }
+
+ if (op == Delete || op == Cut) {
+ /* This operation needs to remove things from the main AutomationList, so do that now */
+
+ for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
+ i->first->freeze ();
+ }
+
+ /* Remove each selected point from its AutomationList */
+ for (PointSelection::iterator sel_point = selection->points.begin(); sel_point != selection->points.end(); ++sel_point) {
+ AutomationLine& line = (*sel_point)->line ();
+ boost::shared_ptr<AutomationList> al = line.the_list();
+
+ bool erase = true;
+
+ if (dynamic_cast<AudioRegionGainLine*> (&line)) {
+ /* removing of first and last gain point in region gain lines is prohibited*/
+ if (line.is_last_point (*(*sel_point)) || line.is_first_point (*(*sel_point))) {
+ erase = false;
+ }
+ }
+
+ if(erase) {
+ al->erase ((*sel_point)->model ());
+ }
+ }
+
+ /* Thaw the lists and add undo records for them */
+ for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
+ boost::shared_ptr<AutomationList> al = i->first;
+ al->thaw ();
+ _session->add_command (new MementoCommand<AutomationList> (*al.get(), i->second.state, &(al->get_state ())));
+ }
+ }
+}
+
+/** Cut, copy or clear selected automation points.
+ * @param op Operation (Cut, Copy or Clear)
+ */
+void
+Editor::cut_copy_midi (CutCopyOp op)
+{
+ Evoral::Beats earliest = Evoral::Beats::max();
+ for (MidiRegionSelection::iterator i = selection->midi_regions.begin(); i != selection->midi_regions.end(); ++i) {
+ MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
+ if (mrv) {
+ if (!mrv->selection().empty()) {
+ earliest = std::min(earliest, (*mrv->selection().begin())->note()->time());
+ }
+ mrv->cut_copy_clear (op);
+
+ /* XXX: not ideal, as there may be more than one track involved in the selection */
+ _last_cut_copy_source_track = &mrv->get_time_axis_view();
+ }
+ }
+
+ if (!selection->points.empty()) {
+ cut_copy_points (op, earliest, true);
+ if (op == Cut || op == Delete) {
+ selection->clear_points ();
+ }
+ }
+}
+
+struct lt_playlist {
+ bool operator () (const PlaylistState& a, const PlaylistState& b) {
+ return a.playlist < b.playlist;
+ }
+};
+
+struct PlaylistMapping {
+ TimeAxisView* tv;
+ boost::shared_ptr<Playlist> pl;
+
+ PlaylistMapping (TimeAxisView* tvp) : tv (tvp) {}
+};
+
+/** Remove `clicked_regionview' */
+void
+Editor::remove_clicked_region ()
+{
+ if (clicked_routeview == 0 || clicked_regionview == 0) {
+ return;
+ }
+
+ begin_reversible_command (_("remove region"));
+
+ boost::shared_ptr<Playlist> playlist = clicked_routeview->playlist();
+
+ playlist->clear_changes ();
+ playlist->clear_owned_changes ();
+ playlist->remove_region (clicked_regionview->region());
+ if (Config->get_edit_mode() == Ripple)
+ playlist->ripple (clicked_regionview->region()->position(), -clicked_regionview->region()->length(), boost::shared_ptr<Region>());
+
+ /* We might have removed regions, which alters other regions' layering_index,
+ so we need to do a recursive diff here.
+ */
+ vector<Command*> cmds;
+ playlist->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command(new StatefulDiffCommand (playlist));
+ commit_reversible_command ();
+}
+
+
+/** Remove the selected regions */
+void
+Editor::remove_selected_regions ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ list<boost::shared_ptr<Region> > regions_to_remove;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ // we can't just remove the region(s) in this loop because
+ // this removes them from the RegionSelection, and they thus
+ // disappear from underneath the iterator, and the ++i above
+ // SEGVs in a puzzling fashion.
+
+ // so, first iterate over the regions to be removed from rs and
+ // add them to the regions_to_remove list, and then
+ // iterate over the list to actually remove them.
+
+ regions_to_remove.push_back ((*i)->region());
+ }
+
+ vector<boost::shared_ptr<Playlist> > playlists;
+
+ for (list<boost::shared_ptr<Region> >::iterator rl = regions_to_remove.begin(); rl != regions_to_remove.end(); ++rl) {
+
+ boost::shared_ptr<Playlist> playlist = (*rl)->playlist();
+
+ if (!playlist) {
+ // is this check necessary?
+ continue;
+ }
+
+ /* get_regions_from_selection_and_entered() guarantees that
+ the playlists involved are unique, so there is no need
+ to check here.
+ */
+
+ playlists.push_back (playlist);
+
+ playlist->clear_changes ();
+ playlist->clear_owned_changes ();
+ playlist->freeze ();
+ playlist->remove_region (*rl);
+ if (Config->get_edit_mode() == Ripple)
+ playlist->ripple ((*rl)->position(), -(*rl)->length(), boost::shared_ptr<Region>());
+
+ }
+
+ vector<boost::shared_ptr<Playlist> >::iterator pl;
+ bool in_command = false;
+
+ for (pl = playlists.begin(); pl != playlists.end(); ++pl) {
+ (*pl)->thaw ();
+
+ /* We might have removed regions, which alters other regions' layering_index,
+ so we need to do a recursive diff here.
+ */
+
+ if (!in_command) {
+ begin_reversible_command (_("remove region"));
+ in_command = true;
+ }
+ vector<Command*> cmds;
+ (*pl)->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command(new StatefulDiffCommand (*pl));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+/** Cut, copy or clear selected regions.
+ * @param op Operation (Cut, Copy or Clear)
+ */
+void
+Editor::cut_copy_regions (CutCopyOp op, RegionSelection& rs)
+{
+ /* we can't use a std::map here because the ordering is important, and we can't trivially sort
+ a map when we want ordered access to both elements. i think.
+ */
+
+ vector<PlaylistMapping> pmap;
+
+ framepos_t first_position = max_framepos;
+
+ typedef set<boost::shared_ptr<Playlist> > FreezeList;
+ FreezeList freezelist;
+
+ /* get ordering correct before we cut/copy */
+
+ rs.sort_by_position_and_track ();
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+
+ first_position = min ((framepos_t) (*x)->region()->position(), first_position);
+
+ if (op == Cut || op == Clear || op == Delete) {
+ boost::shared_ptr<Playlist> pl = (*x)->region()->playlist();
+
+ if (pl) {
+ FreezeList::iterator fl;
+
+ // only take state if this is a new playlist.
+ for (fl = freezelist.begin(); fl != freezelist.end(); ++fl) {
+ if ((*fl) == pl) {
+ break;
+ }
+ }
+
+ if (fl == freezelist.end()) {
+ pl->clear_changes();
+ pl->clear_owned_changes ();
+ pl->freeze ();
+ freezelist.insert (pl);
+ }
+ }
+ }
+
+ TimeAxisView* tv = &(*x)->get_time_axis_view();
+ vector<PlaylistMapping>::iterator z;
+
+ for (z = pmap.begin(); z != pmap.end(); ++z) {
+ if ((*z).tv == tv) {
+ break;
+ }
+ }
+
+ if (z == pmap.end()) {
+ pmap.push_back (PlaylistMapping (tv));
+ }
+ }
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ) {
+
+ boost::shared_ptr<Playlist> pl = (*x)->region()->playlist();
+
+ if (!pl) {
+ /* region not yet associated with a playlist (e.g. unfinished
+ capture pass.
+ */
+ ++x;
+ continue;
+ }
+
+ TimeAxisView& tv = (*x)->get_time_axis_view();
+ boost::shared_ptr<Playlist> npl;
+ RegionSelection::iterator tmp;
+
+ tmp = x;
+ ++tmp;
+
+ if (op != Delete) {
+
+ vector<PlaylistMapping>::iterator z;
+
+ for (z = pmap.begin(); z != pmap.end(); ++z) {
+ if ((*z).tv == &tv) {
+ break;
+ }
+ }
+
+ assert (z != pmap.end());
+
+ if (!(*z).pl) {
+ npl = PlaylistFactory::create (pl->data_type(), *_session, "cutlist", true);
+ npl->freeze();
+ (*z).pl = npl;
+ } else {
+ npl = (*z).pl;
+ }
+ }
+
+ boost::shared_ptr<Region> r = (*x)->region();
+ boost::shared_ptr<Region> _xx;
+
+ assert (r != 0);
+
+ switch (op) {
+ case Delete:
+ pl->remove_region (r);
+ if (Config->get_edit_mode() == Ripple)
+ pl->ripple (r->position(), -r->length(), boost::shared_ptr<Region>());
+ break;
+
+ case Cut:
+ _xx = RegionFactory::create (r);
+ npl->add_region (_xx, r->position() - first_position);
+ pl->remove_region (r);
+ if (Config->get_edit_mode() == Ripple)
+ pl->ripple (r->position(), -r->length(), boost::shared_ptr<Region>());
+ break;
+
+ case Copy:
+ /* copy region before adding, so we're not putting same object into two different playlists */
+ npl->add_region (RegionFactory::create (r), r->position() - first_position);
+ break;
+
+ case Clear:
+ pl->remove_region (r);
+ if (Config->get_edit_mode() == Ripple)
+ pl->ripple (r->position(), -r->length(), boost::shared_ptr<Region>());
+ break;
+ }
+
+ x = tmp;
+ }
+
+ if (op != Delete) {
+
+ list<boost::shared_ptr<Playlist> > foo;
+
+ /* the pmap is in the same order as the tracks in which selected regions occurred */
+
+ for (vector<PlaylistMapping>::iterator i = pmap.begin(); i != pmap.end(); ++i) {
+ if ((*i).pl) {
+ (*i).pl->thaw();
+ foo.push_back ((*i).pl);
+ }
+ }
+
+ if (!foo.empty()) {
+ cut_buffer->set (foo);
+ }
+
+ if (pmap.empty()) {
+ _last_cut_copy_source_track = 0;
+ } else {
+ _last_cut_copy_source_track = pmap.front().tv;
+ }
+ }
+
+ for (FreezeList::iterator pl = freezelist.begin(); pl != freezelist.end(); ++pl) {
+ (*pl)->thaw ();
+
+ /* We might have removed regions, which alters other regions' layering_index,
+ so we need to do a recursive diff here.
+ */
+ vector<Command*> cmds;
+ (*pl)->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command (new StatefulDiffCommand (*pl));
+ }
+}
+
+void
+Editor::cut_copy_ranges (CutCopyOp op)
+{
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+
+ /* Sort the track selection now, so that it if is used, the playlists
+ selected by the calls below to cut_copy_clear are in the order that
+ their tracks appear in the editor. This makes things like paste
+ of ranges work properly.
+ */
+
+ sort_track_selection (ts);
+
+ if (ts.empty()) {
+ if (!entered_track) {
+ return;
+ }
+ ts.push_back (entered_track);
+ }
+
+ for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
+ (*i)->cut_copy_clear (*selection, op);
+ }
+}
+
+void
+Editor::paste (float times, bool from_context)
+{
+ DEBUG_TRACE (DEBUG::CutNPaste, "paste to preferred edit pos\n");
+ MusicFrame where (get_preferred_edit_position (EDIT_IGNORE_NONE, from_context), 0);
+ paste_internal (where.frame, times, 0);
+}
+
+void
+Editor::mouse_paste ()
+{
+ MusicFrame where (0, 0);
+ bool ignored;
+ if (!mouse_frame (where.frame, ignored)) {
+ return;
+ }
+
+ snap_to (where);
+ paste_internal (where.frame, 1, where.division);
+}
+
+void
+Editor::paste_internal (framepos_t position, float times, const int32_t sub_num)
+{
+ DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("apparent paste position is %1\n", position));
+
+ if (cut_buffer->empty(internal_editing())) {
+ return;
+ }
+
+ if (position == max_framepos) {
+ position = get_preferred_edit_position();
+ DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("preferred edit position is %1\n", position));
+ }
+
+ if (position == last_paste_pos) {
+ /* repeated paste in the same position */
+ ++paste_count;
+ } else {
+ /* paste in new location, reset repeated paste state */
+ paste_count = 0;
+ last_paste_pos = position;
+ }
+
+ /* get everything in the correct order */
+
+ TrackViewList ts;
+ if (!selection->tracks.empty()) {
+ /* If there is a track selection, paste into exactly those tracks and
+ * only those tracks. This allows the user to be explicit and override
+ * the below "do the reasonable thing" logic. */
+ ts = selection->tracks.filter_to_unique_playlists ();
+ sort_track_selection (ts);
+ } else {
+ /* Figure out which track to base the paste at. */
+ TimeAxisView* base_track = NULL;
+ if (_edit_point == Editing::EditAtMouse && entered_track) {
+ /* With the mouse edit point, paste onto the track under the mouse. */
+ base_track = entered_track;
+ } else if (_edit_point == Editing::EditAtMouse && entered_regionview) {
+ /* With the mouse edit point, paste onto the track of the region under the mouse. */
+ base_track = &entered_regionview->get_time_axis_view();
+ } else if (_last_cut_copy_source_track) {
+ /* Paste to the track that the cut/copy came from (see mantis #333). */
+ base_track = _last_cut_copy_source_track;
+ } else {
+ /* This is "impossible" since we've copied... well, do nothing. */
+ return;
+ }
+
+ /* Walk up to parent if necessary, so base track is a route. */
+ while (base_track->get_parent()) {
+ base_track = base_track->get_parent();
+ }
+
+ /* Add base track and all tracks below it. The paste logic will select
+ the appropriate object types from the cut buffer in relative order. */
+ for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ if ((*i)->order() >= base_track->order()) {
+ ts.push_back(*i);
+ }
+ }
+
+ /* Sort tracks so the nth track of type T will pick the nth object of type T. */
+ sort_track_selection (ts);
+
+ /* Add automation children of each track in order, for pasting several lines. */
+ for (TrackViewList::iterator i = ts.begin(); i != ts.end();) {
+ /* Add any automation children for pasting several lines */
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(*i++);
+ if (!rtv) {
+ continue;
+ }
+
+ typedef RouteTimeAxisView::AutomationTracks ATracks;
+ const ATracks& atracks = rtv->automation_tracks();
+ for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
+ i = ts.insert(i, a->second.get());
+ ++i;
+ }
+ }
+
+ /* We now have a list of trackviews starting at base_track, including
+ automation children, in the order shown in the editor, e.g. R1,
+ R1.A1, R1.A2, R2, R2.A1, ... */
+ }
+
+ begin_reversible_command (Operations::paste);
+
+ if (ts.size() == 1 && cut_buffer->lines.size() == 1 &&
+ dynamic_cast<AutomationTimeAxisView*>(ts.front())) {
+ /* Only one line copied, and one automation track selected. Do a
+ "greedy" paste from one automation type to another. */
+
+ PasteContext ctx(paste_count, times, ItemCounts(), true);
+ ts.front()->paste (position, *cut_buffer, ctx, sub_num);
+
+ } else {
+
+ /* Paste into tracks */
+
+ PasteContext ctx(paste_count, times, ItemCounts(), false);
+ for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
+ (*i)->paste (position, *cut_buffer, ctx, sub_num);
+ }
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::duplicate_regions (float times)
+{
+ RegionSelection rs (get_regions_from_selection_and_entered());
+ duplicate_some_regions (rs, times);
+}
+
+void
+Editor::duplicate_some_regions (RegionSelection& regions, float times)
+{
+ if (regions.empty ()) {
+ return;
+ }
+
+ boost::shared_ptr<Playlist> playlist;
+ RegionSelection sel = regions; // clear (below) may clear the argument list if its the current region selection
+ RegionSelection foo;
+
+ framepos_t const start_frame = regions.start ();
+ framepos_t const end_frame = regions.end_frame ();
+ framecnt_t const gap = end_frame - start_frame + 1;
+
+ begin_reversible_command (Operations::duplicate_region);
+
+ selection->clear_regions ();
+
+ for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
+
+ boost::shared_ptr<Region> r ((*i)->region());
+
+ TimeAxisView& tv = (*i)->get_time_axis_view();
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&tv);
+ latest_regionviews.clear ();
+ sigc::connection c = rtv->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
+
+ framepos_t const position = end_frame + (r->first_frame() - start_frame + 1);
+ playlist = (*i)->region()->playlist();
+ playlist->clear_changes ();
+ playlist->duplicate (r, position, gap, times);
+ _session->add_command(new StatefulDiffCommand (playlist));
+
+ c.disconnect ();
+
+ foo.insert (foo.end(), latest_regionviews.begin(), latest_regionviews.end());
+ }
+
+ if (!foo.empty()) {
+ selection->set (foo);
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::duplicate_selection (float times)
+{
+ if (selection->time.empty() || selection->tracks.empty()) {
+ return;
+ }
+
+ boost::shared_ptr<Playlist> playlist;
+
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+
+ bool in_command = false;
+
+ for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
+ if ((playlist = (*i)->playlist()) == 0) {
+ continue;
+ }
+ playlist->clear_changes ();
+
+ if (clicked_selection) {
+ playlist->duplicate_range (selection->time[clicked_selection], times);
+ } else {
+ playlist->duplicate_ranges (selection->time, times);
+ }
+
+ if (!in_command) {
+ begin_reversible_command (_("duplicate range selection"));
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (playlist));
+
+ }
+
+ if (in_command) {
+ if (times == 1.0f) {
+ // now "move" range selection to after the current range selection
+ framecnt_t distance = 0;
+
+ if (clicked_selection) {
+ distance =
+ selection->time[clicked_selection].end - selection->time[clicked_selection].start;
+ } else {
+ distance = selection->time.end_frame () - selection->time.start ();
+ }
+
+ selection->move_time (distance);
+ }
+ commit_reversible_command ();
+ }
+}
+
+/** Reset all selected points to the relevant default value */
+void
+Editor::reset_point_selection ()
+{
+ for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) {
+ ARDOUR::AutomationList::iterator j = (*i)->model ();
+ (*j)->value = (*i)->line().the_list()->descriptor ().normal;
+ }
+}
+
+void
+Editor::center_playhead ()
+{
+ float const page = _visible_canvas_width * samples_per_pixel;
+ center_screen_internal (playhead_cursor->current_frame (), page);
+}
+
+void
+Editor::center_edit_point ()
+{
+ float const page = _visible_canvas_width * samples_per_pixel;
+ center_screen_internal (get_preferred_edit_position(), page);
+}
+
+/** Caller must begin and commit a reversible command */
+void
+Editor::clear_playlist (boost::shared_ptr<Playlist> playlist)
+{
+ playlist->clear_changes ();
+ playlist->clear ();
+ _session->add_command (new StatefulDiffCommand (playlist));
+}
+
+void
+Editor::nudge_track (bool use_edit, bool forwards)
+{
+ boost::shared_ptr<Playlist> playlist;
+ framepos_t distance;
+ framepos_t next_distance;
+ framepos_t start;
+
+ if (use_edit) {
+ start = get_preferred_edit_position();
+ } else {
+ start = 0;
+ }
+
+ if ((distance = get_nudge_distance (start, next_distance)) == 0) {
+ return;
+ }
+
+ if (selection->tracks.empty()) {
+ return;
+ }
+
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+ bool in_command = false;
+
+ for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
+
+ if ((playlist = (*i)->playlist()) == 0) {
+ continue;
+ }
+
+ playlist->clear_changes ();
+ playlist->clear_owned_changes ();
+
+ playlist->nudge_after (start, distance, forwards);
+
+ if (!in_command) {
+ begin_reversible_command (_("nudge track"));
+ in_command = true;
+ }
+ vector<Command*> cmds;
+
+ playlist->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command (new StatefulDiffCommand (playlist));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::remove_last_capture ()
+{
+ vector<string> choices;
+ string prompt;
+
+ if (!_session) {
+ return;
+ }
+
+ if (Config->get_verify_remove_last_capture()) {
+ prompt = _("Do you really want to destroy the last capture?"
+ "\n(This is destructive and cannot be undone)");
+
+ choices.push_back (_("No, do nothing."));
+ choices.push_back (_("Yes, destroy it."));
+
+ Choice prompter (_("Destroy last capture"), prompt, choices);
+
+ if (prompter.run () == 1) {
+ _session->remove_last_capture ();
+ _regions->redisplay ();
+ }
+
+ } else {
+ _session->remove_last_capture();
+ _regions->redisplay ();
+ }
+}
+
+void
+Editor::normalize_region ()
+{
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ NormalizeDialog dialog (rs.size() > 1);
+
+ if (dialog.run () != RESPONSE_ACCEPT) {
+ return;
+ }
+
+ CursorContext::Handle cursor_ctx = CursorContext::create(*this, _cursors->wait);
+ gdk_flush ();
+
+ /* XXX: should really only count audio regions here */
+ int const regions = rs.size ();
+
+ /* Make a list of the selected audio regions' maximum amplitudes, and also
+ obtain the maximum amplitude of them all.
+ */
+ list<double> max_amps;
+ list<double> rms_vals;
+ double max_amp = 0;
+ double max_rms = 0;
+ bool use_rms = dialog.constrain_rms ();
+
+ for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) {
+ AudioRegionView const * arv = dynamic_cast<AudioRegionView const *> (*i);
+ if (!arv) {
+ continue;
+ }
+ dialog.descend (1.0 / regions);
+ double const a = arv->audio_region()->maximum_amplitude (&dialog);
+ if (use_rms) {
+ double r = arv->audio_region()->rms (&dialog);
+ max_rms = max (max_rms, r);
+ rms_vals.push_back (r);
+ }
+
+ if (a == -1) {
+ /* the user cancelled the operation */
+ return;
+ }
+
+ max_amps.push_back (a);
+ max_amp = max (max_amp, a);
+ dialog.ascend ();
+ }
+
+ list<double>::const_iterator a = max_amps.begin ();
+ list<double>::const_iterator l = rms_vals.begin ();
+ bool in_command = false;
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (*r);
+ if (!arv) {
+ continue;
+ }
+
+ arv->region()->clear_changes ();
+
+ double amp = dialog.normalize_individually() ? *a : max_amp;
+ double target = dialog.target_peak (); // dB
+
+ if (use_rms) {
+ double const amp_rms = dialog.normalize_individually() ? *l : max_rms;
+ const double t_rms = dialog.target_rms ();
+ const gain_t c_peak = dB_to_coefficient (target);
+ const gain_t c_rms = dB_to_coefficient (t_rms);
+ if ((amp_rms / c_rms) > (amp / c_peak)) {
+ amp = amp_rms;
+ target = t_rms;
+ }
+ }
+
+ arv->audio_region()->normalize (amp, target);
+
+ if (!in_command) {
+ begin_reversible_command (_("normalize"));
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (arv->region()));
+
+ ++a;
+ ++l;
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+
+void
+Editor::reset_region_scale_amplitude ()
+{
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*r);
+ if (!arv)
+ continue;
+ arv->region()->clear_changes ();
+ arv->audio_region()->set_scale_amplitude (1.0f);
+
+ if(!in_command) {
+ begin_reversible_command ("reset gain");
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (arv->region()));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::adjust_region_gain (bool up)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*r);
+ if (!arv) {
+ continue;
+ }
+
+ arv->region()->clear_changes ();
+
+ double dB = accurate_coefficient_to_dB (arv->audio_region()->scale_amplitude ());
+
+ if (up) {
+ dB += 1;
+ } else {
+ dB -= 1;
+ }
+
+ arv->audio_region()->set_scale_amplitude (dB_to_coefficient (dB));
+
+ if (!in_command) {
+ begin_reversible_command ("adjust region gain");
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (arv->region()));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::reset_region_gain ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*r);
+ if (!arv) {
+ continue;
+ }
+
+ arv->region()->clear_changes ();
+
+ arv->audio_region()->set_scale_amplitude (1.0f);
+
+ if (!in_command) {
+ begin_reversible_command ("reset region gain");
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (arv->region()));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::reverse_region ()
+{
+ if (!_session) {
+ return;
+ }
+
+ Reverse rev (*_session);
+ apply_filter (rev, _("reverse regions"));
+}
+
+void
+Editor::strip_region_silence ()
+{
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ std::list<RegionView*> audio_only;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (*i);
+ if (arv) {
+ audio_only.push_back (arv);
+ }
+ }
+
+ assert (!audio_only.empty());
+
+ StripSilenceDialog d (_session, audio_only);
+ int const r = d.run ();
+
+ d.drop_rects ();
+
+ if (r == Gtk::RESPONSE_OK) {
+ ARDOUR::AudioIntervalMap silences;
+ d.silences (silences);
+ StripSilence s (*_session, silences, d.fade_length());
+
+ apply_filter (s, _("strip silence"), &d);
+ }
+}
+
+Command*
+Editor::apply_midi_note_edit_op_to_region (MidiOperator& op, MidiRegionView& mrv)
+{
+ Evoral::Sequence<Evoral::Beats>::Notes selected;
+ mrv.selection_as_notelist (selected, true);
+
+ vector<Evoral::Sequence<Evoral::Beats>::Notes> v;
+ v.push_back (selected);
+
+ Evoral::Beats pos_beats = Evoral::Beats (mrv.midi_region()->beat()) - mrv.midi_region()->start_beats();
+
+ return op (mrv.midi_region()->model(), pos_beats, v);
+}
+
+void
+Editor::apply_midi_note_edit_op (MidiOperator& op, const RegionSelection& rs)
+{
+ if (rs.empty()) {
+ return;
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::const_iterator r = rs.begin(); r != rs.end(); ) {
+ RegionSelection::const_iterator tmp = r;
+ ++tmp;
+
+ MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (*r);
+
+ if (mrv) {
+ Command* cmd = apply_midi_note_edit_op_to_region (op, *mrv);
+ if (cmd) {
+ if (!in_command) {
+ begin_reversible_command (op.name ());
+ in_command = true;
+ }
+ (*cmd)();
+ _session->add_command (cmd);
+ }
+ }
+
+ r = tmp;
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::fork_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ CursorContext::Handle cursor_ctx = CursorContext::create(*this, _cursors->wait);
+ bool in_command = false;
+
+ gdk_flush ();
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ) {
+ RegionSelection::iterator tmp = r;
+ ++tmp;
+
+ MidiRegionView* const mrv = dynamic_cast<MidiRegionView*>(*r);
+
+ if (mrv) {
+ try {
+ boost::shared_ptr<Playlist> playlist = mrv->region()->playlist();
+ boost::shared_ptr<MidiSource> new_source = _session->create_midi_source_by_stealing_name (mrv->midi_view()->track());
+ boost::shared_ptr<MidiRegion> newregion = mrv->midi_region()->clone (new_source);
+
+ if (!in_command) {
+ begin_reversible_command (_("Fork Region(s)"));
+ in_command = true;
+ }
+ playlist->clear_changes ();
+ playlist->replace_region (mrv->region(), newregion, mrv->region()->position());
+ _session->add_command(new StatefulDiffCommand (playlist));
+ } catch (...) {
+ error << string_compose (_("Could not unlink %1"), mrv->region()->name()) << endmsg;
+ }
+ }
+
+ r = tmp;
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::quantize_region ()
+{
+ if (_session) {
+ quantize_regions(get_regions_from_selection_and_entered ());
+ }
+}
+
+void
+Editor::quantize_regions (const RegionSelection& rs)
+{
+ if (rs.n_midi_regions() == 0) {
+ return;
+ }
+
+ if (!quantize_dialog) {
+ quantize_dialog = new QuantizeDialog (*this);
+ }
+
+ if (quantize_dialog->is_mapped()) {
+ /* in progress already */
+ return;
+ }
+
+ quantize_dialog->present ();
+ const int r = quantize_dialog->run ();
+ quantize_dialog->hide ();
+
+ if (r == Gtk::RESPONSE_OK) {
+ Quantize quant (quantize_dialog->snap_start(),
+ quantize_dialog->snap_end(),
+ quantize_dialog->start_grid_size(),
+ quantize_dialog->end_grid_size(),
+ quantize_dialog->strength(),
+ quantize_dialog->swing(),
+ quantize_dialog->threshold());
+
+ apply_midi_note_edit_op (quant, rs);
+ }
+}
+
+void
+Editor::legatize_region (bool shrink_only)
+{
+ if (_session) {
+ legatize_regions(get_regions_from_selection_and_entered (), shrink_only);
+ }
+}
+
+void
+Editor::legatize_regions (const RegionSelection& rs, bool shrink_only)
+{
+ if (rs.n_midi_regions() == 0) {
+ return;
+ }
+
+ Legatize legatize(shrink_only);
+ apply_midi_note_edit_op (legatize, rs);
+}
+
+void
+Editor::transform_region ()
+{
+ if (_session) {
+ transform_regions(get_regions_from_selection_and_entered ());
+ }
+}
+
+void
+Editor::transform_regions (const RegionSelection& rs)
+{
+ if (rs.n_midi_regions() == 0) {
+ return;
+ }
+
+ TransformDialog td;
+
+ td.present();
+ const int r = td.run();
+ td.hide();
+
+ if (r == Gtk::RESPONSE_OK) {
+ Transform transform(td.get());
+ apply_midi_note_edit_op(transform, rs);
+ }
+}
+
+void
+Editor::transpose_region ()
+{
+ if (_session) {
+ transpose_regions(get_regions_from_selection_and_entered ());
+ }
+}
+
+void
+Editor::transpose_regions (const RegionSelection& rs)
+{
+ if (rs.n_midi_regions() == 0) {
+ return;
+ }
+
+ TransposeDialog d;
+ int const r = d.run ();
+
+ if (r == RESPONSE_ACCEPT) {
+ Transpose transpose(d.semitones ());
+ apply_midi_note_edit_op (transpose, rs);
+ }
+}
+
+void
+Editor::insert_patch_change (bool from_context)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty ()) {
+ return;
+ }
+
+ const framepos_t p = get_preferred_edit_position (EDIT_IGNORE_NONE, from_context);
+
+ /* XXX: bit of a hack; use the MIDNAM from the first selected region;
+ there may be more than one, but the PatchChangeDialog can only offer
+ one set of patch menus.
+ */
+ MidiRegionView* first = dynamic_cast<MidiRegionView*> (rs.front ());
+
+ Evoral::PatchChange<Evoral::Beats> empty (Evoral::Beats(), 0, 0, 0);
+ PatchChangeDialog d (0, _session, empty, first->instrument_info(), Gtk::Stock::ADD);
+
+ if (d.run() == RESPONSE_CANCEL) {
+ return;
+ }
+
+ for (RegionSelection::iterator i = rs.begin (); i != rs.end(); ++i) {
+ MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (*i);
+ if (mrv) {
+ if (p >= mrv->region()->first_frame() && p <= mrv->region()->last_frame()) {
+ mrv->add_patch_change (p - mrv->region()->position(), d.patch ());
+ }
+ }
+ }
+}
+
+void
+Editor::apply_filter (Filter& filter, string command, ProgressReporter* progress)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ CursorContext::Handle cursor_ctx = CursorContext::create(*this, _cursors->wait);
+ bool in_command = false;
+
+ gdk_flush ();
+
+ int n = 0;
+ int const N = rs.size ();
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ) {
+ RegionSelection::iterator tmp = r;
+ ++tmp;
+
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*r);
+ if (arv) {
+ boost::shared_ptr<Playlist> playlist = arv->region()->playlist();
+
+ if (progress) {
+ progress->descend (1.0 / N);
+ }
+
+ if (arv->audio_region()->apply (filter, progress) == 0) {
+
+ playlist->clear_changes ();
+ playlist->clear_owned_changes ();
+
+ if (!in_command) {
+ begin_reversible_command (command);
+ in_command = true;
+ }
+
+ if (filter.results.empty ()) {
+
+ /* no regions returned; remove the old one */
+ playlist->remove_region (arv->region ());
+
+ } else {
+
+ std::vector<boost::shared_ptr<Region> >::iterator res = filter.results.begin ();
+
+ /* first region replaces the old one */
+ playlist->replace_region (arv->region(), *res, (*res)->position());
+ ++res;
+
+ /* add the rest */
+ while (res != filter.results.end()) {
+ playlist->add_region (*res, (*res)->position());
+ ++res;
+ }
+
+ }
+
+ /* We might have removed regions, which alters other regions' layering_index,
+ so we need to do a recursive diff here.
+ */
+ vector<Command*> cmds;
+ playlist->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command(new StatefulDiffCommand (playlist));
+ }
+
+ if (progress) {
+ progress->ascend ();
+ }
+ }
+
+ r = tmp;
+ ++n;
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::external_edit_region ()
+{
+ /* more to come */
+}
+
+void
+Editor::reset_region_gain_envelopes ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
+ if (arv) {
+ boost::shared_ptr<AutomationList> alist (arv->audio_region()->envelope());
+ XMLNode& before (alist->get_state());
+
+ arv->audio_region()->set_default_envelope ();
+
+ if (!in_command) {
+ begin_reversible_command (_("reset region gain"));
+ in_command = true;
+ }
+ _session->add_command (new MementoCommand<AutomationList>(*arv->audio_region()->envelope().get(), &before, &alist->get_state()));
+ }
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::set_region_gain_visibility (RegionView* rv)
+{
+ AudioRegionView* arv = dynamic_cast<AudioRegionView*> (rv);
+ if (arv) {
+ arv->update_envelope_visibility();
+ }
+}
+
+void
+Editor::set_gain_envelope_visibility ()
+{
+ if (!_session) {
+ return;
+ }
+
+ for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ AudioTimeAxisView* v = dynamic_cast<AudioTimeAxisView*>(*i);
+ if (v) {
+ v->audio_view()->foreach_regionview (sigc::mem_fun (this, &Editor::set_region_gain_visibility));
+ }
+ }
+}
+
+void
+Editor::toggle_gain_envelope_active ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
+ if (arv) {
+ arv->region()->clear_changes ();
+ arv->audio_region()->set_envelope_active (!arv->audio_region()->envelope_active());
+
+ if (!in_command) {
+ begin_reversible_command (_("region gain envelope active"));
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (arv->region()));
+ }
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::toggle_region_lock ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("toggle region lock"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ (*i)->region()->clear_changes ();
+ (*i)->region()->set_locked (!(*i)->region()->locked());
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::toggle_region_video_lock ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("Toggle Video Lock"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ (*i)->region()->clear_changes ();
+ (*i)->region()->set_video_locked (!(*i)->region()->video_locked());
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::toggle_region_lock_style ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ Glib::RefPtr<ToggleAction> a = Glib::RefPtr<ToggleAction>::cast_dynamic (_region_actions->get_action("toggle-region-lock-style"));
+ vector<Widget*> proxies = a->get_proxies();
+ Gtk::CheckMenuItem* cmi = dynamic_cast<Gtk::CheckMenuItem*> (proxies.front());
+
+ assert (cmi);
+
+ begin_reversible_command (_("toggle region lock style"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ (*i)->region()->clear_changes ();
+ PositionLockStyle const ns = ((*i)->region()->position_lock_style() == AudioTime && !cmi->get_inconsistent()) ? MusicTime : AudioTime;
+ (*i)->region()->set_position_lock_style (ns);
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::toggle_opaque_region ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("change region opacity"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ (*i)->region()->clear_changes ();
+ (*i)->region()->set_opaque (!(*i)->region()->opaque());
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::toggle_record_enable ()
+{
+ bool new_state = false;
+ bool first = true;
+ for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
+ RouteTimeAxisView *rtav = dynamic_cast<RouteTimeAxisView *>(*i);
+ if (!rtav)
+ continue;
+ if (!rtav->is_track())
+ continue;
+
+ if (first) {
+ new_state = !rtav->track()->rec_enable_control()->get_value();
+ first = false;
+ }
+
+ rtav->track()->rec_enable_control()->set_value (new_state, Controllable::UseGroup);
+ }
+}
+
+void
+Editor::toggle_solo ()
+{
+ bool new_state = false;
+ bool first = true;
+ boost::shared_ptr<ControlList> cl (new ControlList);
+
+ for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
+ StripableTimeAxisView *stav = dynamic_cast<StripableTimeAxisView *>(*i);
+
+ if (!stav || !stav->stripable()->solo_control()) {
+ continue;
+ }
+
+ if (first) {
+ new_state = !stav->stripable()->solo_control()->soloed ();
+ first = false;
+ }
+
+ cl->push_back (stav->stripable()->solo_control());
+ }
+
+ _session->set_controls (cl, new_state ? 1.0 : 0.0, Controllable::UseGroup);
+}
+
+void
+Editor::toggle_mute ()
+{
+ bool new_state = false;
+ bool first = true;
+ boost::shared_ptr<ControlList> cl (new ControlList);
+
+ for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
+ StripableTimeAxisView *stav = dynamic_cast<StripableTimeAxisView *>(*i);
+
+ if (!stav || !stav->stripable()->mute_control()) {
+ continue;
+ }
+
+ if (first) {
+ new_state = !stav->stripable()->mute_control()->muted();
+ first = false;
+ }
+
+ cl->push_back (stav->stripable()->mute_control());
+ }
+
+ _session->set_controls (cl, new_state, Controllable::UseGroup);
+}
+
+void
+Editor::toggle_solo_isolate ()
+{
+}
+
+
+void
+Editor::fade_range ()
+{
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+
+ begin_reversible_command (_("fade range"));
+
+ for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
+ (*i)->fade_range (selection->time);
+ }
+
+ commit_reversible_command ();
+}
+
+
+void
+Editor::set_fade_length (bool in)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ /* we need a region to measure the offset from the start */
+
+ RegionView* rv = rs.front ();
+
+ framepos_t pos = get_preferred_edit_position();
+ framepos_t len;
+ char const * cmd;
+
+ if (pos > rv->region()->last_frame() || pos < rv->region()->first_frame()) {
+ /* edit point is outside the relevant region */
+ return;
+ }
+
+ if (in) {
+ if (pos <= rv->region()->position()) {
+ /* can't do it */
+ return;
+ }
+ len = pos - rv->region()->position();
+ cmd = _("set fade in length");
+ } else {
+ if (pos >= rv->region()->last_frame()) {
+ /* can't do it */
+ return;
+ }
+ len = rv->region()->last_frame() - pos;
+ cmd = _("set fade out length");
+ }
+
+ bool in_command = false;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+ AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*x);
+
+ if (!tmp) {
+ continue;
+ }
+
+ boost::shared_ptr<AutomationList> alist;
+ if (in) {
+ alist = tmp->audio_region()->fade_in();
+ } else {
+ alist = tmp->audio_region()->fade_out();
+ }
+
+ XMLNode &before = alist->get_state();
+
+ if (in) {
+ tmp->audio_region()->set_fade_in_length (len);
+ tmp->audio_region()->set_fade_in_active (true);
+ } else {
+ tmp->audio_region()->set_fade_out_length (len);
+ tmp->audio_region()->set_fade_out_active (true);
+ }
+
+ if (!in_command) {
+ begin_reversible_command (cmd);
+ in_command = true;
+ }
+ XMLNode &after = alist->get_state();
+ _session->add_command(new MementoCommand<AutomationList>(*alist, &before, &after));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::set_fade_in_shape (FadeShape shape)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+ bool in_command = false;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+ AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*x);
+
+ if (!tmp) {
+ continue;
+ }
+
+ boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
+ XMLNode &before = alist->get_state();
+
+ tmp->audio_region()->set_fade_in_shape (shape);
+
+ if (!in_command) {
+ begin_reversible_command (_("set fade in shape"));
+ in_command = true;
+ }
+ XMLNode &after = alist->get_state();
+ _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::set_fade_out_shape (FadeShape shape)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+ bool in_command = false;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+ AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*x);
+
+ if (!tmp) {
+ continue;
+ }
+
+ boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
+ XMLNode &before = alist->get_state();
+
+ tmp->audio_region()->set_fade_out_shape (shape);
+
+ if(!in_command) {
+ begin_reversible_command (_("set fade out shape"));
+ in_command = true;
+ }
+ XMLNode &after = alist->get_state();
+ _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::set_fade_in_active (bool yn)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+ bool in_command = false;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+ AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*x);
+
+ if (!tmp) {
+ continue;
+ }
+
+
+ boost::shared_ptr<AudioRegion> ar (tmp->audio_region());
+
+ ar->clear_changes ();
+ ar->set_fade_in_active (yn);
+
+ if (!in_command) {
+ begin_reversible_command (_("set fade in active"));
+ in_command = true;
+ }
+ _session->add_command (new StatefulDiffCommand (ar));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::set_fade_out_active (bool yn)
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+ bool in_command = false;
+
+ for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) {
+ AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*x);
+
+ if (!tmp) {
+ continue;
+ }
+
+ boost::shared_ptr<AudioRegion> ar (tmp->audio_region());
+
+ ar->clear_changes ();
+ ar->set_fade_out_active (yn);
+
+ if (!in_command) {
+ begin_reversible_command (_("set fade out active"));
+ in_command = true;
+ }
+ _session->add_command(new StatefulDiffCommand (ar));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::toggle_region_fades (int dir)
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ boost::shared_ptr<AudioRegion> ar;
+ bool yn = false;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ RegionSelection::iterator i;
+ for (i = rs.begin(); i != rs.end(); ++i) {
+ if ((ar = boost::dynamic_pointer_cast<AudioRegion>((*i)->region())) != 0) {
+ if (dir == -1) {
+ yn = ar->fade_out_active ();
+ } else {
+ yn = ar->fade_in_active ();
+ }
+ break;
+ }
+ }
+
+ if (i == rs.end()) {
+ return;
+ }
+
+ /* XXX should this undo-able? */
+ bool in_command = false;
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ if ((ar = boost::dynamic_pointer_cast<AudioRegion>((*i)->region())) == 0) {
+ continue;
+ }
+ ar->clear_changes ();
+
+ if (dir == 1 || dir == 0) {
+ ar->set_fade_in_active (!yn);
+ }
+
+ if (dir == -1 || dir == 0) {
+ ar->set_fade_out_active (!yn);
+ }
+ if (!in_command) {
+ begin_reversible_command (_("toggle fade active"));
+ in_command = true;
+ }
+ _session->add_command(new StatefulDiffCommand (ar));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+
+/** Update region fade visibility after its configuration has been changed */
+void
+Editor::update_region_fade_visibility ()
+{
+ bool _fade_visibility = _session->config.get_show_region_fades ();
+
+ for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ AudioTimeAxisView* v = dynamic_cast<AudioTimeAxisView*>(*i);
+ if (v) {
+ if (_fade_visibility) {
+ v->audio_view()->show_all_fades ();
+ } else {
+ v->audio_view()->hide_all_fades ();
+ }
+ }
+ }
+}
+
+void
+Editor::set_edit_point ()
+{
+ bool ignored;
+ MusicFrame where (0, 0);
+
+ if (!mouse_frame (where.frame, ignored)) {
+ return;
+ }
+
+ snap_to (where);
+
+ if (selection->markers.empty()) {
+
+ mouse_add_new_marker (where.frame);
+
+ } else {
+ bool ignored;
+
+ Location* loc = find_location_from_marker (selection->markers.front(), ignored);
+
+ if (loc) {
+ loc->move_to (where.frame, where.division);
+ }
+ }
+}
+
+void
+Editor::set_playhead_cursor ()
+{
+ if (entered_marker) {
+ _session->request_locate (entered_marker->position(), _session->transport_rolling());
+ } else {
+ MusicFrame where (0, 0);
+ bool ignored;
+
+ if (!mouse_frame (where.frame, ignored)) {
+ return;
+ }
+
+ snap_to (where);
+
+ if (_session) {
+ _session->request_locate (where.frame, _session->transport_rolling());
+ }
+ }
+
+//not sure what this was for; remove it for now.
+// if (UIConfiguration::instance().get_follow_edits() && (!_session || !_session->config.get_external_sync())) {
+// cancel_time_selection();
+// }
+
+}
+
+void
+Editor::split_region ()
+{
+ if (_drags->active ()) {
+ return;
+ }
+
+ //if a range is selected, separate it
+ if ( !selection->time.empty()) {
+ separate_regions_between (selection->time);
+ return;
+ }
+
+ //if no range was selected, try to find some regions to split
+ if (current_mouse_mode() == MouseObject) { //don't try this for Internal Edit, Stretch, Draw, etc.
+
+ RegionSelection rs = get_regions_from_selection_and_edit_point ();
+ const framepos_t pos = get_preferred_edit_position();
+ const int32_t division = get_grid_music_divisions (0);
+ MusicFrame where (pos, division);
+
+ if (rs.empty()) {
+ return;
+ }
+
+ split_regions_at (where, rs);
+
+ }
+}
+
+void
+Editor::select_next_stripable (bool routes_only)
+{
+ if (selection->tracks.empty()) {
+ selection->set (track_views.front());
+ return;
+ }
+
+ TimeAxisView* current = selection->tracks.front();
+
+ bool valid;
+ do {
+ for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+
+ if (*i == current) {
+ ++i;
+ if (i != track_views.end()) {
+ current = (*i);
+ } else {
+ current = (*(track_views.begin()));
+ //selection->set (*(track_views.begin()));
+ }
+ break;
+ }
+ }
+
+ if (routes_only) {
+ RouteUI* rui = dynamic_cast<RouteUI *>(current);
+ valid = rui && rui->route()->active();
+ } else {
+ valid = 0 != current->stripable ().get();
+ }
+
+ } while (current->hidden() || !valid);
+
+ selection->set (current);
+
+ ensure_time_axis_view_is_visible (*current, false);
+}
+
+void
+Editor::select_prev_stripable (bool routes_only)
+{
+ if (selection->tracks.empty()) {
+ selection->set (track_views.front());
+ return;
+ }
+
+ TimeAxisView* current = selection->tracks.front();
+
+ bool valid;
+ do {
+ for (TrackViewList::reverse_iterator i = track_views.rbegin(); i != track_views.rend(); ++i) {
+
+ if (*i == current) {
+ ++i;
+ if (i != track_views.rend()) {
+ current = (*i);
+ } else {
+ current = *(track_views.rbegin());
+ }
+ break;
+ }
+ }
+ if (routes_only) {
+ RouteUI* rui = dynamic_cast<RouteUI *>(current);
+ valid = rui && rui->route()->active();
+ } else {
+ valid = 0 != current->stripable ().get();
+ }
+
+ } while (current->hidden() || !valid);
+
+ selection->set (current);
+
+ ensure_time_axis_view_is_visible (*current, false);
+}
+
+void
+Editor::set_loop_from_selection (bool play)
+{
+ if (_session == 0) {
+ return;
+ }
+
+ framepos_t start, end;
+ if (!get_selection_extents ( start, end))
+ return;
+
+ set_loop_range (start, end, _("set loop range from selection"));
+
+ if (play) {
+ _session->request_play_loop (true, true);
+ }
+}
+
+void
+Editor::set_loop_from_region (bool play)
+{
+ framepos_t start, end;
+ if (!get_selection_extents ( start, end))
+ return;
+
+ set_loop_range (start, end, _("set loop range from region"));
+
+ if (play) {
+ _session->request_locate (start, true);
+ _session->request_play_loop (true);
+ }
+}
+
+void
+Editor::set_punch_from_selection ()
+{
+ if (_session == 0) {
+ return;
+ }
+
+ framepos_t start, end;
+ if (!get_selection_extents ( start, end))
+ return;
+
+ set_punch_range (start, end, _("set punch range from selection"));
+}
+
+void
+Editor::set_auto_punch_range ()
+{
+ // auto punch in/out button from a single button
+ // If Punch In is unset, set punch range from playhead to end, enable punch in
+ // If Punch In is set, the next punch sets Punch Out, unless the playhead has been
+ // rewound beyond the Punch In marker, in which case that marker will be moved back
+ // to the current playhead position.
+ // If punch out is set, it clears the punch range and Punch In/Out buttons
+
+ if (_session == 0) {
+ return;
+ }
+
+ Location* tpl = transport_punch_location();
+ framepos_t now = playhead_cursor->current_frame();
+ framepos_t begin = now;
+ framepos_t end = _session->current_end_frame();
+
+ if (!_session->config.get_punch_in()) {
+ // First Press - set punch in and create range from here to eternity
+ set_punch_range (begin, end, _("Auto Punch In"));
+ _session->config.set_punch_in(true);
+ } else if (tpl && !_session->config.get_punch_out()) {
+ // Second press - update end range marker and set punch_out
+ if (now < tpl->start()) {
+ // playhead has been rewound - move start back and pretend nothing happened
+ begin = now;
+ set_punch_range (begin, end, _("Auto Punch In/Out"));
+ } else {
+ // normal case for 2nd press - set the punch out
+ end = playhead_cursor->current_frame ();
+ set_punch_range (tpl->start(), now, _("Auto Punch In/Out"));
+ _session->config.set_punch_out(true);
+ }
+ } else {
+ if (_session->config.get_punch_out()) {
+ _session->config.set_punch_out(false);
+ }
+
+ if (_session->config.get_punch_in()) {
+ _session->config.set_punch_in(false);
+ }
+
+ if (tpl)
+ {
+ // third press - unset punch in/out and remove range
+ _session->locations()->remove(tpl);
+ }
+ }
+
+}
+
+void
+Editor::set_session_extents_from_selection ()
+{
+ if (_session == 0) {
+ return;
+ }
+
+ framepos_t start, end;
+ if (!get_selection_extents ( start, end))
+ return;
+
+ Location* loc;
+ if ((loc = _session->locations()->session_range_location()) == 0) {
+ _session->set_session_extents (start, end); // this will create a new session range; no need for UNDO
+ } else {
+ XMLNode &before = loc->get_state();
+
+ _session->set_session_extents (start, end);
+
+ XMLNode &after = loc->get_state();
+
+ begin_reversible_command (_("set session start/end from selection"));
+
+ _session->add_command (new MementoCommand<Location>(*loc, &before, &after));
+
+ commit_reversible_command ();
+ }
+
+ _session->set_end_is_free (false);
+}
+
+void
+Editor::set_punch_start_from_edit_point ()
+{
+ if (_session) {
+
+ MusicFrame start (0, 0);
+ framepos_t end = max_framepos;
+
+ //use the existing punch end, if any
+ Location* tpl = transport_punch_location();
+ if (tpl) {
+ end = tpl->end();
+ }
+
+ if ((_edit_point == EditAtPlayhead) && _session->transport_rolling()) {
+ start.frame = _session->audible_frame();
+ } else {
+ start.frame = get_preferred_edit_position();
+ }
+
+ //snap the selection start/end
+ snap_to(start);
+
+ //if there's not already a sensible selection endpoint, go "forever"
+ if (start.frame > end ) {
+ end = max_framepos;
+ }
+
+ set_punch_range (start.frame, end, _("set punch start from EP"));
+ }
+
+}
+
+void
+Editor::set_punch_end_from_edit_point ()
+{
+ if (_session) {
+
+ framepos_t start = 0;
+ MusicFrame end (max_framepos, 0);
+
+ //use the existing punch start, if any
+ Location* tpl = transport_punch_location();
+ if (tpl) {
+ start = tpl->start();
+ }
+
+ if ((_edit_point == EditAtPlayhead) && _session->transport_rolling()) {
+ end.frame = _session->audible_frame();
+ } else {
+ end.frame = get_preferred_edit_position();
+ }
+
+ //snap the selection start/end
+ snap_to (end);
+
+ set_punch_range (start, end.frame, _("set punch end from EP"));
+
+ }
+}
+
+void
+Editor::set_loop_start_from_edit_point ()
+{
+ if (_session) {
+
+ MusicFrame start (0, 0);
+ framepos_t end = max_framepos;
+
+ //use the existing loop end, if any
+ Location* tpl = transport_loop_location();
+ if (tpl) {
+ end = tpl->end();
+ }
+
+ if ((_edit_point == EditAtPlayhead) && _session->transport_rolling()) {
+ start.frame = _session->audible_frame();
+ } else {
+ start.frame = get_preferred_edit_position();
+ }
+
+ //snap the selection start/end
+ snap_to (start);
+
+ //if there's not already a sensible selection endpoint, go "forever"
+ if (start.frame > end ) {
+ end = max_framepos;
+ }
+
+ set_loop_range (start.frame, end, _("set loop start from EP"));
+ }
+
+}
+
+void
+Editor::set_loop_end_from_edit_point ()
+{
+ if (_session) {
+
+ framepos_t start = 0;
+ MusicFrame end (max_framepos, 0);
+
+ //use the existing loop start, if any
+ Location* tpl = transport_loop_location();
+ if (tpl) {
+ start = tpl->start();
+ }
+
+ if ((_edit_point == EditAtPlayhead) && _session->transport_rolling()) {
+ end.frame = _session->audible_frame();
+ } else {
+ end.frame = get_preferred_edit_position();
+ }
+
+ //snap the selection start/end
+ snap_to(end);
+
+ set_loop_range (start, end.frame, _("set loop end from EP"));
+ }
+}
+
+void
+Editor::set_punch_from_region ()
+{
+ framepos_t start, end;
+ if (!get_selection_extents ( start, end))
+ return;
+
+ set_punch_range (start, end, _("set punch range from region"));
+}
+
+void
+Editor::pitch_shift_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ RegionSelection audio_rs;
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+ if (dynamic_cast<AudioRegionView*> (*i)) {
+ audio_rs.push_back (*i);
+ }
+ }
+
+ if (audio_rs.empty()) {
+ return;
+ }
+
+ pitch_shift (audio_rs, 1.2);
+}
+
+void
+Editor::set_tempo_from_region ()
+{
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ RegionView* rv = rs.front();
+
+ define_one_bar (rv->region()->position(), rv->region()->last_frame() + 1);
+}
+
+void
+Editor::use_range_as_bar ()
+{
+ framepos_t start, end;
+ if (get_edit_op_range (start, end)) {
+ define_one_bar (start, end);
+ }
+}
+
+void
+Editor::define_one_bar (framepos_t start, framepos_t end)
+{
+ framepos_t length = end - start;
+
+ const Meter& m (_session->tempo_map().meter_at_frame (start));
+
+ /* length = 1 bar */
+
+ /* We're going to deliver a constant tempo here,
+ so we can use frames per beat to determine length.
+ now we want frames per beat.
+ we have frames per bar, and beats per bar, so ...
+ */
+
+ /* XXXX METER MATH */
+
+ double frames_per_beat = length / m.divisions_per_bar();
+
+ /* beats per minute = */
+
+ double beats_per_minute = (_session->frame_rate() * 60.0) / frames_per_beat;
+
+ /* now decide whether to:
+
+ (a) set global tempo
+ (b) add a new tempo marker
+
+ */
+
+ const TempoSection& t (_session->tempo_map().tempo_section_at_frame (start));
+
+ bool do_global = false;
+
+ if ((_session->tempo_map().n_tempos() == 1) && (_session->tempo_map().n_meters() == 1)) {
+
+ /* only 1 tempo & 1 meter: ask if the user wants to set the tempo
+ at the start, or create a new marker
+ */
+
+ vector<string> options;
+ options.push_back (_("Cancel"));
+ options.push_back (_("Add new marker"));
+ options.push_back (_("Set global tempo"));
+
+ Choice c (
+ _("Define one bar"),
+ _("Do you want to set the global tempo or add a new tempo marker?"),
+ options
+ );
+
+ c.set_default_response (2);
+
+ switch (c.run()) {
+ case 0:
+ return;
+
+ case 2:
+ do_global = true;
+ break;
+
+ default:
+ do_global = false;
+ }
+
+ } else {
+
+ /* more than 1 tempo and/or meter section already, go ahead do the "usual":
+ if the marker is at the region starter, change it, otherwise add
+ a new tempo marker
+ */
+ }
+
+ begin_reversible_command (_("set tempo from region"));
+ XMLNode& before (_session->tempo_map().get_state());
+
+ if (do_global) {
+ _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(), t.end_note_types_per_minute());
+ } else {
+ /* constant tempo */
+ const Tempo tempo (beats_per_minute, t.note_type());
+ _session->tempo_map().add_tempo (tempo, 0.0, start, AudioTime);
+ }
+
+ XMLNode& after (_session->tempo_map().get_state());
+
+ _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
+ commit_reversible_command ();
+}
+
+void
+Editor::split_region_at_transients ()
+{
+ AnalysisFeatureList positions;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("split regions"));
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ) {
+
+ RegionSelection::iterator tmp;
+
+ tmp = i;
+ ++tmp;
+
+ boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> ((*i)->region());
+
+ if (ar) {
+ ar->transients (positions);
+ split_region_at_points ((*i)->region(), positions, true);
+ positions.clear ();
+ }
+
+ i = tmp;
+ }
+
+ commit_reversible_command ();
+
+}
+
+void
+Editor::split_region_at_points (boost::shared_ptr<Region> r, AnalysisFeatureList& positions, bool can_ferret, bool select_new)
+{
+ bool use_rhythmic_rodent = false;
+
+ boost::shared_ptr<Playlist> pl = r->playlist();
+
+ list<boost::shared_ptr<Region> > new_regions;
+
+ if (!pl) {
+ return;
+ }
+
+ if (positions.empty()) {
+ return;
+ }
+
+ if (positions.size() > 20 && can_ferret) {
+ std::string msgstr = string_compose (_("You are about to split\n%1\ninto %2 pieces.\nThis could take a long time."), r->name(), positions.size() + 1);
+ MessageDialog msg (msgstr,
+ false,
+ Gtk::MESSAGE_INFO,
+ Gtk::BUTTONS_OK_CANCEL);
+
+ if (can_ferret) {
+ msg.add_button (_("Call for the Ferret!"), RESPONSE_APPLY);
+ msg.set_secondary_text (_("Press OK to continue with this split operation\nor ask the Ferret dialog to tune the analysis"));
+ } else {
+ msg.set_secondary_text (_("Press OK to continue with this split operation"));
+ }
+
+ msg.set_title (_("Excessive split?"));
+ msg.present ();
+
+ int response = msg.run();
+ msg.hide ();
+
+ switch (response) {
+ case RESPONSE_OK:
+ break;
+ case RESPONSE_APPLY:
+ use_rhythmic_rodent = true;
+ break;
+ default:
+ return;
+ }
+ }
+
+ if (use_rhythmic_rodent) {
+ show_rhythm_ferret ();
+ return;
+ }
+
+ AnalysisFeatureList::const_iterator x;
+
+ pl->clear_changes ();
+ pl->clear_owned_changes ();
+
+ x = positions.begin();
+
+ if (x == positions.end()) {
+ return;
+ }
+
+ pl->freeze ();
+ pl->remove_region (r);
+
+ framepos_t pos = 0;
+
+ framepos_t rstart = r->first_frame ();
+ framepos_t rend = r->last_frame ();
+
+ while (x != positions.end()) {
+
+ /* deal with positons that are out of scope of present region bounds */
+ if (*x <= rstart || *x > rend) {
+ ++x;
+ continue;
+ }
+
+ /* file start = original start + how far we from the initial position ? */
+
+ framepos_t file_start = r->start() + pos;
+
+ /* length = next position - current position */
+
+ framepos_t len = (*x) - pos - rstart;
+
+ /* XXX we do we really want to allow even single-sample regions?
+ * shouldn't we have some kind of lower limit on region size?
+ */
+
+ if (len <= 0) {
+ break;
+ }
+
+ string new_name;
+
+ if (RegionFactory::region_name (new_name, r->name())) {
+ break;
+ }
+
+ /* do NOT announce new regions 1 by one, just wait till they are all done */
+
+ PropertyList plist;
+
+ plist.add (ARDOUR::Properties::start, file_start);
+ plist.add (ARDOUR::Properties::length, len);
+ plist.add (ARDOUR::Properties::name, new_name);
+ plist.add (ARDOUR::Properties::layer, 0);
+ // TODO set transients_offset
+
+ boost::shared_ptr<Region> nr = RegionFactory::create (r->sources(), plist, false);
+ /* because we set annouce to false, manually add the new region to the
+ * RegionFactory map
+ */
+ RegionFactory::map_add (nr);
+
+ pl->add_region (nr, rstart + pos);
+
+ if (select_new) {
+ new_regions.push_front(nr);
+ }
+
+ pos += len;
+ ++x;
+ }
+
+ string new_name;
+
+ RegionFactory::region_name (new_name, r->name());
+
+ /* Add the final region */
+ PropertyList plist;
+
+ plist.add (ARDOUR::Properties::start, r->start() + pos);
+ plist.add (ARDOUR::Properties::length, r->last_frame() - (r->position() + pos) + 1);
+ plist.add (ARDOUR::Properties::name, new_name);
+ plist.add (ARDOUR::Properties::layer, 0);
+
+ boost::shared_ptr<Region> nr = RegionFactory::create (r->sources(), plist, false);
+ /* because we set annouce to false, manually add the new region to the
+ RegionFactory map
+ */
+ RegionFactory::map_add (nr);
+ pl->add_region (nr, r->position() + pos);
+
+ if (select_new) {
+ new_regions.push_front(nr);
+ }
+
+ pl->thaw ();
+
+ /* We might have removed regions, which alters other regions' layering_index,
+ so we need to do a recursive diff here.
+ */
+ vector<Command*> cmds;
+ pl->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command (new StatefulDiffCommand (pl));
+
+ if (select_new) {
+
+ for (list<boost::shared_ptr<Region> >::iterator i = new_regions.begin(); i != new_regions.end(); ++i){
+ set_selected_regionview_from_region_list ((*i), Selection::Add);
+ }
+ }
+}
+
+void
+Editor::place_transient()
+{
+ if (!_session) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_edit_point ();
+
+ if (rs.empty()) {
+ return;
+ }
+
+ framepos_t where = get_preferred_edit_position();
+
+ begin_reversible_command (_("place transient"));
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+ (*r)->region()->add_transient(where);
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::remove_transient(ArdourCanvas::Item* item)
+{
+ if (!_session) {
+ return;
+ }
+
+ ArdourCanvas::Line* _line = reinterpret_cast<ArdourCanvas::Line*> (item);
+ assert (_line);
+
+ AudioRegionView* _arv = reinterpret_cast<AudioRegionView*> (item->get_data ("regionview"));
+ _arv->remove_transient (*(float*) _line->get_data ("position"));
+}
+
+void
+Editor::snap_regions_to_grid ()
+{
+ list <boost::shared_ptr<Playlist > > used_playlists;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ begin_reversible_command (_("snap regions to grid"));
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+
+ boost::shared_ptr<Playlist> pl = (*r)->region()->playlist();
+
+ if (!pl->frozen()) {
+ /* we haven't seen this playlist before */
+
+ /* remember used playlists so we can thaw them later */
+ used_playlists.push_back(pl);
+ pl->freeze();
+ }
+ (*r)->region()->clear_changes ();
+
+ MusicFrame start ((*r)->region()->first_frame (), 0);
+ snap_to (start);
+ (*r)->region()->set_position (start.frame, start.division);
+ _session->add_command(new StatefulDiffCommand ((*r)->region()));
+ }
+
+ while (used_playlists.size() > 0) {
+ list <boost::shared_ptr<Playlist > >::iterator i = used_playlists.begin();
+ (*i)->thaw();
+ used_playlists.pop_front();
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::close_region_gaps ()
+{
+ list <boost::shared_ptr<Playlist > > used_playlists;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session || rs.empty()) {
+ return;
+ }
+
+ Dialog dialog (_("Close Region Gaps"));
+
+ Table table (2, 3);
+ table.set_spacings (12);
+ table.set_border_width (12);
+ Label* l = manage (left_aligned_label (_("Crossfade length")));
+ table.attach (*l, 0, 1, 0, 1);
+
+ SpinButton spin_crossfade (1, 0);
+ spin_crossfade.set_range (0, 15);
+ spin_crossfade.set_increments (1, 1);
+ spin_crossfade.set_value (5);
+ table.attach (spin_crossfade, 1, 2, 0, 1);
+
+ table.attach (*manage (new Label (_("ms"))), 2, 3, 0, 1);
+
+ l = manage (left_aligned_label (_("Pull-back length")));
+ table.attach (*l, 0, 1, 1, 2);
+
+ SpinButton spin_pullback (1, 0);
+ spin_pullback.set_range (0, 100);
+ spin_pullback.set_increments (1, 1);
+ spin_pullback.set_value(30);
+ table.attach (spin_pullback, 1, 2, 1, 2);
+
+ table.attach (*manage (new Label (_("ms"))), 2, 3, 1, 2);
+
+ dialog.get_vbox()->pack_start (table);
+ dialog.add_button (Stock::CANCEL, RESPONSE_CANCEL);
+ dialog.add_button (_("Ok"), RESPONSE_ACCEPT);
+ dialog.show_all ();
+
+ if (dialog.run () == RESPONSE_CANCEL) {
+ return;
+ }
+
+ framepos_t crossfade_len = spin_crossfade.get_value();
+ framepos_t pull_back_frames = spin_pullback.get_value();
+
+ crossfade_len = lrintf (crossfade_len * _session->frame_rate()/1000);
+ pull_back_frames = lrintf (pull_back_frames * _session->frame_rate()/1000);
+
+ /* Iterate over the region list and make adjacent regions overlap by crossfade_len_ms */
+
+ begin_reversible_command (_("close region gaps"));
+
+ int idx = 0;
+ boost::shared_ptr<Region> last_region;
+
+ rs.sort_by_position_and_track();
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+
+ boost::shared_ptr<Playlist> pl = (*r)->region()->playlist();
+
+ if (!pl->frozen()) {
+ /* we haven't seen this playlist before */
+
+ /* remember used playlists so we can thaw them later */
+ used_playlists.push_back(pl);
+ pl->freeze();
+ }
+
+ framepos_t position = (*r)->region()->position();
+
+ if (idx == 0 || position < last_region->position()){
+ last_region = (*r)->region();
+ idx++;
+ continue;
+ }
+
+ (*r)->region()->clear_changes ();
+ (*r)->region()->trim_front( (position - pull_back_frames));
+
+ last_region->clear_changes ();
+ last_region->trim_end( (position - pull_back_frames + crossfade_len));
+
+ _session->add_command (new StatefulDiffCommand ((*r)->region()));
+ _session->add_command (new StatefulDiffCommand (last_region));
+
+ last_region = (*r)->region();
+ idx++;
+ }
+
+ while (used_playlists.size() > 0) {
+ list <boost::shared_ptr<Playlist > >::iterator i = used_playlists.begin();
+ (*i)->thaw();
+ used_playlists.pop_front();
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::tab_to_transient (bool forward)
+{
+ AnalysisFeatureList positions;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (!_session) {
+ return;
+ }
+
+ framepos_t pos = _session->audible_frame ();
+
+ if (!selection->tracks.empty()) {
+
+ /* don't waste time searching for transients in duplicate playlists.
+ */
+
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+
+ for (TrackViewList::iterator t = ts.begin(); t != ts.end(); ++t) {
+
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*t);
+
+ if (rtv) {
+ boost::shared_ptr<Track> tr = rtv->track();
+ if (tr) {
+ boost::shared_ptr<Playlist> pl = tr->playlist ();
+ if (pl) {
+ framepos_t result = pl->find_next_transient (pos, forward ? 1 : -1);
+
+ if (result >= 0) {
+ positions.push_back (result);
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+
+ if (rs.empty()) {
+ return;
+ }
+
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
+ (*r)->region()->get_transients (positions);
+ }
+ }
+
+ TransientDetector::cleanup_transients (positions, _session->frame_rate(), 3.0);
+
+ if (forward) {
+ AnalysisFeatureList::iterator x;
+
+ for (x = positions.begin(); x != positions.end(); ++x) {
+ if ((*x) > pos) {
+ break;
+ }
+ }
+
+ if (x != positions.end ()) {
+ _session->request_locate (*x);
+ }
+
+ } else {
+ AnalysisFeatureList::reverse_iterator x;
+
+ for (x = positions.rbegin(); x != positions.rend(); ++x) {
+ if ((*x) < pos) {
+ break;
+ }
+ }
+
+ if (x != positions.rend ()) {
+ _session->request_locate (*x);
+ }
+ }
+}
+
+void
+Editor::playhead_forward_to_grid ()
+{
+ if (!_session) {
+ return;
+ }
+
+ MusicFrame pos (playhead_cursor->current_frame (), 0);
+
+ if (pos.frame < max_framepos - 1) {
+ pos.frame += 2;
+ snap_to_internal (pos, RoundUpAlways, false, true);
+ _session->request_locate (pos.frame);
+ }
+}
+
+
+void
+Editor::playhead_backward_to_grid ()
+{
+ if (!_session) {
+ return;
+ }
+
+ MusicFrame pos (playhead_cursor->current_frame (), 0);
+
+ if (pos.frame > 2) {
+ pos.frame -= 2;
+ snap_to_internal (pos, RoundDownAlways, false, true);
+ _session->request_locate (pos.frame);
+ }
+}
+
+void
+Editor::set_track_height (Height h)
+{
+ TrackSelection& ts (selection->tracks);
+
+ for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) {
+ (*x)->set_height_enum (h);
+ }
+}
+
+void
+Editor::toggle_tracks_active ()
+{
+ TrackSelection& ts (selection->tracks);
+ bool first = true;
+ bool target = false;
+
+ if (ts.empty()) {
+ return;
+ }
+
+ for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(*x);
+
+ if (rtv) {
+ if (first) {
+ target = !rtv->_route->active();
+ first = false;
+ }
+ rtv->_route->set_active (target, this);
+ }
+ }
+}
+
+void
+Editor::remove_tracks ()
+{
+ /* this will delete GUI objects that may be the subject of an event
+ handler in which this method is called. Defer actual deletion to the
+ next idle callback, when all event handling is finished.
+ */
+ Glib::signal_idle().connect (sigc::mem_fun (*this, &Editor::idle_remove_tracks));
+}
+
+bool
+Editor::idle_remove_tracks ()
+{
+ Session::StateProtector sp (_session);
+ _remove_tracks ();
+ return false; /* do not call again */
+}
+
+void
+Editor::_remove_tracks ()
+{
+ TrackSelection& ts (selection->tracks);
+
+ if (ts.empty()) {
+ return;
+ }
+
+ vector<string> choices;
+ string prompt;
+ int ntracks = 0;
+ int nbusses = 0;
+ int nvcas = 0;
+ const char* trackstr;
+ const char* busstr;
+ const char* vcastr;
+ vector<boost::shared_ptr<Route> > routes;
+ vector<boost::shared_ptr<VCA> > vcas;
+ bool special_bus = false;
+
+ for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) {
+ VCATimeAxisView* vtv = dynamic_cast<VCATimeAxisView*> (*x);
+ if (vtv) {
+ vcas.push_back (vtv->vca());
+ ++nvcas;
+ continue;
+ }
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*x);
+ if (!rtv) {
+ continue;
+ }
+ if (rtv->is_track()) {
+ ++ntracks;
+ } else {
+ ++nbusses;
+ }
+ routes.push_back (rtv->_route);
+
+ if (rtv->route()->is_master() || rtv->route()->is_monitor()) {
+ special_bus = true;
+ }
+ }
+
+ if (special_bus && !Config->get_allow_special_bus_removal()) {
+ MessageDialog msg (_("That would be bad news ...."),
+ false,
+ Gtk::MESSAGE_INFO,
+ Gtk::BUTTONS_OK);
+ msg.set_secondary_text (string_compose (_(
+ "Removing the master or monitor bus is such a bad idea\n\
+that %1 is not going to allow it.\n\
+\n\
+If you really want to do this sort of thing\n\
+edit your ardour.rc file to set the\n\
+\"allow-special-bus-removal\" option to be \"yes\""), PROGRAM_NAME));
+
+ msg.present ();
+ msg.run ();
+ return;
+ }
+
+ if (ntracks + nbusses + nvcas == 0) {
+ return;
+ }
+
+ string title;
+
+ trackstr = P_("track", "tracks", ntracks);
+ busstr = P_("bus", "busses", nbusses);
+ vcastr = P_("VCA", "VCAs", nvcas);
+
+ if (ntracks > 0 && nbusses > 0 && nvcas > 0) {
+ title = _("Remove various strips");
+ prompt = string_compose (_("Do you really want to remove %1 %2, %3 %4 and %5 %6?"),
+ ntracks, trackstr, nbusses, busstr, nvcas, vcastr);
+ }
+ else if (ntracks > 0 && nbusses > 0) {
+ title = string_compose (_("Remove %1 and %2"), trackstr, busstr);
+ prompt = string_compose (_("Do you really want to remove %1 %2 and %3 %4?"),
+ ntracks, trackstr, nbusses, busstr);
+ }
+ else if (ntracks > 0 && nvcas > 0) {
+ title = string_compose (_("Remove %1 and %2"), trackstr, vcastr);
+ prompt = string_compose (_("Do you really want to remove %1 %2 and %3 %4?"),
+ ntracks, trackstr, nvcas, vcastr);
+ }
+ else if (nbusses > 0 && nvcas > 0) {
+ title = string_compose (_("Remove %1 and %2"), busstr, vcastr);
+ prompt = string_compose (_("Do you really want to remove %1 %2 and %3 %4?"),
+ nbusses, busstr, nvcas, vcastr);
+ }
+ else if (ntracks > 0) {
+ title = string_compose (_("Remove %1"), trackstr);
+ prompt = string_compose (_("Do you really want to remove %1 %2?"),
+ ntracks, trackstr);
+ }
+ else if (nbusses > 0) {
+ title = string_compose (_("Remove %1"), busstr);
+ prompt = string_compose (_("Do you really want to remove %1 %2?"),
+ nbusses, busstr);
+ }
+ else if (nvcas > 0) {
+ title = string_compose (_("Remove %1"), vcastr);
+ prompt = string_compose (_("Do you really want to remove %1 %2?"),
+ nvcas, vcastr);
+ }
+ else {
+ assert (0);
+ }
+
+ if (ntracks > 0) {
+ prompt += "\n" + string_compose ("(You may also lose the playlists associated with the %1)", trackstr) + "\n";
+ }
+
+ prompt += "\n" + string(_("This action cannot be undone, and the session file will be overwritten!"));
+
+ choices.push_back (_("No, do nothing."));
+ if (ntracks + nbusses + nvcas > 1) {
+ choices.push_back (_("Yes, remove them."));
+ } else {
+ choices.push_back (_("Yes, remove it."));
+ }
+
+ Choice prompter (title, prompt, choices);
+
+ if (prompter.run () != 1) {
+ return;
+ }
+
+ if (current_mixer_strip && routes.size () > 1 && std::find (routes.begin(), routes.end(), current_mixer_strip->route()) != routes.end ()) {
+ /* Route deletion calls Editor::timeaxisview_deleted() iteratively (for each deleted
+ * route). If the deleted route is currently displayed in the Editor-Mixer (highly
+ * likely because deletion requires selection) this will call
+ * Editor::set_selected_mixer_strip () which is expensive ( MixerStrip::set_route() ).
+ * It's likewise likely that the route that has just been displayed in the
+ * Editor-Mixer will be next in line for deletion.
+ *
+ * So simply switch to the master-bus (if present)
+ */
+ for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ if ((*i)->stripable ()->is_master ()) {
+ set_selected_mixer_strip (*(*i));
+ break;
+ }
+ }
+ }
+
+ {
+ PresentationInfo::ChangeSuspender cs;
+ DisplaySuspender ds;
+
+ boost::shared_ptr<RouteList> rl (new RouteList);
+ for (vector<boost::shared_ptr<Route> >::iterator x = routes.begin(); x != routes.end(); ++x) {
+ rl->push_back (*x);
+ }
+ _session->remove_routes (rl);
+
+ for (vector<boost::shared_ptr<VCA> >::iterator x = vcas.begin(); x != vcas.end(); ++x) {
+ _session->vca_manager().remove_vca (*x);
+ }
+
+ }
+ /* TrackSelection and RouteList leave scope,
+ * destructors are called,
+ * diskstream drops references, save_state is called (again for every track)
+ */
+}
+
+void
+Editor::do_insert_time ()
+{
+ if (selection->tracks.empty()) {
+ return;
+ }
+
+ InsertRemoveTimeDialog d (*this);
+ int response = d.run ();
+
+ if (response != RESPONSE_OK) {
+ return;
+ }
+
+ if (d.distance() == 0) {
+ return;
+ }
+
+ insert_time (
+ d.position(),
+ d.distance(),
+ d.intersected_region_action (),
+ d.all_playlists(),
+ d.move_glued(),
+ d.move_markers(),
+ d.move_glued_markers(),
+ d.move_locked_markers(),
+ d.move_tempos()
+ );
+}
+
+void
+Editor::insert_time (
+ framepos_t pos, framecnt_t frames, InsertTimeOption opt,
+ bool all_playlists, bool ignore_music_glue, bool markers_too, bool glued_markers_too, bool locked_markers_too, bool tempo_too
+ )
+{
+
+ if (Config->get_edit_mode() == Lock) {
+ return;
+ }
+ bool in_command = false;
+
+ TrackViewList ts = selection->tracks.filter_to_unique_playlists ();
+
+ for (TrackViewList::iterator x = ts.begin(); x != ts.end(); ++x) {
+
+ /* regions */
+
+ /* don't operate on any playlist more than once, which could
+ * happen if "all playlists" is enabled, but there is more
+ * than 1 track using playlists "from" a given track.
+ */
+
+ set<boost::shared_ptr<Playlist> > pl;
+
+ if (all_playlists) {
+ RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*x);
+ if (rtav && rtav->track ()) {
+ vector<boost::shared_ptr<Playlist> > all = _session->playlists->playlists_for_track (rtav->track ());
+ for (vector<boost::shared_ptr<Playlist> >::iterator p = all.begin(); p != all.end(); ++p) {
+ pl.insert (*p);
+ }
+ }
+ } else {
+ if ((*x)->playlist ()) {
+ pl.insert ((*x)->playlist ());
+ }
+ }
+
+ for (set<boost::shared_ptr<Playlist> >::iterator i = pl.begin(); i != pl.end(); ++i) {
+
+ (*i)->clear_changes ();
+ (*i)->clear_owned_changes ();
+
+ if (!in_command) {
+ begin_reversible_command (_("insert time"));
+ in_command = true;
+ }
+
+ if (opt == SplitIntersected) {
+ /* non musical split */
+ (*i)->split (MusicFrame (pos, 0));
+ }
+
+ (*i)->shift (pos, frames, (opt == MoveIntersected), ignore_music_glue);
+
+ vector<Command*> cmds;
+ (*i)->rdiff (cmds);
+ _session->add_commands (cmds);
+
+ _session->add_command (new StatefulDiffCommand (*i));
+ }
+
+ /* automation */
+ RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*x);
+ if (rtav) {
+ if (!in_command) {
+ begin_reversible_command (_("insert time"));
+ in_command = true;
+ }
+ rtav->route ()->shift (pos, frames);
+ }
+ }
+
+ /* markers */
+ if (markers_too) {
+ bool moved = false;
+ const int32_t divisions = get_grid_music_divisions (0);
+ XMLNode& before (_session->locations()->get_state());
+ Locations::LocationList copy (_session->locations()->list());
+
+ for (Locations::LocationList::iterator i = copy.begin(); i != copy.end(); ++i) {
+
+ Locations::LocationList::const_iterator tmp;
+
+ if ((*i)->position_lock_style() == AudioTime || glued_markers_too) {
+ bool const was_locked = (*i)->locked ();
+ if (locked_markers_too) {
+ (*i)->unlock ();
+ }
+
+ if ((*i)->start() >= pos) {
+ // move end first, in case we're moving by more than the length of the range
+ if (!(*i)->is_mark()) {
+ (*i)->set_end ((*i)->end() + frames, false, true, divisions);
+ }
+ (*i)->set_start ((*i)->start() + frames, false, true, divisions);
+ moved = true;
+ }
+
+ if (was_locked) {
+ (*i)->lock ();
+ }
+ }
+ }
+
+ if (moved) {
+ if (!in_command) {
+ begin_reversible_command (_("insert time"));
+ in_command = true;
+ }
+ XMLNode& after (_session->locations()->get_state());
+ _session->add_command (new MementoCommand<Locations>(*_session->locations(), &before, &after));
+ }
+ }
+
+ if (tempo_too) {
+ if (!in_command) {
+ begin_reversible_command (_("insert time"));
+ in_command = true;
+ }
+ XMLNode& before (_session->tempo_map().get_state());
+ _session->tempo_map().insert_time (pos, frames);
+ XMLNode& after (_session->tempo_map().get_state());
+ _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::do_remove_time ()
+{
+ if (selection->tracks.empty()) {
+ return;
+ }
+
+ InsertRemoveTimeDialog d (*this, true);
+
+ int response = d.run ();
+
+ if (response != RESPONSE_OK) {
+ return;
+ }
+
+ framecnt_t distance = d.distance();
+
+ if (distance == 0) {
+ return;
+ }
+
+ remove_time (
+ d.position(),
+ distance,
+ SplitIntersected,
+ d.move_glued(),
+ d.move_markers(),
+ d.move_glued_markers(),
+ d.move_locked_markers(),
+ d.move_tempos()
+ );
+}
+
+void
+Editor::remove_time (framepos_t pos, framecnt_t frames, InsertTimeOption opt,
+ bool ignore_music_glue, bool markers_too, bool glued_markers_too, bool locked_markers_too, bool tempo_too)
+{
+ if (Config->get_edit_mode() == Lock) {
+ error << (_("Cannot insert or delete time when in Lock edit.")) << endmsg;
+ return;
+ }
+ bool in_command = false;
+
+ for (TrackSelection::iterator x = selection->tracks.begin(); x != selection->tracks.end(); ++x) {
+ /* regions */
+ boost::shared_ptr<Playlist> pl = (*x)->playlist();
+
+ if (pl) {
+
+ XMLNode &before = pl->get_state();
+
+ if (!in_command) {
+ begin_reversible_command (_("remove time"));
+ in_command = true;
+ }
+
+ std::list<AudioRange> rl;
+ AudioRange ar(pos, pos+frames, 0);
+ rl.push_back(ar);
+ pl->cut (rl);
+ pl->shift (pos, -frames, true, ignore_music_glue);
+
+ XMLNode &after = pl->get_state();
+
+ _session->add_command (new MementoCommand<Playlist> (*pl, &before, &after));
+ }
+
+ /* automation */
+ RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*x);
+ if (rtav) {
+ if (!in_command) {
+ begin_reversible_command (_("remove time"));
+ in_command = true;
+ }
+ rtav->route ()->shift (pos, -frames);
+ }
+ }
+
+ const int32_t divisions = get_grid_music_divisions (0);
+ std::list<Location*> loc_kill_list;
+
+ /* markers */
+ if (markers_too) {
+ bool moved = false;
+ XMLNode& before (_session->locations()->get_state());
+ Locations::LocationList copy (_session->locations()->list());
+
+ for (Locations::LocationList::iterator i = copy.begin(); i != copy.end(); ++i) {
+ if ((*i)->position_lock_style() == AudioTime || glued_markers_too) {
+
+ bool const was_locked = (*i)->locked ();
+ if (locked_markers_too) {
+ (*i)->unlock ();
+ }
+
+ if (!(*i)->is_mark()) { // it's a range; have to handle both start and end
+ if ((*i)->end() >= pos
+ && (*i)->end() < pos+frames
+ && (*i)->start() >= pos
+ && (*i)->end() < pos+frames) { // range is completely enclosed; kill it
+ moved = true;
+ loc_kill_list.push_back(*i);
+ } else { // only start or end is included, try to do the right thing
+ // move start before moving end, to avoid trying to move the end to before the start
+ // if we're removing more time than the length of the range
+ if ((*i)->start() >= pos && (*i)->start() < pos+frames) {
+ // start is within cut
+ (*i)->set_start (pos, false, true,divisions); // bring the start marker to the beginning of the cut
+ moved = true;
+ } else if ((*i)->start() >= pos+frames) {
+ // start (and thus entire range) lies beyond end of cut
+ (*i)->set_start ((*i)->start() - frames, false, true, divisions); // slip the start marker back
+ moved = true;
+ }
+ if ((*i)->end() >= pos && (*i)->end() < pos+frames) {
+ // end is inside cut
+ (*i)->set_end (pos, false, true, divisions); // bring the end to the cut
+ moved = true;
+ } else if ((*i)->end() >= pos+frames) {
+ // end is beyond end of cut
+ (*i)->set_end ((*i)->end() - frames, false, true, divisions); // slip the end marker back
+ moved = true;
+ }
+
+ }
+ } else if ((*i)->start() >= pos && (*i)->start() < pos+frames ) {
+ loc_kill_list.push_back(*i);
+ moved = true;
+ } else if ((*i)->start() >= pos) {
+ (*i)->set_start ((*i)->start() -frames, false, true, divisions);
+ moved = true;
+ }
+
+ if (was_locked) {
+ (*i)->lock ();
+ }
+ }
+ }
+
+ for (list<Location*>::iterator i = loc_kill_list.begin(); i != loc_kill_list.end(); ++i) {
+ _session->locations()->remove( *i );
+ }
+
+ if (moved) {
+ if (!in_command) {
+ begin_reversible_command (_("remove time"));
+ in_command = true;
+ }
+ XMLNode& after (_session->locations()->get_state());
+ _session->add_command (new MementoCommand<Locations>(*_session->locations(), &before, &after));
+ }
+ }
+
+ if (tempo_too) {
+ XMLNode& before (_session->tempo_map().get_state());
+
+ if (_session->tempo_map().remove_time (pos, frames) ) {
+ if (!in_command) {
+ begin_reversible_command (_("remove time"));
+ in_command = true;
+ }
+ XMLNode& after (_session->tempo_map().get_state());
+ _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
+ }
+ }
+
+ if (in_command) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::fit_selection ()
+{
+ if (!selection->tracks.empty()) {
+ fit_tracks (selection->tracks);
+ } else {
+ TrackViewList tvl;
+
+ /* no selected tracks - use tracks with selected regions */
+
+ if (!selection->regions.empty()) {
+ for (RegionSelection::iterator r = selection->regions.begin(); r != selection->regions.end(); ++r) {
+ tvl.push_back (&(*r)->get_time_axis_view ());
+ }
+
+ if (!tvl.empty()) {
+ fit_tracks (tvl);
+ }
+ } else if (internal_editing()) {
+ /* no selected tracks, or regions, but in internal edit mode, so follow the mouse and use
+ * the entered track
+ */
+ if (entered_track) {
+ tvl.push_back (entered_track);
+ fit_tracks (tvl);
+ }
+ }
+ }
+}
+
+void
+Editor::fit_tracks (TrackViewList & tracks)
+{
+ if (tracks.empty()) {
+ return;
+ }
+
+ uint32_t child_heights = 0;
+ int visible_tracks = 0;
+
+ for (TrackSelection::iterator t = tracks.begin(); t != tracks.end(); ++t) {
+
+ if (!(*t)->marked_for_display()) {
+ continue;
+ }
+
+ child_heights += (*t)->effective_height() - (*t)->current_height();
+ ++visible_tracks;
+ }
+
+ /* compute the per-track height from:
+ *
+ * total canvas visible height
+ * - height that will be taken by visible children of selected tracks
+ * - height of the ruler/hscroll area
+ */
+ uint32_t h = (uint32_t) floor ((trackviews_height() - child_heights) / visible_tracks);
+ double first_y_pos = DBL_MAX;
+
+ if (h < TimeAxisView::preset_height (HeightSmall)) {
+ MessageDialog msg (_("There are too many tracks to fit in the current window"));
+ /* too small to be displayed */
+ return;
+ }
+
+ undo_visual_stack.push_back (current_visual_state (true));
+ PBD::Unwinder<bool> nsv (no_save_visual, true);
+
+ /* build a list of all tracks, including children */
+
+ TrackViewList all;
+ for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+ all.push_back (*i);
+ TimeAxisView::Children c = (*i)->get_child_list ();
+ for (TimeAxisView::Children::iterator j = c.begin(); j != c.end(); ++j) {
+ all.push_back (j->get());
+ }
+ }
+
+
+ // find selection range.
+ // if someone knows how to user TrackViewList::iterator for this
+ // I'm all ears.
+ int selected_top = -1;
+ int selected_bottom = -1;
+ int i = 0;
+ for (TrackViewList::iterator t = all.begin(); t != all.end(); ++t, ++i) {
+ if ((*t)->marked_for_display ()) {
+ if (tracks.contains(*t)) {
+ if (selected_top == -1) {
+ selected_top = i;
+ }
+ selected_bottom = i;
+ }
+ }
+ }
+
+ i = 0;
+ for (TrackViewList::iterator t = all.begin(); t != all.end(); ++t, ++i) {
+ if ((*t)->marked_for_display ()) {
+ if (tracks.contains(*t)) {
+ (*t)->set_height (h);
+ first_y_pos = std::min ((*t)->y_position (), first_y_pos);
+ } else {
+ if (i > selected_top && i < selected_bottom) {
+ hide_track_in_display (*t);
+ }
+ }
+ }
+ }
+
+ /*
+ set the controls_layout height now, because waiting for its size
+ request signal handler will cause the vertical adjustment setting to fail
+ */
+
+ controls_layout.property_height () = _full_canvas_height;
+ vertical_adjustment.set_value (first_y_pos);
+
+ redo_visual_stack.push_back (current_visual_state (true));
+
+ visible_tracks_selector.set_text (_("Sel"));
+}
+
+void
+Editor::save_visual_state (uint32_t n)
+{
+ while (visual_states.size() <= n) {
+ visual_states.push_back (0);
+ }
+
+ if (visual_states[n] != 0) {
+ delete visual_states[n];
+ }
+
+ visual_states[n] = current_visual_state (true);
+ gdk_beep ();
+}
+
+void
+Editor::goto_visual_state (uint32_t n)
+{
+ if (visual_states.size() <= n) {
+ return;
+ }
+
+ if (visual_states[n] == 0) {
+ return;
+ }
+
+ use_visual_state (*visual_states[n]);
+}
+
+void
+Editor::start_visual_state_op (uint32_t n)
+{
+ save_visual_state (n);
+
+ PopUp* pup = new PopUp (WIN_POS_MOUSE, 1000, true);
+ char buf[32];
+ snprintf (buf, sizeof (buf), _("Saved view %u"), n+1);
+ pup->set_text (buf);
+ pup->touch();
+}
+
+void
+Editor::cancel_visual_state_op (uint32_t n)
+{
+ goto_visual_state (n);
+}
+
+void
+Editor::toggle_region_mute ()
+{
+ if (_ignore_region_action) {
+ return;
+ }
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+
+ if (rs.empty ()) {
+ return;
+ }
+
+ if (rs.size() > 1) {
+ begin_reversible_command (_("mute regions"));
+ } else {
+ begin_reversible_command (_("mute region"));
+ }
+
+ for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
+
+ (*i)->region()->playlist()->clear_changes ();
+ (*i)->region()->set_muted (!(*i)->region()->muted ());
+ _session->add_command (new StatefulDiffCommand ((*i)->region()));
+
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::combine_regions ()
+{
+ /* foreach track with selected regions, take all selected regions
+ and join them into a new region containing the subregions (as a
+ playlist)
+ */
+
+ typedef set<RouteTimeAxisView*> RTVS;
+ RTVS tracks;
+
+ if (selection->regions.empty()) {
+ return;
+ }
+
+ for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&(*i)->get_time_axis_view());
+
+ if (rtv) {
+ tracks.insert (rtv);
+ }
+ }
+
+ begin_reversible_command (_("combine regions"));
+
+ vector<RegionView*> new_selection;
+
+ for (RTVS::iterator i = tracks.begin(); i != tracks.end(); ++i) {
+ RegionView* rv;
+
+ if ((rv = (*i)->combine_regions ()) != 0) {
+ new_selection.push_back (rv);
+ }
+ }
+
+ selection->clear_regions ();
+ for (vector<RegionView*>::iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
+ selection->add (*i);
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::uncombine_regions ()
+{
+ typedef set<RouteTimeAxisView*> RTVS;
+ RTVS tracks;
+
+ if (selection->regions.empty()) {
+ return;
+ }
+
+ for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&(*i)->get_time_axis_view());
+
+ if (rtv) {
+ tracks.insert (rtv);
+ }
+ }
+
+ begin_reversible_command (_("uncombine regions"));
+
+ for (RTVS::iterator i = tracks.begin(); i != tracks.end(); ++i) {
+ (*i)->uncombine_regions ();
+ }
+
+ commit_reversible_command ();
+}
+
+void
+Editor::toggle_midi_input_active (bool flip_others)
+{
+ bool onoff = false;
+ boost::shared_ptr<RouteList> rl (new RouteList);
+
+ for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
+ RouteTimeAxisView *rtav = dynamic_cast<RouteTimeAxisView *>(*i);
+
+ if (!rtav) {
+ continue;
+ }
+
+ boost::shared_ptr<MidiTrack> mt = rtav->midi_track();
+
+ if (mt) {
+ rl->push_back (rtav->route());
+ onoff = !mt->input_active();
+ }
+ }
+
+ _session->set_exclusive_input_active (rl, onoff, flip_others);
+}
+
+static bool ok_fine (GdkEventAny*) { return true; }
+
+void
+Editor::lock ()
+{
+ if (!lock_dialog) {
+ lock_dialog = new Gtk::Dialog (string_compose (_("%1: Locked"), PROGRAM_NAME), true);
+
+ Gtk::Image* padlock = manage (new Gtk::Image (ARDOUR_UI_UTILS::get_icon ("padlock_closed")));
+ lock_dialog->get_vbox()->pack_start (*padlock);
+ lock_dialog->signal_delete_event ().connect (sigc::ptr_fun (ok_fine));
+
+ ArdourButton* b = manage (new ArdourButton);
+ b->set_name ("lock button");
+ b->set_text (_("Click to unlock"));
+ b->signal_clicked.connect (sigc::mem_fun (*this, &Editor::unlock));
+ lock_dialog->get_vbox()->pack_start (*b);
+
+ lock_dialog->get_vbox()->show_all ();
+ lock_dialog->set_size_request (200, 200);
+ }
+
+ delete _main_menu_disabler;
+ _main_menu_disabler = new MainMenuDisabler;
+
+ lock_dialog->present ();
+
+ lock_dialog->get_window()->set_decorations (Gdk::WMDecoration (0));
+}
+
+void
+Editor::unlock ()
+{
+ lock_dialog->hide ();
+
+ delete _main_menu_disabler;
+ _main_menu_disabler = 0;
+
+ if (UIConfiguration::instance().get_lock_gui_after_seconds()) {
+ start_lock_event_timing ();
+ }
+}
+
+void
+Editor::bring_in_callback (Gtk::Label* label, uint32_t n, uint32_t total, string name)
+{
+ Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&Editor::update_bring_in_message, this, label, n, total, name));
+}
+
+void
+Editor::update_bring_in_message (Gtk::Label* label, uint32_t n, uint32_t total, string name)
+{
+ Timers::TimerSuspender t;
+ label->set_text (string_compose ("Copying %1, %2 of %3", name, n, total));
+ Gtkmm2ext::UI::instance()->flush_pending (1);
+}
+
+void
+Editor::bring_all_sources_into_session ()
+{
+ if (!_session) {
+ return;
+ }
+
+ Gtk::Label msg;
+ ArdourDialog w (_("Moving embedded files into session folder"));
+ w.get_vbox()->pack_start (msg);
+ w.present ();
+
+ /* flush all pending GUI events because we're about to start copying
+ * files
+ */
+
+ Timers::TimerSuspender t;
+ Gtkmm2ext::UI::instance()->flush_pending (3);
+
+ cerr << " Do it\n";
+
+ _session->bring_all_sources_into_session (boost::bind (&Editor::bring_in_callback, this, &msg, _1, _2, _3));
+}
diff --git a/gtk2_ardour/midi_region_view.cc.orig b/gtk2_ardour/midi_region_view.cc.orig
new file mode 100644
index 0000000000..47d1843407
--- /dev/null
+++ b/gtk2_ardour/midi_region_view.cc.orig
@@ -0,0 +1,4428 @@
+/*
+ Copyright (C) 2001-2011 Paul Davis
+ Author: David Robillard
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <cmath>
+#include <algorithm>
+#include <ostream>
+
+#include <gtkmm.h>
+
+#include "gtkmm2ext/gtk_ui.h"
+
+#include <sigc++/signal.h>
+
+#include "midi++/midnam_patch.h"
+
+#include "pbd/memento_command.h"
+#include "pbd/stateful_diff_command.h"
+
+#include "ardour/midi_model.h"
+#include "ardour/midi_playlist.h"
+#include "ardour/midi_region.h"
+#include "ardour/midi_source.h"
+#include "ardour/midi_track.h"
+#include "ardour/operations.h"
+#include "ardour/session.h"
+
+#include "evoral/Parameter.hpp"
+#include "evoral/Event.hpp"
+#include "evoral/Control.hpp"
+#include "evoral/midi_util.h"
+
+#include "canvas/debug.h"
+#include "canvas/text.h"
+
+#include "automation_region_view.h"
+#include "automation_time_axis.h"
+#include "control_point.h"
+#include "debug.h"
+#include "editor.h"
+#include "editor_drag.h"
+#include "ghostregion.h"
+#include "gui_thread.h"
+#include "item_counts.h"
+#include "keyboard.h"
+#include "midi_channel_dialog.h"
+#include "midi_cut_buffer.h"
+#include "midi_list_editor.h"
+#include "midi_region_view.h"
+#include "midi_streamview.h"
+#include "midi_time_axis.h"
+#include "midi_util.h"
+#include "midi_velocity_dialog.h"
+#include "mouse_cursors.h"
+#include "note_player.h"
+#include "paste_context.h"
+#include "public_editor.h"
+#include "route_time_axis.h"
+#include "rgb_macros.h"
+#include "selection.h"
+#include "streamview.h"
+#include "patch_change_dialog.h"
+#include "verbose_cursor.h"
+#include "note.h"
+#include "hit.h"
+#include "patch_change.h"
+#include "sys_ex.h"
+#include "ui_config.h"
+
+#include "pbd/i18n.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace Editing;
+using namespace std;
+using Gtkmm2ext::Keyboard;
+
+#define MIDI_BP_ZERO ((Config->get_first_midi_bank_is_zero())?0:1)
+
+MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
+ RouteTimeAxisView& tv,
+ boost::shared_ptr<MidiRegion> r,
+ double spu,
+ uint32_t basic_color)
+ : RegionView (parent, tv, r, spu, basic_color)
+ , _current_range_min(0)
+ , _current_range_max(0)
+ , _region_relative_time_converter(r->session().tempo_map(), r->position())
+ , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
+ , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
+ , _active_notes(0)
+ , _note_group (new ArdourCanvas::Container (group))
+ , _note_diff_command (0)
+ , _ghost_note(0)
+ , _step_edit_cursor (0)
+ , _step_edit_cursor_width (1.0)
+ , _step_edit_cursor_position (0.0)
+ , _channel_selection_scoped_note (0)
+ , _mouse_state(None)
+ , _pressed_button(0)
+ , _optimization_iterator (_events.end())
+ , _list_editor (0)
+ , _no_sound_notes (false)
+ , _last_display_zoom (0)
+ , _last_event_x (0)
+ , _last_event_y (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
+ , _entered_note (0)
+ , _mouse_changed_selection (false)
+{
+ CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
+
+ _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
+ _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
+
+ _note_group->raise_to_top();
+ PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
+
+ Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
+ connect_to_diskstream ();
+}
+
+MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent,
+ RouteTimeAxisView& tv,
+ boost::shared_ptr<MidiRegion> r,
+ double spu,
+ uint32_t basic_color,
+ bool recording,
+ TimeAxisViewItem::Visibility visibility)
+ : RegionView (parent, tv, r, spu, basic_color, recording, visibility)
+ , _current_range_min(0)
+ , _current_range_max(0)
+ , _region_relative_time_converter(r->session().tempo_map(), r->position())
+ , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start())
+ , _region_relative_time_converter_double(r->session().tempo_map(), r->position())
+ , _active_notes(0)
+ , _note_group (new ArdourCanvas::Container (group))
+ , _note_diff_command (0)
+ , _ghost_note(0)
+ , _step_edit_cursor (0)
+ , _step_edit_cursor_width (1.0)
+ , _step_edit_cursor_position (0.0)
+ , _channel_selection_scoped_note (0)
+ , _mouse_state(None)
+ , _pressed_button(0)
+ , _optimization_iterator (_events.end())
+ , _list_editor (0)
+ , _no_sound_notes (false)
+ , _last_display_zoom (0)
+ , _last_event_x (0)
+ , _last_event_y (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
+ , _entered_note (0)
+ , _mouse_changed_selection (false)
+{
+ CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
+
+ _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
+ _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
+
+ _note_group->raise_to_top();
+
+ PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
+
+ connect_to_diskstream ();
+}
+
+void
+MidiRegionView::parameter_changed (std::string const & p)
+{
+ if (p == "display-first-midi-bank-as-zero") {
+ if (_enable_display) {
+ redisplay_model();
+ }
+ } else if (p == "color-regions-using-track-color") {
+ set_colors ();
+ }
+}
+
+MidiRegionView::MidiRegionView (const MidiRegionView& other)
+ : sigc::trackable(other)
+ , RegionView (other)
+ , _current_range_min(0)
+ , _current_range_max(0)
+ , _region_relative_time_converter(other.region_relative_time_converter())
+ , _source_relative_time_converter(other.source_relative_time_converter())
+ , _region_relative_time_converter_double(other.region_relative_time_converter_double())
+ , _active_notes(0)
+ , _note_group (new ArdourCanvas::Container (get_canvas_group()))
+ , _note_diff_command (0)
+ , _ghost_note(0)
+ , _step_edit_cursor (0)
+ , _step_edit_cursor_width (1.0)
+ , _step_edit_cursor_position (0.0)
+ , _channel_selection_scoped_note (0)
+ , _mouse_state(None)
+ , _pressed_button(0)
+ , _optimization_iterator (_events.end())
+ , _list_editor (0)
+ , _no_sound_notes (false)
+ , _last_display_zoom (0)
+ , _last_event_x (0)
+ , _last_event_y (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
+ , _entered_note (0)
+ , _mouse_changed_selection (false)
+{
+ init (false);
+}
+
+MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<MidiRegion> region)
+ : RegionView (other, boost::shared_ptr<Region> (region))
+ , _current_range_min(0)
+ , _current_range_max(0)
+ , _region_relative_time_converter(other.region_relative_time_converter())
+ , _source_relative_time_converter(other.source_relative_time_converter())
+ , _region_relative_time_converter_double(other.region_relative_time_converter_double())
+ , _active_notes(0)
+ , _note_group (new ArdourCanvas::Container (get_canvas_group()))
+ , _note_diff_command (0)
+ , _ghost_note(0)
+ , _step_edit_cursor (0)
+ , _step_edit_cursor_width (1.0)
+ , _step_edit_cursor_position (0.0)
+ , _channel_selection_scoped_note (0)
+ , _mouse_state(None)
+ , _pressed_button(0)
+ , _optimization_iterator (_events.end())
+ , _list_editor (0)
+ , _no_sound_notes (false)
+ , _last_display_zoom (0)
+ , _last_event_x (0)
+ , _last_event_y (0)
+ , _grabbed_keyboard (false)
+ , _entered (false)
+ , _entered_note (0)
+ , _mouse_changed_selection (false)
+{
+ init (true);
+}
+
+void
+MidiRegionView::init (bool wfd)
+{
+ PublicEditor::DropDownKeys.connect (sigc::mem_fun (*this, &MidiRegionView::drop_down_keys));
+
+ if (wfd) {
+ Glib::Threads::Mutex::Lock lm(midi_region()->midi_source(0)->mutex());
+ midi_region()->midi_source(0)->load_model(lm);
+ }
+
+ _model = midi_region()->midi_source(0)->model();
+ _enable_display = false;
+ fill_color_name = "midi frame base";
+
+ RegionView::init (false);
+
+ //set_height (trackview.current_height());
+
+ region_muted ();
+ region_sync_changed ();
+ region_resized (ARDOUR::bounds_change);
+ //region_locked ();
+
+ set_colors ();
+
+ _enable_display = true;
+ if (_model) {
+ if (wfd) {
+ display_model (_model);
+ }
+ }
+
+ reset_width_dependent_items (_pixel_width);
+
+ group->raise_to_top();
+
+ midi_view()->midi_track()->playback_filter().ChannelModeChanged.connect (_channel_mode_changed_connection, invalidator (*this),
+ boost::bind (&MidiRegionView::midi_channel_mode_changed, this),
+ gui_context ());
+
+ instrument_info().Changed.connect (_instrument_changed_connection, invalidator (*this),
+ boost::bind (&MidiRegionView::instrument_settings_changed, this), gui_context());
+
+ trackview.editor().SnapChanged.connect(snap_changed_connection, invalidator(*this),
+ boost::bind (&MidiRegionView::snap_changed, this),
+ gui_context());
+
+ trackview.editor().MouseModeChanged.connect(_mouse_mode_connection, invalidator (*this),
+ boost::bind (&MidiRegionView::mouse_mode_changed, this),
+ gui_context ());
+
+ Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&MidiRegionView::parameter_changed, this, _1), gui_context());
+ UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &MidiRegionView::parameter_changed));
+ connect_to_diskstream ();
+}
+
+InstrumentInfo&
+MidiRegionView::instrument_info () const
+{
+ RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
+ return route_ui->route()->instrument_info();
+}
+
+const boost::shared_ptr<ARDOUR::MidiRegion>
+MidiRegionView::midi_region() const
+{
+ return boost::dynamic_pointer_cast<ARDOUR::MidiRegion>(_region);
+}
+
+void
+MidiRegionView::connect_to_diskstream ()
+{
+ midi_view()->midi_track()->DataRecorded.connect(
+ *this, invalidator(*this),
+ boost::bind (&MidiRegionView::data_recorded, this, _1),
+ gui_context());
+}
+
+bool
+MidiRegionView::canvas_group_event(GdkEvent* ev)
+{
+ if (in_destructor || _recregion) {
+ return false;
+ }
+
+ if (!trackview.editor().internal_editing()) {
+ // not in internal edit mode, so just act like a normal region
+ return RegionView::canvas_group_event (ev);
+ }
+
+ bool r;
+
+ switch (ev->type) {
+ case GDK_ENTER_NOTIFY:
+ _last_event_x = ev->crossing.x;
+ _last_event_y = ev->crossing.y;
+ enter_notify(&ev->crossing);
+ // set entered_regionview (among other things)
+ return RegionView::canvas_group_event (ev);
+
+ case GDK_LEAVE_NOTIFY:
+ _last_event_x = ev->crossing.x;
+ _last_event_y = ev->crossing.y;
+ leave_notify(&ev->crossing);
+ // reset entered_regionview (among other things)
+ return RegionView::canvas_group_event (ev);
+
+ case GDK_SCROLL:
+ if (scroll (&ev->scroll)) {
+ return true;
+ }
+ break;
+
+ case GDK_KEY_PRESS:
+ return key_press (&ev->key);
+
+ case GDK_KEY_RELEASE:
+ return key_release (&ev->key);
+
+ case GDK_BUTTON_PRESS:
+ return button_press (&ev->button);
+
+ case GDK_BUTTON_RELEASE:
+ r = button_release (&ev->button);
+ return r;
+
+ case GDK_MOTION_NOTIFY:
+ _last_event_x = ev->motion.x;
+ _last_event_y = ev->motion.y;
+ return motion (&ev->motion);
+
+ default:
+ break;
+ }
+
+ return RegionView::canvas_group_event (ev);
+}
+
+bool
+MidiRegionView::enter_notify (GdkEventCrossing* ev)
+{
+ enter_internal (ev->state);
+
+ _entered = true;
+ return false;
+}
+
+bool
+MidiRegionView::leave_notify (GdkEventCrossing*)
+{
+ leave_internal();
+
+ _entered = false;
+ return false;
+}
+
+void
+MidiRegionView::mouse_mode_changed ()
+{
+ // Adjust frame colour (become more transparent for internal tools)
+ set_frame_color();
+
+ if (_entered) {
+ if (!trackview.editor().internal_editing()) {
+ /* Switched out of internal editing mode while entered.
+ Only necessary for leave as a mouse_mode_change over a region
+ automatically triggers an enter event. */
+ leave_internal();
+ }
+ else if (trackview.editor().current_mouse_mode() == MouseContent) {
+ // hide cursor and ghost note after changing to internal edit mode
+ remove_ghost_note ();
+
+ /* XXX This is problematic as the function is executed for every region
+ and only for one region _entered_note can be true. Still it's
+ necessary as to hide the verbose cursor when we're changing from
+ draw mode to internal edit mode. These lines are the reason why
+ in some situations no verbose cursor is shown when we enter internal
+ edit mode over a note. */
+ if (!_entered_note) {
+ hide_verbose_cursor ();
+ }
+ }
+ }
+}
+
+void
+MidiRegionView::enter_internal (uint32_t state)
+{
+ if (trackview.editor().current_mouse_mode() == MouseDraw && _mouse_state != AddDragging) {
+ // Show ghost note under pencil
+ create_ghost_note(_last_event_x, _last_event_y, state);
+ }
+
+ if (!_selection.empty()) {
+ // Grab keyboard for moving selected notes with arrow keys
+ Keyboard::magic_widget_grab_focus();
+ _grabbed_keyboard = true;
+ }
+
+ // Lower frame handles below notes so they don't steal events
+ if (frame_handle_start) {
+ frame_handle_start->lower_to_bottom();
+ }
+ if (frame_handle_end) {
+ frame_handle_end->lower_to_bottom();
+ }
+}
+
+void
+MidiRegionView::leave_internal()
+{
+ hide_verbose_cursor ();
+ remove_ghost_note ();
+ _entered_note = 0;
+
+ if (_grabbed_keyboard) {
+ Keyboard::magic_widget_drop_focus();
+ _grabbed_keyboard = false;
+ }
+
+ // Raise frame handles above notes so they catch events
+ if (frame_handle_start) {
+ frame_handle_start->raise_to_top();
+ }
+ if (frame_handle_end) {
+ frame_handle_end->raise_to_top();
+ }
+}
+
+bool
+MidiRegionView::button_press (GdkEventButton* ev)
+{
+ if (ev->button != 1) {
+ return false;
+ }
+
+ Editor* editor = dynamic_cast<Editor *> (&trackview.editor());
+ MouseMode m = editor->current_mouse_mode();
+
+ if (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
+ _press_cursor_ctx = CursorContext::create(*editor, editor->cursors()->midi_pencil);
+ }
+
+ if (_mouse_state != SelectTouchDragging) {
+
+ _pressed_button = ev->button;
+
+ if (m == MouseDraw || (m == MouseContent && Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()))) {
+
+ if (midi_view()->note_mode() == Percussive) {
+ editor->drags()->set (new HitCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
+ } else {
+ editor->drags()->set (new NoteCreateDrag (dynamic_cast<Editor *> (editor), group, this), (GdkEvent *) ev);
+ }
+
+ _mouse_state = AddDragging;
+ remove_ghost_note ();
+ hide_verbose_cursor ();
+ } else {
+ _mouse_state = Pressed;
+ }
+
+ return true;
+ }
+
+ _pressed_button = ev->button;
+ _mouse_changed_selection = false;
+
+ return true;
+}
+
+bool
+MidiRegionView::button_release (GdkEventButton* ev)
+{
+ double event_x, event_y;
+
+ if (ev->button != 1) {
+ return false;
+ }
+
+ event_x = ev->x;
+ event_y = ev->y;
+
+ group->canvas_to_item (event_x, event_y);
+ group->ungrab ();
+
+ PublicEditor& editor = trackview.editor ();
+
+ _press_cursor_ctx.reset();
+
+ switch (_mouse_state) {
+ case Pressed: // Clicked
+
+ switch (editor.current_mouse_mode()) {
+ case MouseRange:
+ /* no motion occurred - simple click */
+ clear_editor_note_selection ();
+ _mouse_changed_selection = true;
+ break;
+
+ case MouseContent:
+ case MouseTimeFX:
+ {
+ _mouse_changed_selection = true;
+ clear_editor_note_selection ();
+
+ break;
+ }
+ case MouseDraw:
+ break;
+
+ default:
+ break;
+ }
+
+ _mouse_state = None;
+ break;
+
+ case AddDragging:
+ /* Don't a ghost note when we added a note - wait until motion to avoid visual confusion.
+ we don't want one when we were drag-selecting either. */
+ case SelectRectDragging:
+ editor.drags()->end_grab ((GdkEvent *) ev);
+ _mouse_state = None;
+ break;
+
+
+ default:
+ break;
+ }
+
+ if (_mouse_changed_selection) {
+ trackview.editor().begin_reversible_selection_op (X_("Mouse Selection Change"));
+ trackview.editor().commit_reversible_selection_op ();
+ }
+
+ return false;
+}
+
+bool
+MidiRegionView::motion (GdkEventMotion* ev)
+{
+ PublicEditor& editor = trackview.editor ();
+
+ if (!_entered_note) {
+
+ if (_mouse_state == AddDragging) {
+ if (_ghost_note) {
+ remove_ghost_note ();
+ }
+
+ } else if (!_ghost_note && editor.current_mouse_mode() == MouseContent &&
+ Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier()) &&
+ _mouse_state != AddDragging) {
+
+ create_ghost_note (ev->x, ev->y, ev->state);
+
+ } else if (_ghost_note && editor.current_mouse_mode() == MouseContent &&
+ Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
+
+ update_ghost_note (ev->x, ev->y, ev->state);
+
+ } else if (_ghost_note && editor.current_mouse_mode() == MouseContent) {
+
+ remove_ghost_note ();
+ hide_verbose_cursor ();
+
+ } else if (editor.current_mouse_mode() == MouseDraw) {
+
+ if (_ghost_note) {
+ update_ghost_note (ev->x, ev->y, ev->state);
+ }
+ else {
+ create_ghost_note (ev->x, ev->y, ev->state);
+ }
+ }
+ }
+
+ /* any motion immediately hides velocity text that may have been visible */
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ (*i)->hide_velocity ();
+ }
+
+ switch (_mouse_state) {
+ case Pressed:
+
+ if (_pressed_button == 1) {
+
+ MouseMode m = editor.current_mouse_mode();
+
+ if (m == MouseContent && !Keyboard::modifier_state_contains (ev->state, Keyboard::insert_note_modifier())) {
+ editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
+ if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
+ clear_editor_note_selection ();
+ _mouse_changed_selection = true;
+ }
+ _mouse_state = SelectRectDragging;
+ return true;
+ } else if (m == MouseRange) {
+ editor.drags()->set (new MidiVerticalSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
+ _mouse_state = SelectVerticalDragging;
+ return true;
+ }
+ }
+
+ return false;
+
+ case SelectRectDragging:
+ case SelectVerticalDragging:
+ case AddDragging:
+ editor.drags()->motion_handler ((GdkEvent *) ev, false);
+ break;
+
+ case SelectTouchDragging:
+ return false;
+
+ default:
+ break;
+
+ }
+
+ /* we may be dragging some non-note object (eg. patch-change, sysex)
+ */
+
+ return editor.drags()->motion_handler ((GdkEvent *) ev, false);
+}
+
+
+bool
+MidiRegionView::scroll (GdkEventScroll* ev)
+{
+ if (_selection.empty()) {
+ return false;
+ }
+
+ if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) ||
+ Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
+ /* XXX: bit of a hack; allow PrimaryModifier and TertiaryModifier scroll
+ * through so that it still works for navigation.
+ */
+ return false;
+ }
+
+ hide_verbose_cursor ();
+
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ Keyboard::ModifierMask mask_together(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier);
+ bool together = Keyboard::modifier_state_contains (ev->state, mask_together);
+
+ if (ev->direction == GDK_SCROLL_UP) {
+ change_velocities (true, fine, false, together);
+ } else if (ev->direction == GDK_SCROLL_DOWN) {
+ change_velocities (false, fine, false, together);
+ } else {
+ /* left, right: we don't use them */
+ return false;
+ }
+
+ return true;
+}
+
+bool
+MidiRegionView::key_press (GdkEventKey* ev)
+{
+ /* since GTK bindings are generally activated on press, and since
+ detectable auto-repeat is the name of the game and only sends
+ repeated presses, carry out key actions at key press, not release.
+ */
+ bool unmodified = Keyboard::no_modifier_keys_pressed (ev);
+
+ if (unmodified && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
+
+ if (_mouse_state != AddDragging) {
+ _mouse_state = SelectTouchDragging;
+ }
+
+ return true;
+
+ } else if (ev->keyval == GDK_Escape && unmodified) {
+ clear_editor_note_selection ();
+ _mouse_state = None;
+
+ } else if (ev->keyval == GDK_comma || ev->keyval == GDK_period) {
+
+ bool start = (ev->keyval == GDK_comma);
+ bool end = (ev->keyval == GDK_period);
+ bool shorter = Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier);
+ bool fine = Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+
+ change_note_lengths (fine, shorter, Evoral::Beats(), start, end);
+
+ return true;
+
+ } else if ((ev->keyval == GDK_BackSpace || ev->keyval == GDK_Delete) && unmodified) {
+
+ if (_selection.empty()) {
+ return false;
+ }
+
+ delete_selection();
+ return true;
+
+ } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
+
+ trackview.editor().begin_reversible_selection_op (X_("Select Adjacent Note"));
+
+ if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
+ goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
+ } else {
+ goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
+ }
+
+ trackview.editor().commit_reversible_selection_op();
+
+ return true;
+
+ } else if (ev->keyval == GDK_Up) {
+
+ bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
+
+ if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
+ change_velocities (true, fine, allow_smush, together);
+ } else {
+ transpose (true, fine, allow_smush);
+ }
+ return true;
+
+ } else if (ev->keyval == GDK_Down) {
+
+ bool allow_smush = Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier);
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ bool together = Keyboard::modifier_state_contains (ev->state, Keyboard::Level4Modifier);
+
+ if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
+ change_velocities (false, fine, allow_smush, together);
+ } else {
+ transpose (false, fine, allow_smush);
+ }
+ return true;
+
+ } else if (ev->keyval == GDK_Left) {
+
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ nudge_notes (false, fine);
+ return true;
+
+ } else if (ev->keyval == GDK_Right) {
+
+ bool fine = !Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier);
+ nudge_notes (true, fine);
+ return true;
+
+ } else if (ev->keyval == GDK_c && unmodified) {
+ channel_edit ();
+ return true;
+
+ } else if (ev->keyval == GDK_v && unmodified) {
+ velocity_edit ();
+ return true;
+ }
+
+ return false;
+}
+
+bool
+MidiRegionView::key_release (GdkEventKey* ev)
+{
+ if ((_mouse_state == SelectTouchDragging) && (ev->keyval == GDK_Alt_L || ev->keyval == GDK_Alt_R)) {
+ _mouse_state = None;
+ return true;
+ }
+ return false;
+}
+
+void
+MidiRegionView::channel_edit ()
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ /* pick a note somewhat at random (since Selection is a set<>) to
+ * provide the "current" channel for the dialog.
+ */
+
+ uint8_t current_channel = (*_selection.begin())->note()->channel ();
+ MidiChannelDialog channel_dialog (current_channel);
+ int ret = channel_dialog.run ();
+
+ switch (ret) {
+ case Gtk::RESPONSE_OK:
+ break;
+ default:
+ return;
+ }
+
+ uint8_t new_channel = channel_dialog.active_channel ();
+
+ start_note_diff_command (_("channel edit"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
+ Selection::iterator next = i;
+ ++next;
+ change_note_channel (*i, new_channel);
+ i = next;
+ }
+
+ apply_diff ();
+}
+
+void
+MidiRegionView::velocity_edit ()
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ /* pick a note somewhat at random (since Selection is a set<>) to
+ * provide the "current" velocity for the dialog.
+ */
+
+ uint8_t current_velocity = (*_selection.begin())->note()->velocity ();
+ MidiVelocityDialog velocity_dialog (current_velocity);
+ int ret = velocity_dialog.run ();
+
+ switch (ret) {
+ case Gtk::RESPONSE_OK:
+ break;
+ default:
+ return;
+ }
+
+ uint8_t new_velocity = velocity_dialog.velocity ();
+
+ start_note_diff_command (_("velocity edit"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
+ Selection::iterator next = i;
+ ++next;
+ change_note_velocity (*i, new_velocity, false);
+ i = next;
+ }
+
+ apply_diff ();
+}
+
+void
+MidiRegionView::show_list_editor ()
+{
+ if (!_list_editor) {
+ _list_editor = new MidiListEditor (trackview.session(), midi_region(), midi_view()->midi_track());
+ }
+ _list_editor->present ();
+}
+
+/** Add a note to the model, and the view, at a canvas (click) coordinate.
+ * \param t time in frames relative to the position of the region
+ * \param y vertical position in pixels
+ * \param length duration of the note in beats
+ * \param snap_t true to snap t to the grid, otherwise false.
+ */
+void
+MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, uint32_t state, bool shift_snap)
+{
+ if (length < 2 * DBL_EPSILON) {
+ return;
+ }
+
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ MidiStreamView* const view = mtv->midi_view();
+ boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion> (_region);
+
+ if (!mr) {
+ return;
+ }
+
+ // Start of note in frames relative to region start
+ const int32_t divisions = trackview.editor().get_grid_music_divisions (state);
+ Evoral::Beats beat_time = snap_frame_to_grid_underneath (t, divisions, shift_snap);
+
+ const double note = view->y_to_note(y);
+ const uint8_t chan = mtv->get_channel_for_add();
+ const uint8_t velocity = get_velocity_for_add(beat_time);
+
+ const boost::shared_ptr<NoteType> new_note(
+ new NoteType (chan, beat_time, length, (uint8_t)note, velocity));
+
+ if (_model->contains (new_note)) {
+ return;
+ }
+
+ view->update_note_range(new_note->note());
+
+ start_note_diff_command(_("add note"));
+
+ note_diff_add_note (new_note, true, false);
+
+ apply_diff();
+
+ play_midi_note (new_note);
+}
+
+void
+MidiRegionView::clear_events ()
+{
+ // clear selection without signaling
+ clear_selection_internal ();
+
+ MidiGhostRegion* gr;
+ for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
+ if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
+ gr->clear_events();
+ }
+ }
+
+
+ _note_group->clear_children (true);
+ _events.clear();
+ _patch_changes.clear();
+ _sys_exes.clear();
+ _optimization_iterator = _events.end();
+}
+
+void
+MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
+{
+ _model = model;
+
+ content_connection.disconnect ();
+ _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
+ /* Don't signal as nobody else needs to know until selection has been altered. */
+ clear_events();
+
+ if (_enable_display) {
+ redisplay_model();
+ }
+}
+
+void
+MidiRegionView::start_note_diff_command (string name)
+{
+ if (!_note_diff_command) {
+ trackview.editor().begin_reversible_command (name);
+ _note_diff_command = _model->new_note_diff_command (name);
+ }
+}
+
+void
+MidiRegionView::note_diff_add_note (const boost::shared_ptr<NoteType> note, bool selected, bool show_velocity)
+{
+ if (_note_diff_command) {
+ _note_diff_command->add (note);
+ }
+ if (selected) {
+ _marked_for_selection.insert(note);
+ }
+ if (show_velocity) {
+ _marked_for_velocity.insert(note);
+ }
+}
+
+void
+MidiRegionView::note_diff_remove_note (NoteBase* ev)
+{
+ if (_note_diff_command && ev->note()) {
+ _note_diff_command->remove(ev->note());
+ }
+}
+
+void
+MidiRegionView::note_diff_add_change (NoteBase* ev,
+ MidiModel::NoteDiffCommand::Property property,
+ uint8_t val)
+{
+ if (_note_diff_command) {
+ _note_diff_command->change (ev->note(), property, val);
+ }
+}
+
+void
+MidiRegionView::note_diff_add_change (NoteBase* ev,
+ MidiModel::NoteDiffCommand::Property property,
+ Evoral::Beats val)
+{
+ if (_note_diff_command) {
+ _note_diff_command->change (ev->note(), property, val);
+ }
+}
+
+void
+MidiRegionView::apply_diff (bool as_subcommand, bool was_copy)
+{
+ bool add_or_remove;
+ bool commit = false;
+
+ if (!_note_diff_command) {
+ return;
+ }
+
+ if (!was_copy && (add_or_remove = _note_diff_command->adds_or_removes())) {
+ // Mark all selected notes for selection when model reloads
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ _marked_for_selection.insert((*i)->note());
+ }
+ }
+
+ midi_view()->midi_track()->midi_playlist()->region_edited (_region, _note_diff_command);
+
+ if (as_subcommand) {
+ _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
+ } else {
+ _model->apply_command (*trackview.session(), _note_diff_command);
+ commit = true;
+ }
+
+ _note_diff_command = 0;
+
+ if (add_or_remove) {
+ _marked_for_selection.clear();
+ }
+
+ _marked_for_velocity.clear();
+ if (commit) {
+ trackview.editor().commit_reversible_command ();
+ }
+}
+
+void
+MidiRegionView::abort_command()
+{
+ delete _note_diff_command;
+ _note_diff_command = 0;
+ trackview.editor().abort_reversible_command();
+ clear_editor_note_selection();
+}
+
+NoteBase*
+MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
+{
+
+ if (_optimization_iterator != _events.end()) {
+ ++_optimization_iterator;
+ }
+
+ if (_optimization_iterator != _events.end() && _optimization_iterator->first == note) {
+ return _optimization_iterator->second;
+ }
+
+ _optimization_iterator = _events.find (note);
+ if (_optimization_iterator != _events.end()) {
+ return _optimization_iterator->second;
+ }
+
+ return 0;
+}
+
+/** This version finds any canvas note matching the supplied note. */
+NoteBase*
+MidiRegionView::find_canvas_note (Evoral::event_id_t id)
+{
+ Events::iterator it;
+
+ for (it = _events.begin(); it != _events.end(); ++it) {
+ if (it->first->id() == id) {
+ return it->second;
+ }
+ }
+
+ return 0;
+}
+
+boost::shared_ptr<PatchChange>
+MidiRegionView::find_canvas_patch_change (MidiModel::PatchChangePtr p)
+{
+ PatchChanges::const_iterator f = _patch_changes.find (p);
+
+ if (f != _patch_changes.end()) {
+ return f->second;
+ }
+
+ return boost::shared_ptr<PatchChange>();
+}
+
+boost::shared_ptr<SysEx>
+MidiRegionView::find_canvas_sys_ex (MidiModel::SysExPtr s)
+{
+ SysExes::const_iterator f = _sys_exes.find (s);
+
+ if (f != _sys_exes.end()) {
+ return f->second;
+ }
+
+ return boost::shared_ptr<SysEx>();
+}
+
+void
+MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
+{
+ MidiModel::Notes notes;
+ _model->get_notes (notes, op, val, chan_mask);
+
+ for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
+ NoteBase* cne = find_canvas_note (*n);
+ if (cne) {
+ e.insert (make_pair (*n, cne));
+ }
+ }
+}
+
+void
+MidiRegionView::redisplay_model()
+{
+ if (_active_notes) {
+ // Currently recording
+ const framecnt_t zoom = trackview.editor().get_current_zoom();
+ if (zoom != _last_display_zoom) {
+ /* Update resolved canvas notes to reflect changes in zoom without
+ touching model. Leave active notes (with length 0) alone since
+ they are being extended. */
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ if (i->second->note()->length() > 0) {
+ update_note(i->second);
+ }
+ }
+ _last_display_zoom = zoom;
+ }
+ return;
+ }
+
+ if (!_model) {
+ return;
+ }
+
+ for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) {
+ _optimization_iterator->second->invalidate();
+ }
+
+ bool empty_when_starting = _events.empty();
+ _optimization_iterator = _events.begin();
+ MidiModel::Notes missing_notes;
+ Note* sus = NULL;
+ Hit* hit = NULL;
+
+ MidiModel::ReadLock lock(_model->read_lock());
+ MidiModel::Notes& notes (_model->notes());
+
+ NoteBase* cne;
+ for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
+
+ boost::shared_ptr<NoteType> note (*n);
+ bool visible;
+
+ if (note_in_region_range (note, visible)) {
+ if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) {
+ cne->validate ();
+ if (visible) {
+ cne->show ();
+ } else {
+ cne->hide ();
+ }
+ } else {
+ missing_notes.insert (note);
+ }
+ }
+ }
+
+ if (!empty_when_starting) {
+ MidiModel::Notes::iterator f;
+ for (Events::iterator i = _events.begin(); i != _events.end(); ) {
+
+ NoteBase* cne = i->second;
+
+ /* remove note items that are no longer valid */
+ if (!cne->valid()) {
+
+ for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
+ MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
+ if (gr) {
+ gr->remove_note (cne);
+ }
+ }
+
+ delete cne;
+ i = _events.erase (i);
+
+ } else {
+ bool visible = cne->item()->visible();
+
+ if ((sus = dynamic_cast<Note*>(cne))) {
+
+ if (visible) {
+ update_sustained (sus);
+ }
+
+ } else if ((hit = dynamic_cast<Hit*>(cne))) {
+
+ if (visible) {
+ update_hit (hit);
+ }
+
+ }
+ ++i;
+ }
+ }
+ }
+
+ for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) {
+ boost::shared_ptr<NoteType> note (*n);
+ NoteBase* cne;
+ bool visible;
+
+ if (note_in_region_range (note, visible)) {
+ if (visible) {
+ cne = add_note (note, true);
+ } else {
+ cne = add_note (note, false);
+ }
+ } else {
+ cne = add_note (note, false);
+ }
+
+ for (set<Evoral::event_id_t>::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
+ if ((*it) == note->id()) {
+ add_to_selection (cne);
+ }
+ }
+ }
+
+ for (vector<GhostRegion*>::iterator j = ghosts.begin(); j != ghosts.end(); ++j) {
+ MidiGhostRegion* gr = dynamic_cast<MidiGhostRegion*> (*j);
+ if (gr && !gr->trackview.hidden()) {
+ gr->redisplay_model ();
+ }
+ }
+
+ display_sysexes();
+ display_patch_changes ();
+
+ _marked_for_selection.clear ();
+ _marked_for_velocity.clear ();
+ _pending_note_selection.clear ();
+
+}
+
+void
+MidiRegionView::display_patch_changes ()
+{
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
+
+ for (uint8_t i = 0; i < 16; ++i) {
+ display_patch_changes_on_channel (i, chn_mask & (1 << i));
+ }
+}
+
+/** @param active_channel true to display patch changes fully, false to display
+ * them `greyed-out' (as on an inactive channel)
+ */
+void
+MidiRegionView::display_patch_changes_on_channel (uint8_t channel, bool active_channel)
+{
+ for (MidiModel::PatchChanges::const_iterator i = _model->patch_changes().begin(); i != _model->patch_changes().end(); ++i) {
+ boost::shared_ptr<PatchChange> p;
+
+ if ((*i)->channel() != channel) {
+ continue;
+ }
+
+ if ((p = find_canvas_patch_change (*i)) != 0) {
+
+ const framecnt_t region_frames = source_beats_to_region_frames ((*i)->time());
+
+ if (region_frames < 0 || region_frames >= _region->length()) {
+ p->hide();
+ } else {
+ const double x = trackview.editor().sample_to_pixel (region_frames);
+ const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
+ p->canvas_item()->set_position (ArdourCanvas::Duple (x, 1.0));
+ p->set_text (patch_name);
+
+ p->show();
+ }
+
+ } else {
+ const string patch_name = instrument_info().get_patch_name ((*i)->bank(), (*i)->program(), channel);
+ add_canvas_patch_change (*i, patch_name, active_channel);
+ }
+ }
+}
+
+void
+MidiRegionView::display_sysexes()
+{
+ bool have_periodic_system_messages = false;
+ bool display_periodic_messages = true;
+
+ if (!UIConfiguration::instance().get_never_display_periodic_midi()) {
+
+ for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
+ if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
+ have_periodic_system_messages = true;
+ break;
+ }
+ }
+
+ if (have_periodic_system_messages) {
+ double zoom = trackview.editor().get_current_zoom (); // frames per pixel
+
+ /* get an approximate value for the number of samples per video frame */
+
+ double video_frame = trackview.session()->frame_rate() * (1.0/30);
+
+ /* if we are zoomed out beyond than the cutoff (i.e. more
+ * frames per pixel than frames per 4 video frames), don't
+ * show periodic sysex messages.
+ */
+
+ if (zoom > (video_frame*4)) {
+ display_periodic_messages = false;
+ }
+ }
+ } else {
+ display_periodic_messages = false;
+ }
+
+ for (MidiModel::SysExes::const_iterator i = _model->sysexes().begin(); i != _model->sysexes().end(); ++i) {
+ MidiModel::SysExPtr sysex_ptr = *i;
+ Evoral::Beats time = sysex_ptr->time();
+
+ if ((*i)->is_spp() || (*i)->is_mtc_quarter() || (*i)->is_mtc_full()) {
+ if (!display_periodic_messages) {
+ continue;
+ }
+ }
+
+ ostringstream str;
+ str << hex;
+ for (uint32_t b = 0; b < (*i)->size(); ++b) {
+ str << int((*i)->buffer()[b]);
+ if (b != (*i)->size() -1) {
+ str << " ";
+ }
+ }
+ string text = str.str();
+
+ const double x = trackview.editor().sample_to_pixel(source_beats_to_region_frames(time));
+
+ double height = midi_stream_view()->contents_height();
+
+ // CAIROCANVAS: no longer passing *i (the sysex event) to the
+ // SysEx canvas object!!!
+ boost::shared_ptr<SysEx> sysex = find_canvas_sys_ex (sysex_ptr);
+
+ if (!sysex) {
+ sysex = boost::shared_ptr<SysEx>(
+ new SysEx (*this, _note_group, text, height, x, 1.0, sysex_ptr));
+ _sys_exes.insert (make_pair (sysex_ptr, sysex));
+ } else {
+ sysex->set_height (height);
+ sysex->item().set_position (ArdourCanvas::Duple (x, 1.0));
+ }
+
+ // Show unless message is beyond the region bounds
+ if (time - _region->start() >= _region->length() || time < _region->start()) {
+ sysex->hide();
+ } else {
+ sysex->show();
+ }
+ }
+}
+
+MidiRegionView::~MidiRegionView ()
+{
+ in_destructor = true;
+
+ hide_verbose_cursor ();
+
+ delete _list_editor;
+
+ RegionViewGoingAway (this); /* EMIT_SIGNAL */
+
+ if (_active_notes) {
+ end_write();
+ }
+ _entered_note = 0;
+ clear_events ();
+
+ delete _note_group;
+ delete _note_diff_command;
+ delete _step_edit_cursor;
+}
+
+void
+MidiRegionView::region_resized (const PropertyChange& what_changed)
+{
+ RegionView::region_resized(what_changed); // calls RegionView::set_duration()
+
+ if (what_changed.contains (ARDOUR::Properties::position)) {
+ _region_relative_time_converter.set_origin_b(_region->position());
+ _region_relative_time_converter_double.set_origin_b(_region->position());
+ /* reset_width dependent_items() redisplays model */
+
+ }
+
+ if (what_changed.contains (ARDOUR::Properties::start) ||
+ what_changed.contains (ARDOUR::Properties::position)) {
+ _source_relative_time_converter.set_origin_b (_region->position() - _region->start());
+ }
+ /* catch end and start trim so we can update the view*/
+ if (!what_changed.contains (ARDOUR::Properties::start) &&
+ what_changed.contains (ARDOUR::Properties::length)) {
+ enable_display (true);
+ } else if (what_changed.contains (ARDOUR::Properties::start) &&
+ what_changed.contains (ARDOUR::Properties::length)) {
+ enable_display (true);
+ }
+}
+
+void
+MidiRegionView::reset_width_dependent_items (double pixel_width)
+{
+ RegionView::reset_width_dependent_items(pixel_width);
+
+ if (_enable_display) {
+ redisplay_model();
+ }
+
+ bool hide_all = false;
+ PatchChanges::iterator x = _patch_changes.begin();
+ if (x != _patch_changes.end()) {
+ hide_all = x->second->width() >= _pixel_width;
+ }
+
+ if (hide_all) {
+ for (; x != _patch_changes.end(); ++x) {
+ x->second->hide();
+ }
+ }
+
+ move_step_edit_cursor (_step_edit_cursor_position);
+ set_step_edit_cursor_width (_step_edit_cursor_width);
+}
+
+void
+MidiRegionView::set_height (double height)
+{
+ double old_height = _height;
+ RegionView::set_height(height);
+
+ apply_note_range (midi_stream_view()->lowest_note(),
+ midi_stream_view()->highest_note(),
+ height != old_height);
+
+ if (name_text) {
+ name_text->raise_to_top();
+ }
+
+ for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
+ (*x).second->set_height (midi_stream_view()->contents_height());
+ }
+
+ if (_step_edit_cursor) {
+ _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
+ }
+}
+
+
+/** Apply the current note range from the stream view
+ * by repositioning/hiding notes as necessary
+ */
+void
+MidiRegionView::apply_note_range (uint8_t min, uint8_t max, bool force)
+{
+ if (!_enable_display) {
+ return;
+ }
+
+ if (!force && _current_range_min == min && _current_range_max == max) {
+ return;
+ }
+
+ _current_range_min = min;
+ _current_range_max = max;
+
+ redisplay_model ();
+}
+
+GhostRegion*
+MidiRegionView::add_ghost (TimeAxisView& tv)
+{
+ double unit_position = _region->position () / samples_per_pixel;
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&tv);
+ MidiGhostRegion* ghost;
+
+ if (mtv && mtv->midi_view()) {
+ /* if ghost is inserted into midi track, use a dedicated midi ghost canvas group
+ to allow having midi notes on top of note lines and waveforms.
+ */
+ ghost = new MidiGhostRegion (*this, *mtv->midi_view(), trackview, unit_position);
+ } else {
+ ghost = new MidiGhostRegion (*this, tv, trackview, unit_position);
+ }
+
+ ghost->set_colors ();
+ ghost->set_height ();
+ ghost->set_duration (_region->length() / samples_per_pixel);
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ ghost->add_note(i->second);
+ }
+
+ ghosts.push_back (ghost);
+ enable_display (true);
+ return ghost;
+}
+
+
+/** Begin tracking note state for successive calls to add_event
+ */
+void
+MidiRegionView::begin_write()
+{
+ if (_active_notes) {
+ delete[] _active_notes;
+ }
+ _active_notes = new Note*[128];
+ for (unsigned i = 0; i < 128; ++i) {
+ _active_notes[i] = 0;
+ }
+}
+
+
+/** Destroy note state for add_event
+ */
+void
+MidiRegionView::end_write()
+{
+ delete[] _active_notes;
+ _active_notes = 0;
+ _marked_for_selection.clear();
+ _marked_for_velocity.clear();
+}
+
+
+/** Resolve an active MIDI note (while recording).
+ */
+void
+MidiRegionView::resolve_note(uint8_t note, Evoral::Beats end_time)
+{
+ if (midi_view()->note_mode() != Sustained) {
+ return;
+ }
+
+ if (_active_notes && _active_notes[note]) {
+ /* Set note length so update_note() works. Note this is a local note
+ for recording, not from a model, so we can safely mess with it. */
+ _active_notes[note]->note()->set_length(
+ end_time - _active_notes[note]->note()->time());
+
+ /* End time is relative to the region being recorded. */
+ const framepos_t end_time_frames = region_beats_to_region_frames(end_time);
+
+ _active_notes[note]->set_x1 (trackview.editor().sample_to_pixel(end_time_frames));
+ _active_notes[note]->set_outline_all ();
+ _active_notes[note] = 0;
+ }
+}
+
+
+/** Extend active notes to rightmost edge of region (if length is changed)
+ */
+void
+MidiRegionView::extend_active_notes()
+{
+ if (!_active_notes) {
+ return;
+ }
+
+ for (unsigned i = 0; i < 128; ++i) {
+ if (_active_notes[i]) {
+ _active_notes[i]->set_x1(
+ trackview.editor().sample_to_pixel(_region->length()));
+ }
+ }
+}
+
+void
+MidiRegionView::play_midi_note(boost::shared_ptr<NoteType> note)
+{
+ if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
+ return;
+ }
+
+ RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
+
+ if (!route_ui || !route_ui->midi_track()) {
+ return;
+ }
+
+ NotePlayer* np = new NotePlayer (route_ui->midi_track ());
+ np->add (note);
+ np->play ();
+
+ /* NotePlayer deletes itself */
+}
+
+void
+MidiRegionView::start_playing_midi_note(boost::shared_ptr<NoteType> note)
+{
+ const std::vector< boost::shared_ptr<NoteType> > notes(1, note);
+ start_playing_midi_chord(notes);
+}
+
+void
+MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > notes)
+{
+ if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) {
+ return;
+ }
+
+ RouteUI* route_ui = dynamic_cast<RouteUI*> (&trackview);
+
+ if (!route_ui || !route_ui->midi_track()) {
+ return;
+ }
+
+ NotePlayer* player = new NotePlayer (route_ui->midi_track());
+
+ for (vector<boost::shared_ptr<NoteType> >::iterator n = notes.begin(); n != notes.end(); ++n) {
+ player->add (*n);
+ }
+
+ player->play ();
+}
+
+
+bool
+MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
+{
+ const boost::shared_ptr<ARDOUR::MidiRegion> midi_reg = midi_region();
+
+ /* must compare double explicitly as Beats::operator< rounds to ppqn */
+ const bool outside = (note->time().to_double() < midi_reg->start_beats() ||
+ note->time().to_double() >= midi_reg->start_beats() + midi_reg->length_beats());
+
+ visible = (note->note() >= _current_range_min) &&
+ (note->note() <= _current_range_max);
+
+ return !outside;
+}
+
+void
+MidiRegionView::update_note (NoteBase* note, bool update_ghost_regions)
+{
+ Note* sus = NULL;
+ Hit* hit = NULL;
+ if ((sus = dynamic_cast<Note*>(note))) {
+ update_sustained(sus, update_ghost_regions);
+ } else if ((hit = dynamic_cast<Hit*>(note))) {
+ update_hit(hit, update_ghost_regions);
+ }
+}
+
+/** Update a canvas note's size from its model note.
+ * @param ev Canvas note to update.
+ * @param update_ghost_regions true to update the note in any ghost regions that we have, otherwise false.
+ */
+void
+MidiRegionView::update_sustained (Note* ev, bool update_ghost_regions)
+{
+ TempoMap& map (trackview.session()->tempo_map());
+ const boost::shared_ptr<ARDOUR::MidiRegion> mr = midi_region();
+ boost::shared_ptr<NoteType> note = ev->note();
+
+ const double session_source_start = _region->quarter_note() - mr->start_beats();
+ const framepos_t note_start_frames = map.frame_at_quarter_note (note->time().to_double() + session_source_start) - _region->position();
+
+ const double x0 = trackview.editor().sample_to_pixel (note_start_frames);
+ double x1;
+ const double y0 = 1 + floor(note_to_y(note->note()));
+ double y1;
+
+ /* trim note display to not overlap the end of its region */
+ if (note->length().to_double() > 0.0) {
+ double note_end_time = note->end_time().to_double();
+
+ if (note_end_time > mr->start_beats() + mr->length_beats()) {
+ note_end_time = mr->start_beats() + mr->length_beats();
+ }
+
+ const framepos_t note_end_frames = map.frame_at_quarter_note (session_source_start + note_end_time) - _region->position();
+
+ x1 = std::max(1., trackview.editor().sample_to_pixel (note_end_frames)) - 1;
+ } else {
+ x1 = std::max(1., trackview.editor().sample_to_pixel (_region->length())) - 1;
+ }
+
+ y1 = y0 + std::max(1., floor(note_height()) - 1);
+
+ ev->set (ArdourCanvas::Rect (x0, y0, x1, y1));
+
+ if (!note->length()) {
+ if (_active_notes && note->note() < 128) {
+ Note* const old_rect = _active_notes[note->note()];
+ if (old_rect) {
+ /* There is an active note on this key, so we have a stuck
+ note. Finish the old rectangle here. */
+ old_rect->set_x1 (x1);
+ old_rect->set_outline_all ();
+ }
+ _active_notes[note->note()] = ev;
+ }
+ /* outline all but right edge */
+ ev->set_outline_what (ArdourCanvas::Rectangle::What (
+ ArdourCanvas::Rectangle::TOP|
+ ArdourCanvas::Rectangle::LEFT|
+ ArdourCanvas::Rectangle::BOTTOM));
+ } else {
+ /* outline all edges */
+ ev->set_outline_all ();
+ }
+
+ // Update color in case velocity has changed
+ const uint32_t base_col = ev->base_color();
+ ev->set_fill_color(base_col);
+ ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
+
+}
+
+void
+MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
+{
+ boost::shared_ptr<NoteType> note = ev->note();
+
+ const double note_time_qn = note->time().to_double() + (_region->quarter_note() - midi_region()->start_beats());
+ const framepos_t note_start_frames = trackview.session()->tempo_map().frame_at_quarter_note (note_time_qn) - _region->position();
+
+ const double x = trackview.editor().sample_to_pixel(note_start_frames);
+ const double diamond_size = std::max(1., floor(note_height()) - 2.);
+ const double y = 1.5 + floor(note_to_y(note->note())) + diamond_size * .5;
+
+ // see DnD note in MidiRegionView::apply_note_range() above
+ if (y <= 0 || y >= _height) {
+ ev->hide();
+ } else {
+ ev->show();
+ }
+
+ ev->set_position (ArdourCanvas::Duple (x, y));
+ ev->set_height (diamond_size);
+
+ // Update color in case velocity has changed
+ const uint32_t base_col = ev->base_color();
+ ev->set_fill_color(base_col);
+ ev->set_outline_color(ev->calculate_outline(base_col, ev->selected()));
+
+}
+
+/** Add a MIDI note to the view (with length).
+ *
+ * If in sustained mode, notes with length 0 will be considered active
+ * notes, and resolve_note should be called when the corresponding note off
+ * event arrives, to properly display the note.
+ */
+NoteBase*
+MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
+{
+ NoteBase* event = 0;
+
+ if (midi_view()->note_mode() == Sustained) {
+
+ Note* ev_rect = new Note (*this, _note_group, note); // XXX may leak
+
+ update_sustained (ev_rect);
+
+ event = ev_rect;
+
+ } else if (midi_view()->note_mode() == Percussive) {
+
+ const double diamond_size = std::max(1., floor(note_height()) - 2.);
+
+ Hit* ev_diamond = new Hit (*this, _note_group, diamond_size, note); // XXX may leak
+
+ update_hit (ev_diamond);
+
+ event = ev_diamond;
+
+ } else {
+ event = 0;
+ }
+
+ if (event) {
+ MidiGhostRegion* gr;
+
+ for (std::vector<GhostRegion*>::iterator g = ghosts.begin(); g != ghosts.end(); ++g) {
+ if ((gr = dynamic_cast<MidiGhostRegion*>(*g)) != 0) {
+ gr->add_note(event);
+ }
+ }
+
+ if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
+ note_selected(event, true);
+ }
+
+ if (_marked_for_velocity.find(note) != _marked_for_velocity.end()) {
+ event->show_velocity();
+ }
+
+ event->on_channel_selection_change (get_selected_channels());
+ _events.insert (make_pair (event->note(), event));
+
+ if (visible) {
+ event->show();
+ } else {
+ event->hide ();
+ }
+ }
+
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ MidiStreamView* const view = mtv->midi_view();
+
+ view->update_note_range (note->note());
+ return event;
+}
+
+void
+MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity,
+ Evoral::Beats pos, Evoral::Beats len)
+{
+ boost::shared_ptr<NoteType> new_note (new NoteType (channel, pos, len, number, velocity));
+
+ /* potentially extend region to hold new note */
+
+ framepos_t end_frame = source_beats_to_absolute_frames (new_note->end_time());
+ framepos_t region_end = _region->last_frame();
+
+ if (end_frame > region_end) {
+ /* XX sets length in beats from audio space. make musical */
+ _region->set_length (end_frame - _region->position(), 0);
+ }
+
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ MidiStreamView* const view = mtv->midi_view();
+
+ view->update_note_range(new_note->note());
+
+ _marked_for_selection.clear ();
+
+ start_note_diff_command (_("step add"));
+
+ clear_editor_note_selection ();
+ note_diff_add_note (new_note, true, false);
+
+ apply_diff();
+
+ // last_step_edit_note = new_note;
+}
+
+void
+MidiRegionView::step_sustain (Evoral::Beats beats)
+{
+ change_note_lengths (false, false, beats, false, true);
+}
+
+/** Add a new patch change flag to the canvas.
+ * @param patch the patch change to add
+ * @param the text to display in the flag
+ * @param active_channel true to display the flag as on an active channel, false to grey it out for an inactive channel.
+ */
+void
+MidiRegionView::add_canvas_patch_change (MidiModel::PatchChangePtr patch, const string& displaytext, bool /*active_channel*/)
+{
+ framecnt_t region_frames = source_beats_to_region_frames (patch->time());
+ const double x = trackview.editor().sample_to_pixel (region_frames);
+
+ double const height = midi_stream_view()->contents_height();
+
+ // CAIROCANVAS: active_channel info removed from PatcChange constructor
+ // so we need to do something more sophisticated to keep its color
+ // appearance (MidiPatchChangeFill/MidiPatchChangeInactiveChannelFill)
+ // up to date.
+ boost::shared_ptr<PatchChange> patch_change = boost::shared_ptr<PatchChange>(
+ new PatchChange(*this, group,
+ displaytext,
+ height,
+ x, 1.0,
+ instrument_info(),
+ patch,
+ _patch_change_outline,
+ _patch_change_fill)
+ );
+
+ if (patch_change->item().width() < _pixel_width) {
+ // Show unless patch change is beyond the region bounds
+ if (region_frames < 0 || region_frames >= _region->length()) {
+ patch_change->hide();
+ } else {
+ patch_change->show();
+ }
+ } else {
+ patch_change->hide ();
+ }
+
+ _patch_changes.insert (make_pair (patch, patch_change));
+}
+
+void
+MidiRegionView::remove_canvas_patch_change (PatchChange* pc)
+{
+ /* remove the canvas item */
+ for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
+ if (x->second->patch() == pc->patch()) {
+ _patch_changes.erase (x);
+ break;
+ }
+ }
+}
+
+MIDI::Name::PatchPrimaryKey
+MidiRegionView::patch_change_to_patch_key (MidiModel::PatchChangePtr p)
+{
+ return MIDI::Name::PatchPrimaryKey (p->program(), p->bank());
+}
+
+/// Return true iff @p pc applies to the given time on the given channel.
+static bool
+patch_applies (const ARDOUR::MidiModel::constPatchChangePtr pc, Evoral::Beats time, uint8_t channel)
+{
+ return pc->time() <= time && pc->channel() == channel;
+}
+
+void
+MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Name::PatchPrimaryKey& key) const
+{
+ // The earliest event not before time
+ MidiModel::PatchChanges::iterator i = _model->patch_change_lower_bound (time);
+
+ // Go backwards until we find the latest PC for this channel, or the start
+ while (i != _model->patch_changes().begin() &&
+ (i == _model->patch_changes().end() ||
+ !patch_applies(*i, time, channel))) {
+ --i;
+ }
+
+ if (i != _model->patch_changes().end() && patch_applies(*i, time, channel)) {
+ key.set_bank((*i)->bank());
+ key.set_program((*i)->program ());
+ } else {
+ key.set_bank(0);
+ key.set_program(0);
+ }
+}
+
+void
+MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
+{
+ string name = _("alter patch change");
+ trackview.editor().begin_reversible_command (name);
+
+ MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
+
+ if (pc.patch()->program() != new_patch.program()) {
+ c->change_program (pc.patch (), new_patch.program());
+ }
+
+ int const new_bank = new_patch.bank();
+ if (pc.patch()->bank() != new_bank) {
+ c->change_bank (pc.patch (), new_bank);
+ }
+
+ _model->apply_command (*trackview.session(), c);
+ trackview.editor().commit_reversible_command ();
+
+ remove_canvas_patch_change (&pc);
+ display_patch_changes ();
+}
+
+void
+MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
+{
+ string name = _("alter patch change");
+ trackview.editor().begin_reversible_command (name);
+ MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
+
+ if (old_change->time() != new_change.time()) {
+ c->change_time (old_change, new_change.time());
+ }
+
+ if (old_change->channel() != new_change.channel()) {
+ c->change_channel (old_change, new_change.channel());
+ }
+
+ if (old_change->program() != new_change.program()) {
+ c->change_program (old_change, new_change.program());
+ }
+
+ if (old_change->bank() != new_change.bank()) {
+ c->change_bank (old_change, new_change.bank());
+ }
+
+ _model->apply_command (*trackview.session(), c);
+ trackview.editor().commit_reversible_command ();
+
+ for (PatchChanges::iterator x = _patch_changes.begin(); x != _patch_changes.end(); ++x) {
+ if (x->second->patch() == old_change) {
+ _patch_changes.erase (x);
+ break;
+ }
+ }
+
+ display_patch_changes ();
+}
+
+/** Add a patch change to the region.
+ * @param t Time in frames relative to region position
+ * @param patch Patch to add; time and channel are ignored (time is converted from t, and channel comes from
+ * MidiTimeAxisView::get_channel_for_add())
+ */
+void
+MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
+{
+ string name = _("add patch change");
+
+ trackview.editor().begin_reversible_command (name);
+ MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
+ c->add (MidiModel::PatchChangePtr (
+ new Evoral::PatchChange<Evoral::Beats> (
+ absolute_frames_to_source_beats (_region->position() + t),
+ patch.channel(), patch.program(), patch.bank()
+ )
+ )
+ );
+
+ _model->apply_command (*trackview.session(), c);
+ trackview.editor().commit_reversible_command ();
+
+ display_patch_changes ();
+}
+
+void
+MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
+{
+ trackview.editor().begin_reversible_command (_("move patch change"));
+ MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
+ c->change_time (pc.patch (), t);
+ _model->apply_command (*trackview.session(), c);
+ trackview.editor().commit_reversible_command ();
+
+ display_patch_changes ();
+}
+
+void
+MidiRegionView::delete_patch_change (PatchChange* pc)
+{
+ trackview.editor().begin_reversible_command (_("delete patch change"));
+
+ MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
+ c->remove (pc->patch ());
+ _model->apply_command (*trackview.session(), c);
+ trackview.editor().commit_reversible_command ();
+
+ remove_canvas_patch_change (pc);
+ display_patch_changes ();
+}
+
+void
+MidiRegionView::step_patch (PatchChange& patch, bool bank, int delta)
+{
+ MIDI::Name::PatchPrimaryKey key = patch_change_to_patch_key(patch.patch());
+ if (bank) {
+ key.set_bank(key.bank() + delta);
+ } else {
+ key.set_program(key.program() + delta);
+ }
+ change_patch_change(patch, key);
+}
+
+void
+MidiRegionView::note_deleted (NoteBase* cne)
+{
+ if (_entered_note && cne == _entered_note) {
+ _entered_note = 0;
+ }
+
+ if (_selection.empty()) {
+ return;
+ }
+
+ _selection.erase (cne);
+}
+
+void
+MidiRegionView::delete_selection()
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ if (trackview.editor().drags()->active()) {
+ return;
+ }
+
+ start_note_diff_command (_("delete selection"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->selected()) {
+ _note_diff_command->remove((*i)->note());
+ }
+ }
+
+ _selection.clear();
+
+ apply_diff ();
+
+ hide_verbose_cursor ();
+}
+
+void
+MidiRegionView::delete_note (boost::shared_ptr<NoteType> n)
+{
+ start_note_diff_command (_("delete note"));
+ _note_diff_command->remove (n);
+ apply_diff ();
+
+ hide_verbose_cursor ();
+}
+
+void
+MidiRegionView::clear_editor_note_selection ()
+{
+ DEBUG_TRACE(DEBUG::Selection, "MRV::clear_editor_note_selection\n");
+ PublicEditor& editor(trackview.editor());
+ editor.get_selection().clear_midi_notes();
+}
+
+void
+MidiRegionView::clear_selection ()
+{
+ clear_selection_internal();
+ PublicEditor& editor(trackview.editor());
+ editor.get_selection().remove(this);
+}
+
+void
+MidiRegionView::clear_selection_internal ()
+{
+ DEBUG_TRACE(DEBUG::Selection, "MRV::clear_selection_internal\n");
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ (*i)->set_selected(false);
+ (*i)->hide_velocity();
+ }
+ _selection.clear();
+
+ if (_entered) {
+ // Clearing selection entirely, ungrab keyboard
+ Keyboard::magic_widget_drop_focus();
+ _grabbed_keyboard = false;
+ }
+}
+
+void
+MidiRegionView::unique_select(NoteBase* ev)
+{
+ clear_editor_note_selection();
+ add_to_selection(ev);
+}
+
+void
+MidiRegionView::select_all_notes ()
+{
+ clear_editor_note_selection ();
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ add_to_selection (i->second);
+ }
+}
+
+void
+MidiRegionView::select_range (framepos_t start, framepos_t end)
+{
+ clear_editor_note_selection ();
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ framepos_t t = source_beats_to_absolute_frames(i->first->time());
+ if (t >= start && t <= end) {
+ add_to_selection (i->second);
+ }
+ }
+}
+
+void
+MidiRegionView::invert_selection ()
+{
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ if (i->second->selected()) {
+ remove_from_selection(i->second);
+ } else {
+ add_to_selection (i->second);
+ }
+ }
+}
+
+/** Used for selection undo/redo.
+ The requested notes most likely won't exist in the view until the next model redisplay.
+*/
+void
+MidiRegionView::select_notes (list<Evoral::event_id_t> notes)
+{
+ NoteBase* cne;
+ list<Evoral::event_id_t>::iterator n;
+
+ for (n = notes.begin(); n != notes.end(); ++n) {
+ if ((cne = find_canvas_note(*n)) != 0) {
+ add_to_selection (cne);
+ } else {
+ _pending_note_selection.insert(*n);
+ }
+ }
+}
+
+void
+MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
+{
+ bool have_selection = !_selection.empty();
+ uint8_t low_note = 127;
+ uint8_t high_note = 0;
+ MidiModel::Notes& notes (_model->notes());
+ _optimization_iterator = _events.begin();
+
+ if (extend && !have_selection) {
+ extend = false;
+ }
+
+ /* scan existing selection to get note range */
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->note()->note() < low_note) {
+ low_note = (*i)->note()->note();
+ }
+ if ((*i)->note()->note() > high_note) {
+ high_note = (*i)->note()->note();
+ }
+ }
+
+ if (!add) {
+ clear_editor_note_selection ();
+
+ if (!extend && (low_note == high_note) && (high_note == notenum)) {
+ /* only note previously selected is the one we are
+ * reselecting. treat this as cancelling the selection.
+ */
+ return;
+ }
+ }
+
+ if (extend) {
+ low_note = min (low_note, notenum);
+ high_note = max (high_note, notenum);
+ }
+
+ _no_sound_notes = true;
+
+ for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
+
+ boost::shared_ptr<NoteType> note (*n);
+ NoteBase* cne;
+ bool select = false;
+
+ if (((1 << note->channel()) & channel_mask) != 0) {
+ if (extend) {
+ if ((note->note() >= low_note && note->note() <= high_note)) {
+ select = true;
+ }
+ } else if (note->note() == notenum) {
+ select = true;
+ }
+ }
+
+ if (select) {
+ if ((cne = find_canvas_note (note)) != 0) {
+ // extend is false because we've taken care of it,
+ // since it extends by time range, not pitch.
+ note_selected (cne, add, false);
+ }
+ }
+
+ add = true; // we need to add all remaining matching notes, even if the passed in value was false (for "set")
+
+ }
+
+ _no_sound_notes = false;
+}
+
+void
+MidiRegionView::toggle_matching_notes (uint8_t notenum, uint16_t channel_mask)
+{
+ MidiModel::Notes& notes (_model->notes());
+ _optimization_iterator = _events.begin();
+
+ for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
+
+ boost::shared_ptr<NoteType> note (*n);
+ NoteBase* cne;
+
+ if (note->note() == notenum && (((0x0001 << note->channel()) & channel_mask) != 0)) {
+ if ((cne = find_canvas_note (note)) != 0) {
+ if (cne->selected()) {
+ note_deselected (cne);
+ } else {
+ note_selected (cne, true, false);
+ }
+ }
+ }
+ }
+}
+
+void
+MidiRegionView::note_selected (NoteBase* ev, bool add, bool extend)
+{
+ if (!add) {
+ clear_editor_note_selection();
+ add_to_selection (ev);
+ }
+
+ if (!extend) {
+
+ if (!ev->selected()) {
+ add_to_selection (ev);
+ }
+
+ } else {
+ /* find end of latest note selected, select all between that and the start of "ev" */
+
+ Evoral::Beats earliest = Evoral::MaxBeats;
+ Evoral::Beats latest = Evoral::Beats();
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->note()->end_time() > latest) {
+ latest = (*i)->note()->end_time();
+ }
+ if ((*i)->note()->time() < earliest) {
+ earliest = (*i)->note()->time();
+ }
+ }
+
+ if (ev->note()->end_time() > latest) {
+ latest = ev->note()->end_time();
+ }
+
+ if (ev->note()->time() < earliest) {
+ earliest = ev->note()->time();
+ }
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+
+ /* find notes entirely within OR spanning the earliest..latest range */
+
+ if ((i->first->time() >= earliest && i->first->end_time() <= latest) ||
+ (i->first->time() <= earliest && i->first->end_time() >= latest)) {
+ add_to_selection (i->second);
+ }
+ }
+ }
+}
+
+void
+MidiRegionView::note_deselected(NoteBase* ev)
+{
+ remove_from_selection (ev);
+}
+
+void
+MidiRegionView::update_drag_selection(framepos_t start, framepos_t end, double gy0, double gy1, bool extend)
+{
+ PublicEditor& editor = trackview.editor();
+
+ // Convert to local coordinates
+ const framepos_t p = _region->position();
+ const double y = midi_view()->y_position();
+ const double x0 = editor.sample_to_pixel(max((framepos_t)0, start - p));
+ const double x1 = editor.sample_to_pixel(max((framepos_t)0, end - p));
+ const double y0 = max(0.0, gy0 - y);
+ const double y1 = max(0.0, gy1 - y);
+
+ // TODO: Make this faster by storing the last updated selection rect, and only
+ // adjusting things that are in the area that appears/disappeared.
+ // We probably need a tree to be able to find events in O(log(n)) time.
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ if (i->second->x0() < x1 && i->second->x1() > x0 && i->second->y0() < y1 && i->second->y1() > y0) {
+ // Rectangles intersect
+ if (!i->second->selected()) {
+ add_to_selection (i->second);
+ }
+ } else if (i->second->selected() && !extend) {
+ // Rectangles do not intersect
+ remove_from_selection (i->second);
+ }
+ }
+
+ typedef RouteTimeAxisView::AutomationTracks ATracks;
+ typedef std::list<Selectable*> Selectables;
+
+ /* Add control points to selection. */
+ const ATracks& atracks = midi_view()->automation_tracks();
+ Selectables selectables;
+ editor.get_selection().clear_points();
+ for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
+ a->second->get_selectables(start, end, gy0, gy1, selectables);
+ for (Selectables::const_iterator s = selectables.begin(); s != selectables.end(); ++s) {
+ ControlPoint* cp = dynamic_cast<ControlPoint*>(*s);
+ if (cp) {
+ editor.get_selection().add(cp);
+ }
+ }
+ a->second->set_selected_points(editor.get_selection().points);
+ }
+}
+
+void
+MidiRegionView::update_vertical_drag_selection (double y1, double y2, bool extend)
+{
+ if (y1 > y2) {
+ swap (y1, y2);
+ }
+
+ // TODO: Make this faster by storing the last updated selection rect, and only
+ // adjusting things that are in the area that appears/disappeared.
+ // We probably need a tree to be able to find events in O(log(n)) time.
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ if ((i->second->y1() >= y1 && i->second->y1() <= y2)) {
+ // within y- (note-) range
+ if (!i->second->selected()) {
+ add_to_selection (i->second);
+ }
+ } else if (i->second->selected() && !extend) {
+ remove_from_selection (i->second);
+ }
+ }
+}
+
+void
+MidiRegionView::remove_from_selection (NoteBase* ev)
+{
+ Selection::iterator i = _selection.find (ev);
+
+ if (i != _selection.end()) {
+ _selection.erase (i);
+ if (_selection.empty() && _grabbed_keyboard) {
+ // Ungrab keyboard
+ Keyboard::magic_widget_drop_focus();
+ _grabbed_keyboard = false;
+ }
+ }
+
+ ev->set_selected (false);
+ ev->hide_velocity ();
+
+ if (_selection.empty()) {
+ PublicEditor& editor (trackview.editor());
+ editor.get_selection().remove (this);
+ }
+}
+
+void
+MidiRegionView::add_to_selection (NoteBase* ev)
+{
+ const bool selection_was_empty = _selection.empty();
+
+ if (_selection.insert (ev).second) {
+ ev->set_selected (true);
+ start_playing_midi_note ((ev)->note());
+ if (selection_was_empty && _entered) {
+ // Grab keyboard for moving notes with arrow keys
+ Keyboard::magic_widget_grab_focus();
+ _grabbed_keyboard = true;
+ }
+ }
+
+ if (selection_was_empty) {
+ PublicEditor& editor (trackview.editor());
+ editor.get_selection().add (this);
+ }
+}
+
+Evoral::Beats
+MidiRegionView::earliest_in_selection ()
+{
+ Evoral::Beats earliest = Evoral::MaxBeats;
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->note()->time() < earliest) {
+ earliest = (*i)->note()->time();
+ }
+ }
+
+ return earliest;
+}
+
+void
+MidiRegionView::move_selection(double dx_qn, double dy, double cumulative_dy)
+{
+ typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+ Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
+ TempoMap& tmap (editor->session()->tempo_map());
+ PossibleChord to_play;
+ Evoral::Beats earliest = earliest_in_selection();
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ NoteBase* n = *i;
+ if (n->note()->time() == earliest) {
+ to_play.push_back (n->note());
+ }
+ double const note_time_qn = session_relative_qn (n->note()->time().to_double());
+ double dx = 0.0;
+ if (midi_view()->note_mode() == Sustained) {
+ dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+ - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
+ } else {
+ /* Hit::x0() is offset by _position.x, unlike Note::x0() */
+ Hit* hit = dynamic_cast<Hit*>(n);
+ if (hit) {
+ dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+ - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
+ }
+ }
+
+ (*i)->move_event(dx, dy);
+
+ /* update length */
+ if (midi_view()->note_mode() == Sustained) {
+ Note* sus = dynamic_cast<Note*> (*i);
+ double const len_dx = editor->sample_to_pixel_unrounded (
+ tmap.frame_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
+
+ sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
+ }
+ }
+
+ if (dy && !_selection.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
+
+ if (to_play.size() > 1) {
+
+ PossibleChord shifted;
+
+ for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
+ boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
+ moved_note->set_note (moved_note->note() + cumulative_dy);
+ shifted.push_back (moved_note);
+ }
+
+ start_playing_midi_chord (shifted);
+
+ } else if (!to_play.empty()) {
+
+ boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
+ moved_note->set_note (moved_note->note() + cumulative_dy);
+ start_playing_midi_note (moved_note);
+ }
+ }
+}
+
+NoteBase*
+MidiRegionView::copy_selection (NoteBase* primary)
+{
+ _copy_drag_events.clear ();
+
+ if (_selection.empty()) {
+ return 0;
+ }
+
+ NoteBase* note;
+ NoteBase* ret = 0;
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ boost::shared_ptr<NoteType> g (new NoteType (*((*i)->note())));
+ if (midi_view()->note_mode() == Sustained) {
+ Note* n = new Note (*this, _note_group, g);
+ update_sustained (n, false);
+ note = n;
+ } else {
+ Hit* h = new Hit (*this, _note_group, 10, g);
+ update_hit (h, false);
+ note = h;
+ }
+
+ if ((*i) == primary) {
+ ret = note;
+ }
+
+ _copy_drag_events.push_back (note);
+ }
+
+ return ret;
+}
+
+void
+MidiRegionView::move_copies (double dx_qn, double dy, double cumulative_dy)
+{
+ typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+ Editor* editor = dynamic_cast<Editor*> (&trackview.editor());
+ TempoMap& tmap (editor->session()->tempo_map());
+ PossibleChord to_play;
+ Evoral::Beats earliest = earliest_in_selection();
+
+ for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
+ NoteBase* n = *i;
+ if (n->note()->time() == earliest) {
+ to_play.push_back (n->note());
+ }
+ double const note_time_qn = session_relative_qn (n->note()->time().to_double());
+ double dx = 0.0;
+ if (midi_view()->note_mode() == Sustained) {
+ dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+ - n->item()->item_to_canvas (ArdourCanvas::Duple (n->x0(), 0)).x;
+ } else {
+ Hit* hit = dynamic_cast<Hit*>(n);
+ if (hit) {
+ dx = editor->sample_to_pixel_unrounded (tmap.frame_at_quarter_note (note_time_qn + dx_qn))
+ - n->item()->item_to_canvas (ArdourCanvas::Duple (((hit->x0() + hit->x1()) / 2.0) - hit->position().x, 0)).x;
+ }
+ }
+
+ (*i)->move_event(dx, dy);
+
+ if (midi_view()->note_mode() == Sustained) {
+ Note* sus = dynamic_cast<Note*> (*i);
+ double const len_dx = editor->sample_to_pixel_unrounded (
+ tmap.frame_at_quarter_note (note_time_qn + dx_qn + n->note()->length().to_double()));
+
+ sus->set_x1 (n->item()->canvas_to_item (ArdourCanvas::Duple (len_dx, 0)).x);
+ }
+ }
+
+ if (dy && !_copy_drag_events.empty() && !_no_sound_notes && UIConfiguration::instance().get_sound_midi_notes()) {
+
+ if (to_play.size() > 1) {
+
+ PossibleChord shifted;
+
+ for (PossibleChord::iterator n = to_play.begin(); n != to_play.end(); ++n) {
+ boost::shared_ptr<NoteType> moved_note (new NoteType (**n));
+ moved_note->set_note (moved_note->note() + cumulative_dy);
+ shifted.push_back (moved_note);
+ }
+
+ start_playing_midi_chord (shifted);
+
+ } else if (!to_play.empty()) {
+
+ boost::shared_ptr<NoteType> moved_note (new NoteType (*to_play.front()));
+ moved_note->set_note (moved_note->note() + cumulative_dy);
+ start_playing_midi_note (moved_note);
+ }
+ }
+}
+
+void
+MidiRegionView::note_dropped(NoteBase *, double d_qn, int8_t dnote, bool copy)
+{
+ uint8_t lowest_note_in_selection = 127;
+ uint8_t highest_note_in_selection = 0;
+ uint8_t highest_note_difference = 0;
+
+ if (!copy) {
+ // find highest and lowest notes first
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ uint8_t pitch = (*i)->note()->note();
+ lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, pitch);
+ }
+
+ /*
+ cerr << "dnote: " << (int) dnote << endl;
+ cerr << "lowest note (streamview): " << int(midi_stream_view()->lowest_note())
+ << " highest note (streamview): " << int(midi_stream_view()->highest_note()) << endl;
+ cerr << "lowest note (selection): " << int(lowest_note_in_selection) << " highest note(selection): "
+ << int(highest_note_in_selection) << endl;
+ cerr << "selection size: " << _selection.size() << endl;
+ cerr << "Highest note in selection: " << (int) highest_note_in_selection << endl;
+ */
+
+ // Make sure the note pitch does not exceed the MIDI standard range
+ if (highest_note_in_selection + dnote > 127) {
+ highest_note_difference = highest_note_in_selection - 127;
+ }
+
+ start_note_diff_command (_("move notes"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ++i) {
+
+ Evoral::Beats new_time = Evoral::Beats ((*i)->note()->time().to_double() + d_qn);
+
+ if (new_time < 0) {
+ continue;
+ }
+
+ note_diff_add_change (*i, MidiModel::NoteDiffCommand::StartTime, new_time);
+
+ uint8_t original_pitch = (*i)->note()->note();
+ uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
+
+ // keep notes in standard midi range
+ clamp_to_0_127(new_pitch);
+
+ lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+ note_diff_add_change (*i, MidiModel::NoteDiffCommand::NoteNumber, new_pitch);
+ }
+ } else {
+
+ clear_editor_note_selection ();
+
+ for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end(); ++i) {
+ uint8_t pitch = (*i)->note()->note();
+ lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, pitch);
+ }
+
+ // Make sure the note pitch does not exceed the MIDI standard range
+ if (highest_note_in_selection + dnote > 127) {
+ highest_note_difference = highest_note_in_selection - 127;
+ }
+
+ start_note_diff_command (_("copy notes"));
+
+ for (CopyDragEvents::iterator i = _copy_drag_events.begin(); i != _copy_drag_events.end() ; ++i) {
+
+ /* update time */
+ Evoral::Beats new_time = Evoral::Beats ((*i)->note()->time().to_double() + d_qn);
+
+ if (new_time < 0) {
+ continue;
+ }
+
+ (*i)->note()->set_time (new_time);
+
+ /* update pitch */
+
+ uint8_t original_pitch = (*i)->note()->note();
+ uint8_t new_pitch = original_pitch + dnote - highest_note_difference;
+
+ (*i)->note()->set_note (new_pitch);
+
+ // keep notes in standard midi range
+ clamp_to_0_127(new_pitch);
+
+ lowest_note_in_selection = std::min(lowest_note_in_selection, new_pitch);
+ highest_note_in_selection = std::max(highest_note_in_selection, new_pitch);
+
+ note_diff_add_note ((*i)->note(), true);
+
+ delete *i;
+ }
+
+ _copy_drag_events.clear ();
+ }
+
+ apply_diff (false, copy);
+
+ // care about notes being moved beyond the upper/lower bounds on the canvas
+ if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
+ highest_note_in_selection > midi_stream_view()->highest_note()) {
+ midi_stream_view()->set_note_range (MidiStreamView::ContentsRange);
+ }
+}
+
+/** @param x Pixel relative to the region position.
+ * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap.
+ * Used for inverting the snap logic with key modifiers and snap delta calculation.
+ * @return Snapped frame relative to the region position.
+ */
+framepos_t
+MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap)
+{
+ PublicEditor& editor (trackview.editor());
+ return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap).frame;
+}
+
+/** @param x Pixel relative to the region position.
+ * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation).
+ * @return Snapped pixel relative to the region position.
+ */
+double
+MidiRegionView::snap_to_pixel(double x, bool ensure_snap)
+{
+ return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap));
+}
+
+double
+MidiRegionView::get_position_pixels()
+{
+ framepos_t region_frame = get_position();
+ return trackview.editor().sample_to_pixel(region_frame);
+}
+
+double
+MidiRegionView::get_end_position_pixels()
+{
+ framepos_t frame = get_position() + get_duration ();
+ return trackview.editor().sample_to_pixel(frame);
+}
+
+framepos_t
+MidiRegionView::source_beats_to_absolute_frames(Evoral::Beats beats) const
+{
+ /* the time converter will return the frame corresponding to `beats'
+ relative to the start of the source. The start of the source
+ is an implied position given by region->position - region->start
+ */
+ const framepos_t source_start = _region->position() - _region->start();
+ return source_start + _source_relative_time_converter.to (beats);
+}
+
+Evoral::Beats
+MidiRegionView::absolute_frames_to_source_beats(framepos_t frames) const
+{
+ /* the `frames' argument needs to be converted into a frame count
+ relative to the start of the source before being passed in to the
+ converter.
+ */
+ const framepos_t source_start = _region->position() - _region->start();
+ return _source_relative_time_converter.from (frames - source_start);
+}
+
+framepos_t
+MidiRegionView::region_beats_to_region_frames(Evoral::Beats beats) const
+{
+ return _region_relative_time_converter.to(beats);
+}
+
+Evoral::Beats
+MidiRegionView::region_frames_to_region_beats(framepos_t frames) const
+{
+ return _region_relative_time_converter.from(frames);
+}
+
+double
+MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const
+{
+ return _region_relative_time_converter_double.from(frames);
+}
+
+void
+MidiRegionView::begin_resizing (bool /*at_front*/)
+{
+ _resize_data.clear();
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ Note *note = dynamic_cast<Note*> (*i);
+
+ // only insert CanvasNotes into the map
+ if (note) {
+ NoteResizeData *resize_data = new NoteResizeData();
+ resize_data->note = note;
+
+ // create a new SimpleRect from the note which will be the resize preview
+ ArdourCanvas::Rectangle *resize_rect = new ArdourCanvas::Rectangle (_note_group,
+ ArdourCanvas::Rect (note->x0(), note->y0(), note->x0(), note->y1()));
+
+ // calculate the colors: get the color settings
+ uint32_t fill_color = UINT_RGBA_CHANGE_A(
+ UIConfiguration::instance().color ("midi note selected"),
+ 128);
+
+ // make the resize preview notes more transparent and bright
+ fill_color = UINT_INTERPOLATE(fill_color, 0xFFFFFF40, 0.5);
+
+ // calculate color based on note velocity
+ resize_rect->set_fill_color (UINT_INTERPOLATE(
+ NoteBase::meter_style_fill_color(note->note()->velocity(), note->selected()),
+ fill_color,
+ 0.85));
+
+ resize_rect->set_outline_color (NoteBase::calculate_outline (
+ UIConfiguration::instance().color ("midi note selected")));
+
+ resize_data->resize_rect = resize_rect;
+ _resize_data.push_back(resize_data);
+ }
+ }
+}
+
+/** Update resizing notes while user drags.
+ * @param primary `primary' note for the drag; ie the one that is used as the reference in non-relative mode.
+ * @param at_front which end of the note (true == note on, false == note off)
+ * @param delta_x change in mouse position since the start of the drag
+ * @param relative true if relative resizing is taking place, false if absolute resizing. This only makes
+ * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the
+ * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point
+ * as the \a primary note.
+ * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode.
+ * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used.
+ */
+void
+MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
+{
+ TempoMap& tmap (trackview.session()->tempo_map());
+ bool cursor_set = false;
+ bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
+
+ for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
+ ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
+ Note* canvas_note = (*i)->note;
+ double current_x;
+
+ if (at_front) {
+ if (relative) {
+ current_x = canvas_note->x0() + delta_x + snap_delta;
+ } else {
+ current_x = primary->x0() + delta_x + snap_delta;
+ }
+ } else {
+ if (relative) {
+ current_x = canvas_note->x1() + delta_x + snap_delta;
+ } else {
+ current_x = primary->x1() + delta_x + snap_delta;
+ }
+ }
+
+ if (current_x < 0) {
+ // This works even with snapping because RegionView::snap_frame_to_frame()
+ // snaps forward if the snapped sample is before the beginning of the region
+ current_x = 0;
+ }
+ if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
+ current_x = trackview.editor().sample_to_pixel(_region->length());
+ }
+
+ if (at_front) {
+ if (with_snap) {
+ resize_rect->set_x0 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
+ } else {
+ resize_rect->set_x0 (current_x - snap_delta);
+ }
+ resize_rect->set_x1 (canvas_note->x1());
+ } else {
+ if (with_snap) {
+ resize_rect->set_x1 (snap_to_pixel (current_x, ensure_snap) - snap_delta);
+ } else {
+ resize_rect->set_x1 (current_x - snap_delta);
+ }
+ resize_rect->set_x0 (canvas_note->x0());
+ }
+
+ if (!cursor_set) {
+ /* Convert snap delta from pixels to beats. */
+ framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
+ double snap_delta_beats = 0.0;
+ int sign = 1;
+
+ /* negative beat offsets aren't allowed */
+ if (snap_delta_samps > 0) {
+ snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
+ } else if (snap_delta_samps < 0) {
+ snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
+ sign = -1;
+ }
+
+ double snapped_x;
+ int32_t divisions = 0;
+
+ if (with_snap) {
+ snapped_x = snap_pixel_to_sample (current_x, ensure_snap);
+ divisions = trackview.editor().get_grid_music_divisions (0);
+ } else {
+ snapped_x = trackview.editor ().pixel_to_sample (current_x);
+ }
+ const Evoral::Beats beats = Evoral::Beats (tmap.exact_beat_at_frame (snapped_x + midi_region()->position(), divisions)
+ - midi_region()->beat()) + midi_region()->start_beats();
+
+ Evoral::Beats len = Evoral::Beats();
+
+ if (at_front) {
+ if (beats < canvas_note->note()->end_time()) {
+ len = canvas_note->note()->time() - beats + (sign * snap_delta_beats);
+ len += canvas_note->note()->length();
+ }
+ } else {
+ if (beats >= canvas_note->note()->time()) {
+ len = beats - canvas_note->note()->time() - (sign * snap_delta_beats);
+ }
+ }
+
+ len = std::max(Evoral::Beats(1 / 512.0), len);
+
+ char buf[16];
+ snprintf (buf, sizeof (buf), "%.3g beats", len.to_double());
+ show_verbose_cursor (buf, 0, 0);
+
+ cursor_set = true;
+ }
+
+ }
+}
+
+
+/** Finish resizing notes when the user releases the mouse button.
+ * Parameters the same as for \a update_resizing().
+ */
+void
+MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap)
+{
+ _note_diff_command = _model->new_note_diff_command (_("resize notes"));
+ TempoMap& tmap (trackview.session()->tempo_map());
+
+ /* XX why doesn't snap_pixel_to_sample() handle this properly? */
+ bool const ensure_snap = trackview.editor().snap_mode () != SnapMagnetic;
+
+ for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
+ Note* canvas_note = (*i)->note;
+ ArdourCanvas::Rectangle* resize_rect = (*i)->resize_rect;
+
+ /* Get the new x position for this resize, which is in pixels relative
+ * to the region position.
+ */
+
+ double current_x;
+
+ if (at_front) {
+ if (relative) {
+ current_x = canvas_note->x0() + delta_x + snap_delta;
+ } else {
+ current_x = primary->x0() + delta_x + snap_delta;
+ }
+ } else {
+ if (relative) {
+ current_x = canvas_note->x1() + delta_x + snap_delta;
+ } else {
+ current_x = primary->x1() + delta_x + snap_delta;
+ }
+ }
+
+ if (current_x < 0) {
+ current_x = 0;
+ }
+ if (current_x > trackview.editor().sample_to_pixel(_region->length())) {
+ current_x = trackview.editor().sample_to_pixel(_region->length());
+ }
+
+ /* Convert snap delta from pixels to beats with sign. */
+ framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta);
+ double snap_delta_beats = 0.0;
+ int sign = 1;
+
+ if (snap_delta_samps > 0) {
+ snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps);
+ } else if (snap_delta_samps < 0) {
+ snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps);
+ sign = -1;
+ }
+
+ uint32_t divisions = 0;
+ /* Convert the new x position to a frame within the source */
+ framepos_t current_fr;
+ if (with_snap) {
+ current_fr = snap_pixel_to_sample (current_x, ensure_snap);
+ divisions = trackview.editor().get_grid_music_divisions (0);
+ } else {
+ current_fr = trackview.editor().pixel_to_sample (current_x);
+ }
+
+ /* and then to beats */
+ const double e_qaf = tmap.exact_qn_at_frame (current_fr + midi_region()->position(), divisions);
+ const double quarter_note_start = _region->quarter_note() - midi_region()->start_beats();
+ const Evoral::Beats x_beats = Evoral::Beats (e_qaf - quarter_note_start);
+
+ if (at_front && x_beats < canvas_note->note()->end_time()) {
+ note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats));
+ Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats);
+ len += canvas_note->note()->length();
+
+ if (!!len) {
+ note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
+ }
+ }
+
+ if (!at_front) {
+ Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0),
+ x_beats - canvas_note->note()->time() - (sign * snap_delta_beats));
+ note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len);
+ }
+
+ delete resize_rect;
+ delete (*i);
+ }
+
+ _resize_data.clear();
+ apply_diff(true);
+}
+
+void
+MidiRegionView::abort_resizing ()
+{
+ for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
+ delete (*i)->resize_rect;
+ delete *i;
+ }
+
+ _resize_data.clear ();
+}
+
+void
+MidiRegionView::change_note_velocity(NoteBase* event, int8_t velocity, bool relative)
+{
+ uint8_t new_velocity;
+
+ if (relative) {
+ new_velocity = event->note()->velocity() + velocity;
+ clamp_to_0_127(new_velocity);
+ } else {
+ new_velocity = velocity;
+ }
+
+ event->set_selected (event->selected()); // change color
+
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::Velocity, new_velocity);
+}
+
+void
+MidiRegionView::change_note_note (NoteBase* event, int8_t note, bool relative)
+{
+ uint8_t new_note;
+
+ if (relative) {
+ new_note = event->note()->note() + note;
+ } else {
+ new_note = note;
+ }
+
+ clamp_to_0_127 (new_note);
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::NoteNumber, new_note);
+}
+
+void
+MidiRegionView::trim_note (NoteBase* event, Evoral::Beats front_delta, Evoral::Beats end_delta)
+{
+ bool change_start = false;
+ bool change_length = false;
+ Evoral::Beats new_start;
+ Evoral::Beats new_length;
+
+ /* NOTE: the semantics of the two delta arguments are slightly subtle:
+
+ front_delta: if positive - move the start of the note later in time (shortening it)
+ if negative - move the start of the note earlier in time (lengthening it)
+
+ end_delta: if positive - move the end of the note later in time (lengthening it)
+ if negative - move the end of the note earlier in time (shortening it)
+ */
+
+ if (!!front_delta) {
+ if (front_delta < 0) {
+
+ if (event->note()->time() < -front_delta) {
+ new_start = Evoral::Beats();
+ } else {
+ new_start = event->note()->time() + front_delta; // moves earlier
+ }
+
+ /* start moved toward zero, so move the end point out to where it used to be.
+ Note that front_delta is negative, so this increases the length.
+ */
+
+ new_length = event->note()->length() - front_delta;
+ change_start = true;
+ change_length = true;
+
+ } else {
+
+ Evoral::Beats new_pos = event->note()->time() + front_delta;
+
+ if (new_pos < event->note()->end_time()) {
+ new_start = event->note()->time() + front_delta;
+ /* start moved toward the end, so move the end point back to where it used to be */
+ new_length = event->note()->length() - front_delta;
+ change_start = true;
+ change_length = true;
+ }
+ }
+
+ }
+
+ if (!!end_delta) {
+ bool can_change = true;
+ if (end_delta < 0) {
+ if (event->note()->length() < -end_delta) {
+ can_change = false;
+ }
+ }
+
+ if (can_change) {
+ new_length = event->note()->length() + end_delta;
+ change_length = true;
+ }
+ }
+
+ if (change_start) {
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_start);
+ }
+
+ if (change_length) {
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, new_length);
+ }
+}
+
+void
+MidiRegionView::change_note_channel (NoteBase* event, int8_t chn, bool relative)
+{
+ uint8_t new_channel;
+
+ if (relative) {
+ if (chn < 0.0) {
+ if (event->note()->channel() < -chn) {
+ new_channel = 0;
+ } else {
+ new_channel = event->note()->channel() + chn;
+ }
+ } else {
+ new_channel = event->note()->channel() + chn;
+ }
+ } else {
+ new_channel = (uint8_t) chn;
+ }
+
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::Channel, new_channel);
+}
+
+void
+MidiRegionView::change_note_time (NoteBase* event, Evoral::Beats delta, bool relative)
+{
+ Evoral::Beats new_time;
+
+ if (relative) {
+ if (delta < 0.0) {
+ if (event->note()->time() < -delta) {
+ new_time = Evoral::Beats();
+ } else {
+ new_time = event->note()->time() + delta;
+ }
+ } else {
+ new_time = event->note()->time() + delta;
+ }
+ } else {
+ new_time = delta;
+ }
+
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::StartTime, new_time);
+}
+
+void
+MidiRegionView::change_note_length (NoteBase* event, Evoral::Beats t)
+{
+ note_diff_add_change (event, MidiModel::NoteDiffCommand::Length, t);
+}
+
+void
+MidiRegionView::change_velocities (bool up, bool fine, bool allow_smush, bool all_together)
+{
+ int8_t delta;
+ int8_t value = 0;
+
+ if (_selection.empty()) {
+ return;
+ }
+
+ if (fine) {
+ delta = 1;
+ } else {
+ delta = 10;
+ }
+
+ if (!up) {
+ delta = -delta;
+ }
+
+ if (!allow_smush) {
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->note()->velocity() < -delta || (*i)->note()->velocity() + delta > 127) {
+ goto cursor_label;
+ }
+ }
+ }
+
+ start_note_diff_command (_("change velocities"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end();) {
+ Selection::iterator next = i;
+ ++next;
+
+ if (all_together) {
+ if (i == _selection.begin()) {
+ change_note_velocity (*i, delta, true);
+ value = (*i)->note()->velocity() + delta;
+ } else {
+ change_note_velocity (*i, value, false);
+ }
+
+ } else {
+ change_note_velocity (*i, delta, true);
+ }
+
+ i = next;
+ }
+
+ apply_diff();
+
+ cursor_label:
+ if (!_selection.empty()) {
+ char buf[24];
+ snprintf (buf, sizeof (buf), "Vel %d",
+ (int) (*_selection.begin())->note()->velocity());
+ show_verbose_cursor (buf, 10, 10);
+ }
+}
+
+
+void
+MidiRegionView::transpose (bool up, bool fine, bool allow_smush)
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ int8_t delta;
+
+ if (fine) {
+ delta = 1;
+ } else {
+ delta = 12;
+ }
+
+ if (!up) {
+ delta = -delta;
+ }
+
+ if (!allow_smush) {
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if (!up) {
+ if ((int8_t) (*i)->note()->note() + delta <= 0) {
+ return;
+ }
+ } else {
+ if ((int8_t) (*i)->note()->note() + delta > 127) {
+ return;
+ }
+ }
+ }
+ }
+
+ start_note_diff_command (_("transpose"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
+ Selection::iterator next = i;
+ ++next;
+ change_note_note (*i, delta, true);
+ i = next;
+ }
+
+ apply_diff ();
+}
+
+void
+MidiRegionView::change_note_lengths (bool fine, bool shorter, Evoral::Beats delta, bool start, bool end)
+{
+ if (!delta) {
+ if (fine) {
+ delta = Evoral::Beats(1.0/128.0);
+ } else {
+ /* grab the current grid distance */
+ delta = get_grid_beats(_region->position());
+ }
+ }
+
+ if (shorter) {
+ delta = -delta;
+ }
+
+ start_note_diff_command (_("change note lengths"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
+ Selection::iterator next = i;
+ ++next;
+
+ /* note the negation of the delta for start */
+
+ trim_note (*i,
+ (start ? -delta : Evoral::Beats()),
+ (end ? delta : Evoral::Beats()));
+ i = next;
+ }
+
+ apply_diff ();
+
+}
+
+void
+MidiRegionView::nudge_notes (bool forward, bool fine)
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ /* pick a note as the point along the timeline to get the nudge distance.
+ its not necessarily the earliest note, so we may want to pull the notes out
+ into a vector and sort before using the first one.
+ */
+
+ const framepos_t ref_point = source_beats_to_absolute_frames ((*(_selection.begin()))->note()->time());
+ Evoral::Beats delta;
+
+ if (!fine) {
+
+ /* non-fine, move by 1 bar regardless of snap */
+ delta = Evoral::Beats(trackview.session()->tempo_map().meter_at_frame (ref_point).divisions_per_bar());
+
+ } else if (trackview.editor().snap_mode() == Editing::SnapOff) {
+
+ /* grid is off - use nudge distance */
+
+ framepos_t unused;
+ const framecnt_t distance = trackview.editor().get_nudge_distance (ref_point, unused);
+ delta = region_frames_to_region_beats (fabs ((double)distance));
+
+ } else {
+
+ /* use grid */
+
+ MusicFrame next_pos (ref_point, 0);
+ if (forward) {
+ if (max_framepos - 1 < next_pos.frame) {
+ next_pos.frame += 1;
+ }
+ } else {
+ if (next_pos.frame == 0) {
+ return;
+ }
+ next_pos.frame -= 1;
+ }
+
+ trackview.editor().snap_to (next_pos, (forward ? RoundUpAlways : RoundDownAlways), false);
+ const framecnt_t distance = ref_point - next_pos.frame;
+ delta = region_frames_to_region_beats (fabs ((double)distance));
+ }
+
+ if (!delta) {
+ return;
+ }
+
+ if (!forward) {
+ delta = -delta;
+ }
+
+ start_note_diff_command (_("nudge"));
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ) {
+ Selection::iterator next = i;
+ ++next;
+ change_note_time (*i, delta, true);
+ i = next;
+ }
+
+ apply_diff ();
+}
+
+void
+MidiRegionView::change_channel(uint8_t channel)
+{
+ start_note_diff_command(_("change channel"));
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ note_diff_add_change (*i, MidiModel::NoteDiffCommand::Channel, channel);
+ }
+
+ apply_diff();
+}
+
+
+void
+MidiRegionView::note_entered(NoteBase* ev)
+{
+ _entered_note = ev;
+
+ Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
+
+ if (_mouse_state == SelectTouchDragging) {
+
+ note_selected (ev, true);
+
+ } else if (editor->current_mouse_mode() == MouseContent) {
+
+ remove_ghost_note ();
+ show_verbose_cursor (ev->note ());
+
+ } else if (editor->current_mouse_mode() == MouseDraw) {
+
+ remove_ghost_note ();
+ show_verbose_cursor (ev->note ());
+ }
+}
+
+void
+MidiRegionView::note_left (NoteBase*)
+{
+ _entered_note = 0;
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ (*i)->hide_velocity ();
+ }
+
+ hide_verbose_cursor ();
+}
+
+void
+MidiRegionView::patch_entered (PatchChange* p)
+{
+ ostringstream s;
+ s << _("Bank ") << (p->patch()->bank() + MIDI_BP_ZERO) << '\n'
+ << instrument_info().get_patch_name_without (p->patch()->bank(), p->patch()->program(), p->patch()->channel()) << '\n'
+ << _("Channel ") << ((int) p->patch()->channel() + 1);
+ show_verbose_cursor (s.str(), 10, 20);
+ p->item().grab_focus();
+}
+
+void
+MidiRegionView::patch_left (PatchChange *)
+{
+ hide_verbose_cursor ();
+ /* focus will transfer back via the enter-notify event sent to this
+ * midi region view.
+ */
+}
+
+void
+MidiRegionView::sysex_entered (SysEx* p)
+{
+ // ostringstream s;
+ // CAIROCANVAS
+ // need a way to extract text from p->_flag->_text
+ // s << p->text();
+ // show_verbose_cursor (s.str(), 10, 20);
+ p->item().grab_focus();
+}
+
+void
+MidiRegionView::sysex_left (SysEx *)
+{
+ hide_verbose_cursor ();
+ /* focus will transfer back via the enter-notify event sent to this
+ * midi region view.
+ */
+}
+
+void
+MidiRegionView::note_mouse_position (float x_fraction, float /*y_fraction*/, bool can_set_cursor)
+{
+ Editor* editor = dynamic_cast<Editor*>(&trackview.editor());
+ Editing::MouseMode mm = editor->current_mouse_mode();
+ bool trimmable = (mm == MouseContent || mm == MouseTimeFX || mm == MouseDraw);
+
+ Editor::EnterContext* ctx = editor->get_enter_context(NoteItem);
+ if (can_set_cursor && ctx) {
+ if (trimmable && x_fraction > 0.0 && x_fraction < 0.2) {
+ ctx->cursor_ctx->change(editor->cursors()->left_side_trim);
+ } else if (trimmable && x_fraction >= 0.8 && x_fraction < 1.0) {
+ ctx->cursor_ctx->change(editor->cursors()->right_side_trim);
+ } else {
+ ctx->cursor_ctx->change(editor->cursors()->grabber_note);
+ }
+ }
+}
+
+uint32_t
+MidiRegionView::get_fill_color() const
+{
+ const std::string mod_name = (_dragging ? "dragging region" :
+ trackview.editor().internal_editing() ? "editable region" :
+ "midi frame base");
+ if (_selected) {
+ return UIConfiguration::instance().color_mod ("selected region base", mod_name);
+ } else if ((!UIConfiguration::instance().get_show_name_highlight() || high_enough_for_name) &&
+ !UIConfiguration::instance().get_color_regions_using_track_color()) {
+ return UIConfiguration::instance().color_mod ("midi frame base", mod_name);
+ }
+ return UIConfiguration::instance().color_mod (fill_color, mod_name);
+}
+
+void
+MidiRegionView::midi_channel_mode_changed ()
+{
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ uint16_t mask = mtv->midi_track()->get_playback_channel_mask();
+ ChannelMode mode = mtv->midi_track()->get_playback_channel_mode ();
+
+ if (mode == ForceChannel) {
+ mask = 0xFFFF; // Show all notes as active (below)
+ }
+
+ // Update notes for selection
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ i->second->on_channel_selection_change (mask);
+ }
+
+ _patch_changes.clear ();
+ display_patch_changes ();
+}
+
+void
+MidiRegionView::instrument_settings_changed ()
+{
+ redisplay_model();
+}
+
+void
+MidiRegionView::cut_copy_clear (Editing::CutCopyOp op)
+{
+ if (_selection.empty()) {
+ return;
+ }
+
+ PublicEditor& editor (trackview.editor());
+
+ switch (op) {
+ case Delete:
+ /* XXX what to do ? */
+ break;
+ case Cut:
+ case Copy:
+ editor.get_cut_buffer().add (selection_as_cut_buffer());
+ break;
+ default:
+ break;
+ }
+
+ if (op != Copy) {
+
+ start_note_diff_command();
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ switch (op) {
+ case Copy:
+ break;
+ case Delete:
+ case Cut:
+ case Clear:
+ note_diff_remove_note (*i);
+ break;
+ }
+ }
+
+ apply_diff();
+ }
+}
+
+MidiCutBuffer*
+MidiRegionView::selection_as_cut_buffer () const
+{
+ Notes notes;
+
+ for (Selection::const_iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ NoteType* n = (*i)->note().get();
+ notes.insert (boost::shared_ptr<NoteType> (new NoteType (*n)));
+ }
+
+ MidiCutBuffer* cb = new MidiCutBuffer (trackview.session());
+ cb->set (notes);
+
+ return cb;
+}
+
+/** This method handles undo */
+bool
+MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx, const int32_t sub_num)
+{
+ bool commit = false;
+ // Paste notes, if available
+ MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
+ if (m != selection.midi_notes.end()) {
+ ctx.counts.increase_n_notes();
+ if (!(*m)->empty()) {
+ commit = true;
+ }
+ paste_internal(pos, ctx.count, ctx.times, **m);
+ }
+
+ // Paste control points to automation children, if available
+ typedef RouteTimeAxisView::AutomationTracks ATracks;
+ const ATracks& atracks = midi_view()->automation_tracks();
+ for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
+ if (a->second->paste(pos, selection, ctx, sub_num)) {
+ if(!commit) {
+ trackview.editor().begin_reversible_command (Operations::paste);
+ }
+ commit = true;
+ }
+ }
+
+ if (commit) {
+ trackview.editor().commit_reversible_command ();
+ }
+ return true;
+}
+
+/** This method handles undo */
+void
+MidiRegionView::paste_internal (framepos_t pos, unsigned paste_count, float times, const MidiCutBuffer& mcb)
+{
+ if (mcb.empty()) {
+ return;
+ }
+
+ start_note_diff_command (_("paste"));
+
+ const Evoral::Beats snap_beats = get_grid_beats(pos);
+ const Evoral::Beats first_time = (*mcb.notes().begin())->time();
+ const Evoral::Beats last_time = (*mcb.notes().rbegin())->end_time();
+ const Evoral::Beats duration = last_time - first_time;
+ const Evoral::Beats snap_duration = duration.snap_to(snap_beats);
+ const Evoral::Beats paste_offset = snap_duration * paste_count;
+ const Evoral::Beats quarter_note = absolute_frames_to_source_beats(pos) + paste_offset;
+ Evoral::Beats end_point = Evoral::Beats();
+
+ DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste data spans from %1 to %2 (%3) ; paste pos beats = %4 (based on %5 - %6)\n",
+ first_time,
+ last_time,
+ duration, pos, _region->position(),
+ quarter_note));
+
+ clear_editor_note_selection ();
+
+ for (int n = 0; n < (int) times; ++n) {
+
+ for (Notes::const_iterator i = mcb.notes().begin(); i != mcb.notes().end(); ++i) {
+
+ boost::shared_ptr<NoteType> copied_note (new NoteType (*((*i).get())));
+ copied_note->set_time (quarter_note + copied_note->time() - first_time);
+ copied_note->set_id (Evoral::next_event_id());
+
+ /* make all newly added notes selected */
+
+ note_diff_add_note (copied_note, true);
+ end_point = copied_note->end_time();
+ }
+ }
+
+ /* if we pasted past the current end of the region, extend the region */
+
+ framepos_t end_frame = source_beats_to_absolute_frames (end_point);
+ framepos_t region_end = _region->position() + _region->length() - 1;
+
+ if (end_frame > region_end) {
+
+ DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("Paste extended region from %1 to %2\n", region_end, end_frame));
+
+ _region->clear_changes ();
+ /* we probably need to get the snap modifier somehow to make this correct for non-musical use */
+ _region->set_length (end_frame - _region->position(), trackview.editor().get_grid_music_divisions (0));
+ trackview.session()->add_command (new StatefulDiffCommand (_region));
+ }
+
+ apply_diff (true);
+}
+
+struct EventNoteTimeEarlyFirstComparator {
+ bool operator() (NoteBase* a, NoteBase* b) {
+ return a->note()->time() < b->note()->time();
+ }
+};
+
+void
+MidiRegionView::goto_next_note (bool add_to_selection)
+{
+ bool use_next = false;
+
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask();
+ NoteBase* first_note = 0;
+
+ MidiModel::ReadLock lock(_model->read_lock());
+ MidiModel::Notes& notes (_model->notes());
+
+ for (MidiModel::Notes::iterator n = notes.begin(); n != notes.end(); ++n) {
+ NoteBase* cne = 0;
+ if ((cne = find_canvas_note (*n))) {
+
+ if (!first_note && (channel_mask & (1 << (*n)->channel()))) {
+ first_note = cne;
+ }
+
+ if (cne->selected()) {
+ use_next = true;
+ continue;
+ } else if (use_next) {
+ if (channel_mask & (1 << (*n)->channel())) {
+ if (!add_to_selection) {
+ unique_select (cne);
+ } else {
+ note_selected (cne, true, false);
+ }
+
+ return;
+ }
+ }
+ }
+ }
+
+ /* use the first one */
+
+ if (!_events.empty() && first_note) {
+ unique_select (first_note);
+ }
+}
+
+void
+MidiRegionView::goto_previous_note (bool add_to_selection)
+{
+ bool use_next = false;
+
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ uint16_t const channel_mask = mtv->midi_track()->get_playback_channel_mask ();
+ NoteBase* last_note = 0;
+
+ MidiModel::ReadLock lock(_model->read_lock());
+ MidiModel::Notes& notes (_model->notes());
+
+ for (MidiModel::Notes::reverse_iterator n = notes.rbegin(); n != notes.rend(); ++n) {
+ NoteBase* cne = 0;
+ if ((cne = find_canvas_note (*n))) {
+
+ if (!last_note && (channel_mask & (1 << (*n)->channel()))) {
+ last_note = cne;
+ }
+
+ if (cne->selected()) {
+ use_next = true;
+ continue;
+
+ } else if (use_next) {
+ if (channel_mask & (1 << (*n)->channel())) {
+ if (!add_to_selection) {
+ unique_select (cne);
+ } else {
+ note_selected (cne, true, false);
+ }
+
+ return;
+ }
+ }
+ }
+ }
+
+ /* use the last one */
+
+ if (!_events.empty() && last_note) {
+ unique_select (last_note);
+ }
+}
+
+void
+MidiRegionView::selection_as_notelist (Notes& selected, bool allow_all_if_none_selected)
+{
+ bool had_selected = false;
+
+ /* we previously time sorted events here, but Notes is a multiset sorted by time */
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ if (i->second->selected()) {
+ selected.insert (i->first);
+ had_selected = true;
+ }
+ }
+
+ if (allow_all_if_none_selected && !had_selected) {
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ selected.insert (i->first);
+ }
+ }
+}
+
+void
+MidiRegionView::update_ghost_note (double x, double y, uint32_t state)
+{
+ x = std::max(0.0, x);
+
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+
+ _last_ghost_x = x;
+ _last_ghost_y = y;
+
+ _note_group->canvas_to_item (x, y);
+
+ PublicEditor& editor = trackview.editor ();
+
+ framepos_t const unsnapped_frame = editor.pixel_to_sample (x);
+
+ const int32_t divisions = editor.get_grid_music_divisions (state);
+ const bool shift_snap = midi_view()->note_mode() != Percussive;
+ const Evoral::Beats snapped_beats = snap_frame_to_grid_underneath (unsnapped_frame, divisions, shift_snap);
+
+ /* prevent Percussive mode from displaying a ghost hit at region end */
+ if (!shift_snap && snapped_beats >= midi_region()->start_beats() + midi_region()->length_beats()) {
+ _ghost_note->hide();
+ hide_verbose_cursor ();
+ return;
+ }
+
+ /* ghost note may have been snapped before region */
+ if (_ghost_note && snapped_beats.to_double() < 0.0) {
+ _ghost_note->hide();
+ return;
+
+ } else if (_ghost_note) {
+ _ghost_note->show();
+ }
+
+ /* calculate time in beats relative to start of source */
+ const Evoral::Beats length = get_grid_beats(unsnapped_frame + _region->position());
+
+ _ghost_note->note()->set_time (snapped_beats);
+ _ghost_note->note()->set_length (length);
+ _ghost_note->note()->set_note (y_to_note (y));
+ _ghost_note->note()->set_channel (mtv->get_channel_for_add ());
+ _ghost_note->note()->set_velocity (get_velocity_for_add (snapped_beats));
+ /* the ghost note does not appear in ghost regions, so pass false in here */
+ update_note (_ghost_note, false);
+
+ show_verbose_cursor (_ghost_note->note ());
+}
+
+void
+MidiRegionView::create_ghost_note (double x, double y, uint32_t state)
+{
+ remove_ghost_note ();
+
+ boost::shared_ptr<NoteType> g (new NoteType);
+ if (midi_view()->note_mode() == Sustained) {
+ _ghost_note = new Note (*this, _note_group, g);
+ } else {
+ _ghost_note = new Hit (*this, _note_group, 10, g);
+ }
+ _ghost_note->set_ignore_events (true);
+ _ghost_note->set_outline_color (0x000000aa);
+ update_ghost_note (x, y, state);
+ _ghost_note->show ();
+
+ show_verbose_cursor (_ghost_note->note ());
+}
+
+void
+MidiRegionView::remove_ghost_note ()
+{
+ delete _ghost_note;
+ _ghost_note = 0;
+}
+
+void
+MidiRegionView::hide_verbose_cursor ()
+{
+ trackview.editor().verbose_cursor()->hide ();
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ if (mtv) {
+ mtv->set_note_highlight (NO_MIDI_NOTE);
+ }
+}
+
+void
+MidiRegionView::snap_changed ()
+{
+ if (!_ghost_note) {
+ return;
+ }
+
+ create_ghost_note (_last_ghost_x, _last_ghost_y, 0);
+}
+
+void
+MidiRegionView::drop_down_keys ()
+{
+ _mouse_state = None;
+}
+
+void
+MidiRegionView::maybe_select_by_position (GdkEventButton* ev, double /*x*/, double y)
+{
+ /* XXX: This is dead code. What was it for? */
+
+ double note = y_to_note(y);
+ Events e;
+ MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+
+ uint16_t chn_mask = mtv->midi_track()->get_playback_channel_mask();
+
+ if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
+ get_events (e, Evoral::Sequence<Evoral::Beats>::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask);
+ } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
+ get_events (e, Evoral::Sequence<Evoral::Beats>::PitchLessThanOrEqual, (uint8_t) floor (note), chn_mask);
+ } else {
+ return;
+ }
+
+ bool add_mrv_selection = false;
+
+ if (_selection.empty()) {
+ add_mrv_selection = true;
+ }
+
+ for (Events::iterator i = e.begin(); i != e.end(); ++i) {
+ if (_selection.insert (i->second).second) {
+ i->second->set_selected (true);
+ }
+ }
+
+ if (add_mrv_selection) {
+ PublicEditor& editor (trackview.editor());
+ editor.get_selection().add (this);
+ }
+}
+
+void
+MidiRegionView::color_handler ()
+{
+ RegionView::color_handler ();
+
+ _patch_change_outline = UIConfiguration::instance().color ("midi patch change outline");
+ _patch_change_fill = UIConfiguration::instance().color_mod ("midi patch change fill", "midi patch change fill");
+
+ for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
+ i->second->set_selected (i->second->selected()); // will change color
+ }
+
+ /* XXX probably more to do here */
+}
+
+void
+MidiRegionView::enable_display (bool yn)
+{
+ RegionView::enable_display (yn);
+}
+
+void
+MidiRegionView::show_step_edit_cursor (Evoral::Beats pos)
+{
+ if (_step_edit_cursor == 0) {
+ ArdourCanvas::Item* const group = get_canvas_group();
+
+ _step_edit_cursor = new ArdourCanvas::Rectangle (group);
+ _step_edit_cursor->set_y0 (0);
+ _step_edit_cursor->set_y1 (midi_stream_view()->contents_height());
+ _step_edit_cursor->set_fill_color (RGBA_TO_UINT (45,0,0,90));
+ _step_edit_cursor->set_outline_color (RGBA_TO_UINT (85,0,0,90));
+ }
+
+ move_step_edit_cursor (pos);
+ _step_edit_cursor->show ();
+}
+
+void
+MidiRegionView::move_step_edit_cursor (Evoral::Beats pos)
+{
+ _step_edit_cursor_position = pos;
+
+ if (_step_edit_cursor) {
+ double pixel = trackview.editor().sample_to_pixel (region_beats_to_region_frames (pos));
+ _step_edit_cursor->set_x0 (pixel);
+ set_step_edit_cursor_width (_step_edit_cursor_width);
+ }
+}
+
+void
+MidiRegionView::hide_step_edit_cursor ()
+{
+ if (_step_edit_cursor) {
+ _step_edit_cursor->hide ();
+ }
+}
+
+void
+MidiRegionView::set_step_edit_cursor_width (Evoral::Beats beats)
+{
+ _step_edit_cursor_width = beats;
+
+ if (_step_edit_cursor) {
+ _step_edit_cursor->set_x1 (_step_edit_cursor->x0() + trackview.editor().sample_to_pixel (
+ region_beats_to_region_frames (_step_edit_cursor_position + beats)
+ - region_beats_to_region_frames (_step_edit_cursor_position)));
+ }
+}
+
+/** Called when a diskstream on our track has received some data. Update the view, if applicable.
+ * @param w Source that the data will end up in.
+ */
+void
+MidiRegionView::data_recorded (boost::weak_ptr<MidiSource> w)
+{
+ if (!_active_notes) {
+ /* we aren't actively being recorded to */
+ return;
+ }
+
+ boost::shared_ptr<MidiSource> src = w.lock ();
+ if (!src || src != midi_region()->midi_source()) {
+ /* recorded data was not destined for our source */
+ return;
+ }
+
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (&trackview);
+
+ boost::shared_ptr<MidiBuffer> buf = mtv->midi_track()->get_gui_feed_buffer ();
+
+ framepos_t back = max_framepos;
+
+ for (MidiBuffer::iterator i = buf->begin(); i != buf->end(); ++i) {
+ const Evoral::Event<MidiBuffer::TimeType>& ev = *i;
+
+ if (ev.is_channel_event()) {
+ if (get_channel_mode() == FilterChannels) {
+ if (((uint16_t(1) << ev.channel()) & get_selected_channels()) == 0) {
+ continue;
+ }
+ }
+ }
+
+ /* convert from session frames to source beats */
+ Evoral::Beats const time_beats = _source_relative_time_converter.from(
+ ev.time() - src->timeline_position() + _region->start());
+
+ if (ev.type() == MIDI_CMD_NOTE_ON) {
+ boost::shared_ptr<NoteType> note (
+ new NoteType (ev.channel(), time_beats, Evoral::Beats(), ev.note(), ev.velocity()));
+
+ add_note (note, true);
+
+ /* fix up our note range */
+ if (ev.note() < _current_range_min) {
+ midi_stream_view()->apply_note_range (ev.note(), _current_range_max, true);
+ } else if (ev.note() > _current_range_max) {
+ midi_stream_view()->apply_note_range (_current_range_min, ev.note(), true);
+ }
+
+ } else if (ev.type() == MIDI_CMD_NOTE_OFF) {
+ resolve_note (ev.note (), time_beats);
+ }
+
+ back = ev.time ();
+ }
+
+ midi_stream_view()->check_record_layers (region(), back);
+}
+
+void
+MidiRegionView::trim_front_starting ()
+{
+ /* We used to eparent the note group to the region view's parent, so that it didn't change.
+ now we update it.
+ */
+}
+
+void
+MidiRegionView::trim_front_ending ()
+{
+ if (_region->start() < 0) {
+ /* Trim drag made start time -ve; fix this */
+ midi_region()->fix_negative_start ();
+ }
+}
+
+void
+MidiRegionView::edit_patch_change (PatchChange* pc)
+{
+ PatchChangeDialog d (&_source_relative_time_converter, trackview.session(), *pc->patch (), instrument_info(), Gtk::Stock::APPLY, true);
+
+ int response = d.run();
+
+ switch (response) {
+ case Gtk::RESPONSE_ACCEPT:
+ break;
+ case Gtk::RESPONSE_REJECT:
+ delete_patch_change (pc);
+ return;
+ default:
+ return;
+ }
+
+ change_patch_change (pc->patch(), d.patch ());
+}
+
+void
+MidiRegionView::delete_sysex (SysEx* /*sysex*/)
+{
+ // CAIROCANVAS
+ // sysyex object doesn't have a pointer to a sysex event
+ // MidiModel::SysExDiffCommand* c = _model->new_sysex_diff_command (_("delete sysex"));
+ // c->remove (sysex->sysex());
+ // _model->apply_command (*trackview.session(), c);
+
+ //_sys_exes.clear ();
+ // display_sysexes();
+}
+
+std::string
+MidiRegionView::get_note_name (boost::shared_ptr<NoteType> n, uint8_t note_value) const
+{
+ using namespace MIDI::Name;
+ std::string name;
+
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ if (mtv) {
+ boost::shared_ptr<MasterDeviceNames> device_names(mtv->get_device_names());
+ if (device_names) {
+ MIDI::Name::PatchPrimaryKey patch_key;
+ get_patch_key_at(n->time(), n->channel(), patch_key);
+ name = device_names->note_name(mtv->gui_property(X_("midnam-custom-device-mode")),
+ n->channel(),
+ patch_key.bank(),
+ patch_key.program(),
+ note_value);
+ }
+ }
+
+ char buf[128];
+ snprintf (buf, sizeof (buf), "%d %s\nCh %d Vel %d",
+ (int) note_value,
+ name.empty() ? ParameterDescriptor::midi_note_name (note_value).c_str() : name.c_str(),
+ (int) n->channel() + 1,
+ (int) n->velocity());
+
+ return buf;
+}
+
+void
+MidiRegionView::show_verbose_cursor_for_new_note_value(boost::shared_ptr<NoteType> current_note,
+ uint8_t new_value) const
+{
+ MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+ if (mtv) {
+ mtv->set_note_highlight (new_value);
+ }
+
+ show_verbose_cursor(get_note_name(current_note, new_value), 10, 20);
+}
+
+void
+MidiRegionView::show_verbose_cursor (boost::shared_ptr<NoteType> n) const
+{
+ show_verbose_cursor_for_new_note_value(n, n->note());
+}
+
+void
+MidiRegionView::show_verbose_cursor (string const & text, double xoffset, double yoffset) const
+{
+ trackview.editor().verbose_cursor()->set (text);
+ trackview.editor().verbose_cursor()->show ();
+ trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (xoffset, yoffset));
+}
+
+uint8_t
+MidiRegionView::get_velocity_for_add (MidiModel::TimeType time) const
+{
+ if (_model->notes().empty()) {
+ return 0x40; // No notes, use default
+ }
+
+ MidiModel::Notes::const_iterator m = _model->note_lower_bound(time);
+ if (m == _model->notes().begin()) {
+ // Before the start, use the velocity of the first note
+ return (*m)->velocity();
+ } else if (m == _model->notes().end()) {
+ // Past the end, use the velocity of the last note
+ --m;
+ return (*m)->velocity();
+ }
+
+ // Interpolate velocity of surrounding notes
+ MidiModel::Notes::const_iterator n = m;
+ --n;
+
+ const double frac = ((time - (*n)->time()).to_double() /
+ ((*m)->time() - (*n)->time()).to_double());
+
+ return (*n)->velocity() + (frac * ((*m)->velocity() - (*n)->velocity()));
+}
+
+/** @param p A session framepos.
+ * @param divisions beat division to snap given by Editor::get_grid_music_divisions() where
+ * bar is -1, 0 is audio samples and a positive integer is beat subdivisions.
+ * @return beat duration of p snapped to the grid subdivision underneath it.
+ */
+Evoral::Beats
+MidiRegionView::snap_frame_to_grid_underneath (framepos_t p, int32_t divisions, bool shift_snap) const
+{
+ TempoMap& map (trackview.session()->tempo_map());
+ double eqaf = map.exact_qn_at_frame (p + _region->position(), divisions);
+
+ if (divisions != 0 && shift_snap) {
+ const double qaf = map.quarter_note_at_frame (p + _region->position());
+ /* Hack so that we always snap to the note that we are over, instead of snapping
+ to the next one if we're more than halfway through the one we're over.
+ */
+ const Evoral::Beats grid_beats = get_grid_beats (p + _region->position());
+ const double rem = eqaf - qaf;
+ if (rem >= 0.0) {
+ eqaf -= grid_beats.to_double();
+ }
+ }
+ const double session_start_off = _region->quarter_note() - midi_region()->start_beats();
+
+ return Evoral::Beats (eqaf - session_start_off);
+}
+
+ChannelMode
+MidiRegionView::get_channel_mode () const
+{
+ RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
+ return rtav->midi_track()->get_playback_channel_mode();
+}
+
+uint16_t
+MidiRegionView::get_selected_channels () const
+{
+ RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (&trackview);
+ return rtav->midi_track()->get_playback_channel_mask();
+}
+
+
+Evoral::Beats
+MidiRegionView::get_grid_beats(framepos_t pos) const
+{
+ PublicEditor& editor = trackview.editor();
+ bool success = false;
+ Evoral::Beats beats = editor.get_grid_type_as_beats (success, pos);
+ if (!success) {
+ beats = Evoral::Beats(1);
+ }
+ return beats;
+}
+uint8_t
+MidiRegionView::y_to_note (double y) const
+{
+ int const n = ((contents_height() - y) / contents_height() * (double)(_current_range_max - _current_range_min + 1))
+ + _current_range_min;
+
+ if (n < 0) {
+ return 0;
+ } else if (n > 127) {
+ return 127;
+ }
+
+ /* min due to rounding and/or off-by-one errors */
+ return min ((uint8_t) n, _current_range_max);
+}
+
+double
+MidiRegionView::note_to_y(uint8_t note) const
+{
+ return contents_height() - (note + 1 - _current_range_min) * note_height() + 1;
+}
+
+double
+MidiRegionView::session_relative_qn (double qn) const
+{
+ return qn + (region()->quarter_note() - midi_region()->start_beats());
+}
diff --git a/gtk2_ardour/midi_region_view.cc.rej b/gtk2_ardour/midi_region_view.cc.rej
new file mode 100644
index 0000000000..b0243d0e3e
--- /dev/null
+++ b/gtk2_ardour/midi_region_view.cc.rej
@@ -0,0 +1,11 @@
+--- gtk2_ardour/midi_region_view.cc
++++ gtk2_ardour/midi_region_view.cc
+@@ -2516,7 +2516,7 @@ MidiRegionView::move_selection(double dx, double dy, double cumulative_dy)
+ {
+ typedef vector<boost::shared_ptr<NoteType> > PossibleChord;
+ PossibleChord to_play;
+- Evoral::Beats earliest = Evoral::MaxBeats;
++ Evoral::Beats earliest = std::numeric_limits<Evoral::Beats>::max();
+
+ for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ if ((*i)->note()->time() < earliest) {
diff --git a/gtk2_ardour/out b/gtk2_ardour/out
new file mode 100644
index 0000000000..f0ee94df84
--- /dev/null
+++ b/gtk2_ardour/out
@@ -0,0 +1,5182 @@
+bind txt domain [gtk2_ardour5] to /usr/local/share/ardour5/locale
+Ardour5.11.143 (built using 5.11-167-g0abfd70d0 and GCC version 6.3.0 20170516)
+ardour: [INFO]: Your system is configured to limit Ardour to only 1048576 open files
+ardour: [INFO]: Loading system configuration file /usr/local/music/src/ardour/sd/system_config
+ardour: [INFO]: Loading user configuration file /home/paul/.config/ardour5/config
+ardour: [INFO]: CPU vendor: GenuineIntel
+ardour: [INFO]: AVX-capable processor
+ardour: [INFO]: CPU brand: Intel(R) Core(TM) i7-3770S CPU @ 3.10GHz
+ardour: [INFO]: Using SSE optimized routines
+ardour: [INFO]: Loading default ui configuration file /usr/local/music/src/ardour/sd/build/gtk2_ardour/default_ui_config
+ardour: [INFO]: Loading user ui configuration file /home/paul/.config/ardour5/ui_config
+Color shuttle bg not found
+ardour: [INFO]: Loading color file /usr/local/music/src/ardour/sd/gtk2_ardour/themes/dark-ardour.colors
+ardour: [INFO]: Loading ui configuration file /usr/local/music/src/ardour/sd/build/gtk2_ardour/clearlooks.rc
+ardour: [INFO]: Loading ui configuration file /usr/local/music/src/ardour/sd/build/gtk2_ardour/clearlooks.rc
+Found 1 along /home/paul/.config/ardour5/templates:./../templates:./../build/templates:./../gtk2_ardour/templates:./../build/gtk2_ardour/templates:./templates
+protocol Open Sound Control (OSC) active ? 0
+protocol PreSonus FaderPort8 active ? 0
+protocol PreSonus FaderPort active ? 0
+protocol Generic MIDI active ? 1
+protocol Mackie active ? 1
+protocol Wiimote active ? 0
+protocol Ableton Push 2 active ? 0
+Scanning folders for bundled LV2s: ./../build/libs/LV2
+error: failed to expand CURIE `foaf:name'
+error: /usr/local/lib/lv2/newtonator.lv2/newt_lv2_instr.ttl:15:31: expected `]', not `;'
+lilv_world_load_file(): error: Error loading file `file:///usr/local/lib/lv2/newtonator.lv2/newt_lv2_instr.ttl'
+error: failed to expand CURIE `pprop:notOnGUI'
+error: /usr/local/lib/lv2/qmidiarp_lfo.lv2/qmidiarp_lfo.ttl:289:40: expected `]', not `;'
+lilv_world_load_file(): error: Error loading file `file:///usr/local/lib/lv2/qmidiarp_lfo.lv2/qmidiarp_lfo.ttl'
+lilv_plugin_get_unique(): error: No value found for (458b76 http://lv2plug.in/ns/lv2core#symbol ...) property
+lilv_plugin_load_ports_if_necessary(): error: Plugin <https://git.code.sf.net/p/qmidiarp/lfo> port symbol `(null)' is invalid
+ROOT recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ROOT recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+verbose canvas cursor not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+verbose canvas cursor recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+loop rect recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.5, 1.7e+307) 3 x 1.7e+307]
+ now2 [(-1.5, -1.5), (1.5, 1.7e+307) 3 x 1.7e+307]
+punch rect recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.5, 1.7e+307) 3 x 1.7e+307]
+ now2 [(-1.5, -1.5), (1.5, 1.7e+307) 3 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+meter Bar recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker drag recomputing BBOX before returning it
+ now [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ now2 [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+cd marker drag recomputing BBOX before returning it
+ now [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ now2 [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+cd marker drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+range drag recomputing BBOX before returning it
+ now [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ now2 [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+range drag recomputing BBOX before returning it
+ now [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ now2 [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+range drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport drag recomputing BBOX before returning it
+ now [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ now2 [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport drag recomputing BBOX before returning it
+ now [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ now2 [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+ recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+ recomputing BBOX before returning it
+ now [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ now2 [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+ recomputing BBOX before returning it
+ now [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ now2 [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+ now2 [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+ now2 [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (3, 0) 3 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (3, 0) 3 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+track canvas editor cursor not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow head 0 recomputing BBOX before returning it
+ now [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+ now2 [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (3, 0) 3 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+child bbox was [(-1.5, -1.5), (5.5, 10.5) 7 x 12] in parent space [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+track canvas editor cursor not dirty, bbox = [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (5.5, 10.5) 7 x 12]
+arrow head 0 recomputing BBOX before returning it
+ now [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+ now2 [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (9, 0) 9 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+child bbox was [(-1.5, -1.5), (17.5, 10.5) 19 x 12] in parent space [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+track canvas editor cursor not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 recomputing BBOX before returning it
+ now [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+ now2 [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (9, 0) 9 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+child bbox was [(-1.5, -1.5), (17.5, 10.5) 19 x 12] in parent space [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+child bbox was [(-1.5, -1.5), (5.5, 5.5) 7 x 7] in parent space [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 1 not dirty, bbox = [(-1.5, -1.5), (5.5, 5.5) 7 x 7]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+track canvas editor cursor not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (9, 0) 9 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+child bbox was [(-1.5, -1.5), (17.5, 10.5) 19 x 12] in parent space [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (9, 0) 9 x 0]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+child bbox was [(-1.5, -1.5), (17.5, 10.5) 19 x 12] in parent space [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+child bbox was [(-1, -1), (1, 1) 2 x 2] in parent space [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow line not dirty, bbox = [(-1, -1), (1, 1) 2 x 2]
+arrow line recomputing BBOX before returning it
+ now [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ now2 [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ROOT recomputing BBOX before returning it
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+canvas h scroll recomputing BBOX before returning it
+time line group recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+time bars recomputing BBOX before returning it
+cd marker group recomputing BBOX before returning it
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+marker group recomputing BBOX before returning it
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 14.5), (1.7e+307, 32.5) 1.7e+307 x 18]
+transport marker group recomputing BBOX before returning it
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 29.5), (1.7e+307, 47.5) 1.7e+307 x 18]
+range marker group recomputing BBOX before returning it
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 44.5), (1.7e+307, 62.5) 1.7e+307 x 18]
+tempo group recomputing BBOX before returning it
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 59.5), (1.7e+307, 77.5) 1.7e+307 x 18]
+meter group recomputing BBOX before returning it
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 74.5), (1.7e+307, 92.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 14.5), (1.7e+307, 32.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 29.5), (1.7e+307, 47.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 44.5), (1.7e+307, 62.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 59.5), (1.7e+307, 77.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 74.5), (1.7e+307, 92.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ now2 [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+ now [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+time line group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+time bars not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+ now2 [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll recomputing BBOX before returning it
+global rect group recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3] in parent space [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3] in parent space [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3] in parent space [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+canvas cursor scroll recomputing BBOX before returning it
+track canvas editor cursor recomputing BBOX before returning it
+ now [(0, 0), (9, 1.7e+307) 9 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+child bbox was [(-1.5, -1.5), (17.5, 10.5) 19 x 12] in parent space [(-9.5, -1.5), (9.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+child bbox was [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307] in parent space [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ now2 [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3] in parent space [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+Set cursor set to default
+ recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+ recomputing BBOX before returning it
+ now [(-1, 0), (101, 2) 102 x 2]
+ now2 [(-1, 0), (101, 2) 102 x 2]
+ not dirty, bbox = [(-1, 0), (101, 2) 102 x 2]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ recomputing BBOX before returning it
+ now [(-1.5, -4.5), (1.7e+307, 1.5) 1.7e+307 x 6]
+ now2 [(-1.5, -4.5), (1.7e+307, 1.5) 1.7e+307 x 6]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(18.5, -1.5), (41.5, 21.5) 23 x 23]
+ now2 [(18.5, -1.5), (41.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(38.5, -1.5), (61.5, 21.5) 23 x 23]
+ now2 [(38.5, -1.5), (61.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(58.5, -1.5), (81.5, 21.5) 23 x 23]
+ now2 [(58.5, -1.5), (81.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(78.5, -1.5), (101.5, 21.5) 23 x 23]
+ now2 [(78.5, -1.5), (101.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(98.5, -1.5), (121.5, 21.5) 23 x 23]
+ now2 [(98.5, -1.5), (121.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(118.5, -1.5), (141.5, 21.5) 23 x 23]
+ now2 [(118.5, -1.5), (141.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(138.5, -1.5), (161.5, 21.5) 23 x 23]
+ now2 [(138.5, -1.5), (161.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(158.5, -1.5), (181.5, 21.5) 23 x 23]
+ now2 [(158.5, -1.5), (181.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(178.5, -1.5), (201.5, 21.5) 23 x 23]
+ now2 [(178.5, -1.5), (201.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(198.5, -1.5), (221.5, 21.5) 23 x 23]
+ now2 [(198.5, -1.5), (221.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(218.5, -1.5), (241.5, 21.5) 23 x 23]
+ now2 [(218.5, -1.5), (241.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(238.5, -1.5), (261.5, 21.5) 23 x 23]
+ now2 [(238.5, -1.5), (261.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(258.5, -1.5), (281.5, 21.5) 23 x 23]
+ now2 [(258.5, -1.5), (281.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(278.5, -1.5), (301.5, 21.5) 23 x 23]
+ now2 [(278.5, -1.5), (301.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(298.5, -1.5), (321.5, 21.5) 23 x 23]
+ now2 [(298.5, -1.5), (321.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(318.5, -1.5), (341.5, 21.5) 23 x 23]
+ now2 [(318.5, -1.5), (341.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(338.5, -1.5), (361.5, 21.5) 23 x 23]
+ now2 [(338.5, -1.5), (361.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(358.5, -1.5), (381.5, 21.5) 23 x 23]
+ now2 [(358.5, -1.5), (381.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(378.5, -1.5), (401.5, 21.5) 23 x 23]
+ now2 [(378.5, -1.5), (401.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(398.5, -1.5), (421.5, 21.5) 23 x 23]
+ now2 [(398.5, -1.5), (421.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(418.5, -1.5), (441.5, 21.5) 23 x 23]
+ now2 [(418.5, -1.5), (441.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(438.5, -1.5), (461.5, 21.5) 23 x 23]
+ now2 [(438.5, -1.5), (461.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(458.5, -1.5), (481.5, 21.5) 23 x 23]
+ now2 [(458.5, -1.5), (481.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(478.5, -1.5), (501.5, 21.5) 23 x 23]
+ now2 [(478.5, -1.5), (501.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(498.5, -1.5), (521.5, 21.5) 23 x 23]
+ now2 [(498.5, -1.5), (521.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(518.5, -1.5), (541.5, 21.5) 23 x 23]
+ now2 [(518.5, -1.5), (541.5, 21.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(-1.5, 18.5), (21.5, 41.5) 23 x 23]
+ now2 [(-1.5, 18.5), (21.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(18.5, 18.5), (41.5, 41.5) 23 x 23]
+ now2 [(18.5, 18.5), (41.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(38.5, 18.5), (61.5, 41.5) 23 x 23]
+ now2 [(38.5, 18.5), (61.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(58.5, 18.5), (81.5, 41.5) 23 x 23]
+ now2 [(58.5, 18.5), (81.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(78.5, 18.5), (101.5, 41.5) 23 x 23]
+ now2 [(78.5, 18.5), (101.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(98.5, 18.5), (121.5, 41.5) 23 x 23]
+ now2 [(98.5, 18.5), (121.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(118.5, 18.5), (141.5, 41.5) 23 x 23]
+ now2 [(118.5, 18.5), (141.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(138.5, 18.5), (161.5, 41.5) 23 x 23]
+ now2 [(138.5, 18.5), (161.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(158.5, 18.5), (181.5, 41.5) 23 x 23]
+ now2 [(158.5, 18.5), (181.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(178.5, 18.5), (201.5, 41.5) 23 x 23]
+ now2 [(178.5, 18.5), (201.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(198.5, 18.5), (221.5, 41.5) 23 x 23]
+ now2 [(198.5, 18.5), (221.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(218.5, 18.5), (241.5, 41.5) 23 x 23]
+ now2 [(218.5, 18.5), (241.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(238.5, 18.5), (261.5, 41.5) 23 x 23]
+ now2 [(238.5, 18.5), (261.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(258.5, 18.5), (281.5, 41.5) 23 x 23]
+ now2 [(258.5, 18.5), (281.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(278.5, 18.5), (301.5, 41.5) 23 x 23]
+ now2 [(278.5, 18.5), (301.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(298.5, 18.5), (321.5, 41.5) 23 x 23]
+ now2 [(298.5, 18.5), (321.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(318.5, 18.5), (341.5, 41.5) 23 x 23]
+ now2 [(318.5, 18.5), (341.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(338.5, 18.5), (361.5, 41.5) 23 x 23]
+ now2 [(338.5, 18.5), (361.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(358.5, 18.5), (381.5, 41.5) 23 x 23]
+ now2 [(358.5, 18.5), (381.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(378.5, 18.5), (401.5, 41.5) 23 x 23]
+ now2 [(378.5, 18.5), (401.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(398.5, 18.5), (421.5, 41.5) 23 x 23]
+ now2 [(398.5, 18.5), (421.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(418.5, 18.5), (441.5, 41.5) 23 x 23]
+ now2 [(418.5, 18.5), (441.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(438.5, 18.5), (461.5, 41.5) 23 x 23]
+ now2 [(438.5, 18.5), (461.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(458.5, 18.5), (481.5, 41.5) 23 x 23]
+ now2 [(458.5, 18.5), (481.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(478.5, 18.5), (501.5, 41.5) 23 x 23]
+ now2 [(478.5, 18.5), (501.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(498.5, 18.5), (521.5, 41.5) 23 x 23]
+ now2 [(498.5, 18.5), (521.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(518.5, 18.5), (541.5, 41.5) 23 x 23]
+ now2 [(518.5, 18.5), (541.5, 41.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(-1.5, 38.5), (21.5, 61.5) 23 x 23]
+ now2 [(-1.5, 38.5), (21.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(18.5, 38.5), (41.5, 61.5) 23 x 23]
+ now2 [(18.5, 38.5), (41.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(38.5, 38.5), (61.5, 61.5) 23 x 23]
+ now2 [(38.5, 38.5), (61.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(58.5, 38.5), (81.5, 61.5) 23 x 23]
+ now2 [(58.5, 38.5), (81.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(78.5, 38.5), (101.5, 61.5) 23 x 23]
+ now2 [(78.5, 38.5), (101.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(98.5, 38.5), (121.5, 61.5) 23 x 23]
+ now2 [(98.5, 38.5), (121.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(118.5, 38.5), (141.5, 61.5) 23 x 23]
+ now2 [(118.5, 38.5), (141.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(138.5, 38.5), (161.5, 61.5) 23 x 23]
+ now2 [(138.5, 38.5), (161.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(158.5, 38.5), (181.5, 61.5) 23 x 23]
+ now2 [(158.5, 38.5), (181.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(178.5, 38.5), (201.5, 61.5) 23 x 23]
+ now2 [(178.5, 38.5), (201.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(198.5, 38.5), (221.5, 61.5) 23 x 23]
+ now2 [(198.5, 38.5), (221.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(218.5, 38.5), (241.5, 61.5) 23 x 23]
+ now2 [(218.5, 38.5), (241.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(238.5, 38.5), (261.5, 61.5) 23 x 23]
+ now2 [(238.5, 38.5), (261.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(258.5, 38.5), (281.5, 61.5) 23 x 23]
+ now2 [(258.5, 38.5), (281.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(278.5, 38.5), (301.5, 61.5) 23 x 23]
+ now2 [(278.5, 38.5), (301.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(298.5, 38.5), (321.5, 61.5) 23 x 23]
+ now2 [(298.5, 38.5), (321.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(318.5, 38.5), (341.5, 61.5) 23 x 23]
+ now2 [(318.5, 38.5), (341.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(338.5, 38.5), (361.5, 61.5) 23 x 23]
+ now2 [(338.5, 38.5), (361.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(358.5, 38.5), (381.5, 61.5) 23 x 23]
+ now2 [(358.5, 38.5), (381.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(378.5, 38.5), (401.5, 61.5) 23 x 23]
+ now2 [(378.5, 38.5), (401.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(398.5, 38.5), (421.5, 61.5) 23 x 23]
+ now2 [(398.5, 38.5), (421.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(418.5, 38.5), (441.5, 61.5) 23 x 23]
+ now2 [(418.5, 38.5), (441.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(438.5, 38.5), (461.5, 61.5) 23 x 23]
+ now2 [(438.5, 38.5), (461.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(458.5, 38.5), (481.5, 61.5) 23 x 23]
+ now2 [(458.5, 38.5), (481.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(478.5, 38.5), (501.5, 61.5) 23 x 23]
+ now2 [(478.5, 38.5), (501.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(498.5, 38.5), (521.5, 61.5) 23 x 23]
+ now2 [(498.5, 38.5), (521.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(518.5, 38.5), (541.5, 61.5) 23 x 23]
+ now2 [(518.5, 38.5), (541.5, 61.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(-1.5, 58.5), (21.5, 81.5) 23 x 23]
+ now2 [(-1.5, 58.5), (21.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(18.5, 58.5), (41.5, 81.5) 23 x 23]
+ now2 [(18.5, 58.5), (41.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(38.5, 58.5), (61.5, 81.5) 23 x 23]
+ now2 [(38.5, 58.5), (61.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(58.5, 58.5), (81.5, 81.5) 23 x 23]
+ now2 [(58.5, 58.5), (81.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(78.5, 58.5), (101.5, 81.5) 23 x 23]
+ now2 [(78.5, 58.5), (101.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(98.5, 58.5), (121.5, 81.5) 23 x 23]
+ now2 [(98.5, 58.5), (121.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(118.5, 58.5), (141.5, 81.5) 23 x 23]
+ now2 [(118.5, 58.5), (141.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(138.5, 58.5), (161.5, 81.5) 23 x 23]
+ now2 [(138.5, 58.5), (161.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(158.5, 58.5), (181.5, 81.5) 23 x 23]
+ now2 [(158.5, 58.5), (181.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(178.5, 58.5), (201.5, 81.5) 23 x 23]
+ now2 [(178.5, 58.5), (201.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(198.5, 58.5), (221.5, 81.5) 23 x 23]
+ now2 [(198.5, 58.5), (221.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(218.5, 58.5), (241.5, 81.5) 23 x 23]
+ now2 [(218.5, 58.5), (241.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(238.5, 58.5), (261.5, 81.5) 23 x 23]
+ now2 [(238.5, 58.5), (261.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(258.5, 58.5), (281.5, 81.5) 23 x 23]
+ now2 [(258.5, 58.5), (281.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(278.5, 58.5), (301.5, 81.5) 23 x 23]
+ now2 [(278.5, 58.5), (301.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(298.5, 58.5), (321.5, 81.5) 23 x 23]
+ now2 [(298.5, 58.5), (321.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(318.5, 58.5), (341.5, 81.5) 23 x 23]
+ now2 [(318.5, 58.5), (341.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(338.5, 58.5), (361.5, 81.5) 23 x 23]
+ now2 [(338.5, 58.5), (361.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(358.5, 58.5), (381.5, 81.5) 23 x 23]
+ now2 [(358.5, 58.5), (381.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(378.5, 58.5), (401.5, 81.5) 23 x 23]
+ now2 [(378.5, 58.5), (401.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(398.5, 58.5), (421.5, 81.5) 23 x 23]
+ now2 [(398.5, 58.5), (421.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(418.5, 58.5), (441.5, 81.5) 23 x 23]
+ now2 [(418.5, 58.5), (441.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(438.5, 58.5), (461.5, 81.5) 23 x 23]
+ now2 [(438.5, 58.5), (461.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(458.5, 58.5), (481.5, 81.5) 23 x 23]
+ now2 [(458.5, 58.5), (481.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(478.5, 58.5), (501.5, 81.5) 23 x 23]
+ now2 [(478.5, 58.5), (501.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(498.5, 58.5), (521.5, 81.5) 23 x 23]
+ now2 [(498.5, 58.5), (521.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(518.5, 58.5), (541.5, 81.5) 23 x 23]
+ now2 [(518.5, 58.5), (541.5, 81.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(-1.5, 78.5), (21.5, 101.5) 23 x 23]
+ now2 [(-1.5, 78.5), (21.5, 101.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(18.5, 78.5), (41.5, 101.5) 23 x 23]
+ now2 [(18.5, 78.5), (41.5, 101.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(38.5, 78.5), (61.5, 101.5) 23 x 23]
+ now2 [(38.5, 78.5), (61.5, 101.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(58.5, 78.5), (81.5, 101.5) 23 x 23]
+ now2 [(58.5, 78.5), (81.5, 101.5) 23 x 23]
+ recomputing BBOX before returning it
+ now [(78.5, 78.5), (101.5, 101.5) 23 x 23]
+ now2 [(78.5, 78.5), (101.5, 101.5) 23 x 23]
+ not dirty, bbox = [(-1.5, -4.5), (1.7e+307, 1.5) 1.7e+307 x 6]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 909.5) 1.7e+307 x 911]
+ now2 [(-1.5, -1.5), (1.7e+307, 909.5) 1.7e+307 x 911]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 909.5) 1.7e+307 x 911]
+ recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now2 [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll recomputing BBOX before returning it
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now2 [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+time line group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+time bars not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+time bars not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+set disk io to DiskIOPreFader
+set disk io to DiskIOPreFader
+ROOT not dirty, bbox = [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+set disk io to DiskIOPreFader
+protocol Open Sound Control (OSC) active ? 0
+protocol PreSonus FaderPort8 active ? 0
+protocol PreSonus FaderPort active ? 0
+protocol Generic MIDI active ? 1
+protocol Mackie active ? 1
+protocol Wiimote active ? 0
+protocol Ableton Push 2 active ? 0
+Skip explicit buffer seconds, preset in use
+Skip explicit buffer seconds, preset in use
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ROOT not dirty, bbox = [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle Master recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle Master recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+SV canvas rectangle Master recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Fader recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Fader recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Fader recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Trim recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Trim recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Trim recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Mute recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Mute recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Mute recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for L/R recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for L/R recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for L/R recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Width recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Width recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Width recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Fader recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Fader recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Fader recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Mute recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Mute recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Mute recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for L/R recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for L/R recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for L/R recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1, 1) 2 x 2]
+ now2 [(-1, -1), (1, 1) 2 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+ now2 [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+selection for TAV recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Width recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+base rect for Width recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+base rect for Width recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+ now2 [(-1.5, -1.5), (1.7e+307, 1.5) 1.7e+307 x 3]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+region gain envelope line not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll recomputing BBOX before returning it
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now2 [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+child bbox was [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94] in parent space [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ROOT not dirty, bbox = [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ROOT not dirty, bbox = [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas h scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 92.5) 1.7e+307 x 94]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas hv scroll recomputing BBOX before returning it
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ now2 [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (57, 13) 57 x 13]
+ now2 [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (46, 13) 46 x 13]
+ now2 [(0, 0), (46, 13) 46 x 13]
+TempoCurve::curve for 120 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+TempoCurve::curve for 120 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::mark for recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Marker::mark for recomputing BBOX before returning it
+ now [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ now2 [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::mark for not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::mark for not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::_name_background for recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for recomputing BBOX before returning it
+ now [(-4.5, -1.5), (1.5, 1.5) 6 x 3]
+ now2 [(-4.5, -1.5), (1.5, 1.5) 6 x 3]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (1.5, 1.5) 6 x 3]
+Marker::_name_background for recomputing BBOX before returning it
+ now [(-4.5, -1.5), (10.5, 1.5) 15 x 3]
+ now2 [(-4.5, -1.5), (10.5, 1.5) 15 x 3]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 1.5) 15 x 3]
+Marker::_name_background for recomputing BBOX before returning it
+ now [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+ now2 [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Marker::mark for 4/4 recomputing BBOX before returning it
+ now [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ now2 [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::_name_background for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for 4/4 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ArdourMarker::_name_item for 4/4 recomputing BBOX before returning it
+ now [(0, 0), (18, 13) 18 x 13]
+ now2 [(0, 0), (18, 13) 18 x 13]
+Marker::_name_background for 4/4 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for 4/4 recomputing BBOX before returning it
+ now [(-1.5, -1.5), (4.5, 1.5) 6 x 3]
+ now2 [(-1.5, -1.5), (4.5, 1.5) 6 x 3]
+Marker::_name_background for 4/4 not dirty, bbox = [(-1.5, -1.5), (4.5, 1.5) 6 x 3]
+Marker::_name_background for 4/4 recomputing BBOX before returning it
+ now [(1.5, -1.5), (31.5, 1.5) 30 x 3]
+ now2 [(1.5, -1.5), (31.5, 1.5) 30 x 3]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 1.5) 30 x 3]
+Marker::_name_background for 4/4 recomputing BBOX before returning it
+ now [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+ now2 [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+TempoCurve::curve for 120 not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+TempoCurve::curve for 120 recomputing BBOX before returning it
+ now [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+ now2 [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+Marker::mark for not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+Marker::mark for recomputing BBOX before returning it
+ now [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ now2 [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+canvas h scroll recomputing BBOX before returning it
+time line group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+time bars recomputing BBOX before returning it
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 88.5), (1.7e+307, 106.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 103.5), (1.7e+307, 121.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 73.5), (1.7e+307, 91.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 58.5), (1.7e+307, 76.5) 1.7e+307 x 18]
+tempo group recomputing BBOX before returning it
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 recomputing BBOX before returning it
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+child bbox was [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25] in parent space [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+child bbox was [(0, 0), (57, 13) 57 x 13] in parent space [(10, 0.5), (67, 13.5) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+child bbox was [(0, 0), (46, 13) 46 x 13] in parent space [(2.0971e+06, 0.5), (2.09714e+06, 13.5) 46 x 13]
+ now [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+child bbox was [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25] in parent space [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+child bbox was [(0, 0), (57, 13) 57 x 13] in parent space [(10, 0.5), (67, 13.5) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+child bbox was [(0, 0), (46, 13) 46 x 13] in parent space [(2.0971e+06, 0.5), (2.09714e+06, 13.5) 46 x 13]
+ now2 [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5]
+child bbox was [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5] in parent space [(-1.5, 1.5), (2.09715e+06, 15) 2.09716e+06 x 13.5]
+Marker::group for recomputing BBOX before returning it
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+child bbox was [(-4.5, -1.5), (10.5, 15.5) 15 x 17] in parent space [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+child bbox was [(-1.5, 4.75), (7.5, 14) 9 x 9.25] in parent space [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ now [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+child bbox was [(-4.5, -1.5), (10.5, 15.5) 15 x 17] in parent space [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+child bbox was [(-1.5, 4.75), (7.5, 14) 9 x 9.25] in parent space [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ now2 [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+child bbox was [(-4.5, -1.5), (10.5, 15.5) 15 x 17] in parent space [(-7.5, -0.5), (7.5, 16.5) 15 x 17]
+ now [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5]
+child bbox was [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5] in parent space [(-1.5, 1.5), (2.09715e+06, 15) 2.09716e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+child bbox was [(-4.5, -1.5), (10.5, 15.5) 15 x 17] in parent space [(-7.5, -0.5), (7.5, 16.5) 15 x 17]
+ now2 [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-7.5, 43.5), (1.7e+307, 61.5) 1.7e+307 x 18]
+meter group recomputing BBOX before returning it
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 recomputing BBOX before returning it
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+child bbox was [(1.5, -1.5), (31.5, 15.5) 30 x 17] in parent space [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+child bbox was [(-1.5, -1.5), (7.5, 14) 9 x 15.5] in parent space [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+child bbox was [(0, 0), (18, 13) 18 x 13] in parent space [(8, 0), (26, 13) 18 x 13]
+ now [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+child bbox was [(1.5, -1.5), (31.5, 15.5) 30 x 17] in parent space [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+child bbox was [(-1.5, -1.5), (7.5, 14) 9 x 15.5] in parent space [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+child bbox was [(0, 0), (18, 13) 18 x 13] in parent space [(8, 0), (26, 13) 18 x 13]
+ now2 [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+child bbox was [(-1.5, -1.5), (31.5, 15.5) 33 x 17] in parent space [(-4.5, -0.5), (28.5, 16.5) 33 x 17]
+ now [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+child bbox was [(-1.5, -1.5), (31.5, 15.5) 33 x 17] in parent space [(-4.5, -0.5), (28.5, 16.5) 33 x 17]
+ now2 [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-4.5, 28.5), (1.7e+307, 46.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 13.5), (1.7e+307, 31.5) 1.7e+307 x 18]
+ now [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 88.5), (1.7e+307, 106.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 103.5), (1.7e+307, 121.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 73.5), (1.7e+307, 91.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 58.5), (1.7e+307, 76.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-7.5, 43.5), (1.7e+307, 61.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-4.5, 28.5), (1.7e+307, 46.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 13.5), (1.7e+307, 31.5) 1.7e+307 x 18]
+ now2 [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time line group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now2 [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, 118.5), (1.7e+307, 1054.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, 118.5), (1.7e+307, 1054.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ghosts for Master recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master recomputing BBOX before returning it
+SV canvas rectangle Master recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for Master recomputing BBOX before returning it
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ now2 [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+main for MIDI recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ghosts for MIDI recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI recomputing BBOX before returning it
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI recomputing BBOX before returning it
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for MIDI recomputing BBOX before returning it
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, -1), (1.7e+307, 1) 1.7e+307 x 2]
+separator for TAV recomputing BBOX before returning it
+ now [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ now2 [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll recomputing BBOX before returning it
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews recomputing BBOX before returning it
+main for Master recomputing BBOX before returning it
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+child bbox was [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2] in parent space [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+child bbox was [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2] in parent space [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI recomputing BBOX before returning it
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+child bbox was [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2] in parent space [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+child bbox was [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2] in parent space [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, 66.5), (1.7e+307, 137.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, 66.5), (1.7e+307, 137.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+child bbox was [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139] in parent space [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, 134.5), (1.7e+307, 1070.5) 1.7e+307 x 936]
+ now [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+child bbox was [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139] in parent space [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, 134.5), (1.7e+307, 1070.5) 1.7e+307 x 936]
+ now2 [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time line group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (2.09715e+06, 14) 2.09716e+06 x 13.5]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for auto Master/Fader recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Trim recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Mute recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto Master/L/R recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Width recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Fader recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Mute recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/L/R recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Width recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+selections for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (2.09715e+06, 14) 2.09716e+06 x 9.25]
+TempoCurve::curve for 120 recomputing BBOX before returning it
+ now [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ now2 [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ now2 [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ now2 [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ now2 [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ now2 [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ now2 [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ now2 [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ now2 [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ now2 [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ now2 [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ now2 [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ now2 [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ now2 [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ now2 [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ now2 [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ now2 [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ now2 [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ now2 [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ now2 [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ now2 [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ now2 [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ now2 [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ now2 [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ now2 [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ now2 [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ now2 [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ now2 [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ now2 [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ now2 [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ now2 [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ now2 [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ now2 [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas h scroll recomputing BBOX before returning it
+time line group recomputing BBOX before returning it
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars recomputing BBOX before returning it
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 88.5), (1.7e+307, 106.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 103.5), (1.7e+307, 121.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 73.5), (1.7e+307, 91.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 58.5), (1.7e+307, 76.5) 1.7e+307 x 18]
+tempo group recomputing BBOX before returning it
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 recomputing BBOX before returning it
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+child bbox was [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25] in parent space [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+child bbox was [(0, 0), (57, 13) 57 x 13] in parent space [(10, 0.5), (67, 13.5) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+child bbox was [(0, 0), (46, 13) 46 x 13] in parent space [(6.59744e+06, 0.5), (6.59748e+06, 13.5) 46 x 13]
+ now [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+child bbox was [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25] in parent space [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+child bbox was [(0, 0), (57, 13) 57 x 13] in parent space [(10, 0.5), (67, 13.5) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+child bbox was [(0, 0), (46, 13) 46 x 13] in parent space [(6.59744e+06, 0.5), (6.59748e+06, 13.5) 46 x 13]
+ now2 [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+child bbox was [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5] in parent space [(-1.5, 1.5), (6.59749e+06, 15) 6.5975e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+child bbox was [(-4.5, -1.5), (10.5, 15.5) 15 x 17] in parent space [(-7.5, -0.5), (7.5, 16.5) 15 x 17]
+ now [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+child bbox was [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5] in parent space [(-1.5, 1.5), (6.59749e+06, 15) 6.5975e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+child bbox was [(-4.5, -1.5), (10.5, 15.5) 15 x 17] in parent space [(-7.5, -0.5), (7.5, 16.5) 15 x 17]
+ now2 [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-7.5, 43.5), (1.7e+307, 61.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-4.5, 28.5), (1.7e+307, 46.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 13.5), (1.7e+307, 31.5) 1.7e+307 x 18]
+ now [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 88.5), (1.7e+307, 106.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 103.5), (1.7e+307, 121.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 73.5), (1.7e+307, 91.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 58.5), (1.7e+307, 76.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-7.5, 43.5), (1.7e+307, 61.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-4.5, 28.5), (1.7e+307, 46.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+child bbox was [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18] in parent space [(-1.5, 13.5), (1.7e+307, 31.5) 1.7e+307 x 18]
+ now2 [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now2 [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for auto Master/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Trim not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+selections for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ now2 [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ now2 [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ now2 [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ now2 [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ now2 [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ now2 [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ now2 [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ now2 [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ now2 [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ now2 [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ now2 [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ now2 [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ now2 [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ now2 [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ now2 [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ now2 [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ now2 [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ now2 [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ now2 [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ now2 [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ now2 [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ now2 [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ now2 [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ now2 [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ now2 [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ now2 [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ now2 [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ now2 [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ now2 [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ now2 [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ now2 [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ now2 [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ now2 [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ now2 [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ now2 [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ now2 [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ now2 [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ now2 [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ now2 [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ now2 [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ now2 [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ now2 [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ now2 [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ now2 [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ now2 [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ now2 [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ now2 [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ now2 [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ now2 [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ now2 [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ now2 [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ now2 [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ now2 [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ now2 [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ now2 [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ now2 [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ now2 [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ now2 [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ now2 [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ now2 [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ now2 [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ now2 [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ now2 [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ now2 [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ now2 [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ now2 [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ now2 [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ now2 [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ now2 [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ now2 [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ now2 [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ now2 [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ now2 [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ now2 [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ now2 [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ now2 [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ now2 [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ now2 [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ now2 [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ now2 [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ now2 [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ now2 [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ now2 [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ now2 [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ now2 [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ now2 [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ now2 [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ now2 [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ now2 [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ now2 [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ now2 [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ now2 [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ now2 [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll recomputing BBOX before returning it
+time line group recomputing BBOX before returning it
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now2 [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for auto Master/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Trim not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+selections for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ now2 [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (0.5, 1.7e+307) 1 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ now2 [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (34.371, 1.7e+307) 34.871 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ now2 [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (68.2419, 1.7e+307) 68.7419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ now2 [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (102.113, 1.7e+307) 102.613 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ now2 [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (135.984, 1.7e+307) 136.484 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ now2 [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (169.855, 1.7e+307) 170.355 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ now2 [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (203.726, 1.7e+307) 204.226 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ now2 [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (237.597, 1.7e+307) 238.097 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ now2 [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (271.468, 1.7e+307) 271.968 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ now2 [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (305.339, 1.7e+307) 305.839 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ now2 [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (339.21, 1.7e+307) 339.71 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ now2 [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (373.081, 1.7e+307) 373.581 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ now2 [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (406.952, 1.7e+307) 407.452 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ now2 [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (440.823, 1.7e+307) 441.323 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ now2 [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (474.694, 1.7e+307) 475.194 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ now2 [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (508.565, 1.7e+307) 509.065 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ now2 [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (542.435, 1.7e+307) 542.935 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ now2 [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (576.306, 1.7e+307) 576.806 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ now2 [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (610.177, 1.7e+307) 610.677 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ now2 [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (644.048, 1.7e+307) 644.548 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ now2 [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (677.919, 1.7e+307) 678.419 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ now2 [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (711.79, 1.7e+307) 712.29 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ now2 [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (745.661, 1.7e+307) 746.161 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ now2 [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (779.532, 1.7e+307) 780.032 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ now2 [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (813.403, 1.7e+307) 813.903 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ now2 [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (847.274, 1.7e+307) 847.774 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ now2 [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (881.145, 1.7e+307) 881.645 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ now2 [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (915.016, 1.7e+307) 915.516 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ now2 [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (948.887, 1.7e+307) 949.387 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ now2 [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (982.758, 1.7e+307) 983.258 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ now2 [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1016.63, 1.7e+307) 1017.13 x 1.7e+307]
+ recomputing BBOX before returning it
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll recomputing BBOX before returning it
+time line group recomputing BBOX before returning it
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ now2 [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+child bbox was [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307] in parent space [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+child bbox was [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123] in parent space [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+ now2 [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+cd marker drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+CD Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+transport Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range drag not dirty, bbox = [(-1.5, -1.5), (101.5, 16.5) 103 x 18]
+Range Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Tempo Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+TempoCurve::group for 120 not dirty, bbox = [(-1.5, 0.5), (6.59749e+06, 14) 6.5975e+06 x 13.5]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+ not dirty, bbox = [(0, 0), (46, 13) 46 x 13]
+TempoCurve::curve for 120 not dirty, bbox = [(-1.5, 4.75), (6.59749e+06, 14) 6.5975e+06 x 9.25]
+ not dirty, bbox = [(0, 0), (57, 13) 57 x 13]
+Marker::group for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+ArdourMarker::_name_item for not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Marker::_name_background for not dirty, bbox = [(-4.5, -1.5), (10.5, 15.5) 15 x 17]
+Marker::mark for not dirty, bbox = [(-1.5, 4.75), (7.5, 14) 9 x 9.25]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+meter Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker::group for 4/4 not dirty, bbox = [(-1.5, -1.5), (31.5, 15.5) 33 x 17]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+Marker::_name_background for 4/4 not dirty, bbox = [(1.5, -1.5), (31.5, 15.5) 30 x 17]
+Marker::mark for 4/4 not dirty, bbox = [(-1.5, -1.5), (7.5, 14) 9 x 15.5]
+ArdourMarker::_name_item for 4/4 not dirty, bbox = [(0, 0), (18, 13) 18 x 13]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for auto Master/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Trim not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+selections for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+ROOT recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll recomputing BBOX before returning it
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews recomputing BBOX before returning it
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI recomputing BBOX before returning it
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+child bbox was [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2] in parent space [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI recomputing BBOX before returning it
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ now [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+child bbox was [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2] in parent space [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ now2 [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, 66.5), (1.7e+307, 137.5) 1.7e+307 x 71]
+ now [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+child bbox was [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71] in parent space [(-1.5, 66.5), (1.7e+307, 137.5) 1.7e+307 x 71]
+ now2 [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+child bbox was [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139] in parent space [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, 134.5), (1.7e+307, 1070.5) 1.7e+307 x 936]
+ now [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+child bbox was [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139] in parent space [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+child bbox was [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936] in parent space [(-1.5, 134.5), (1.7e+307, 1070.5) 1.7e+307 x 936]
+ now2 [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+child bbox was [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307] in parent space [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+child bbox was [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072] in parent space [(-1.5, 118.5), (1.7e+307, 1190.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+child bbox was [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307] in parent space [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+ now2 [(-9.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+canvas h scroll not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 1.7e+307) 1.7e+307 x 1.7e+307]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+time line group not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+ not dirty, bbox = [(-0.5, 0), (1050.5, 1.7e+307) 1051 x 1.7e+307]
+time bars not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 121.5) 1.7e+307 x 123]
+cd marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+videotl group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+transport marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+range marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+tempo group not dirty, bbox = [(-7.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+meter group not dirty, bbox = [(-4.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+timecode ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+samples ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+minsec ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+bbt ruler not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+marker group not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+Marker Bar not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 16.5) 1.7e+307 x 18]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+canvas hv scroll not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 1070.5) 1.7e+307 x 1072]
+global rect group not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Canvas Drag Motion not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+Canvas TrackViews not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 137.5) 1.7e+307 x 139]
+main for auto Master/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Trim not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto Master/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Fader not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Mute not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/L/R not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for auto MIDI/Width not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+selections for Master not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle Master not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+main for MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+ghosts for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+selections for MIDI not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+separator for TAV not dirty, bbox = [(-1, 67), (1.7e+307, 69) 1.7e+307 x 2]
+SV canvas group MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+SV canvas rectangle MIDI not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 69.5) 1.7e+307 x 71]
+ not dirty, bbox = [(-1.5, -1.5), (1.7e+307, 934.5) 1.7e+307 x 936]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+canvas cursor scroll not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+track canvas editor cursor not dirty, bbox = [(-9.5, -1.5), (9.5, 1.7e+307) 19 x 1.7e+307]
+arrow head 0 not dirty, bbox = [(-1.5, -1.5), (17.5, 10.5) 19 x 12]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+arrow line not dirty, bbox = [(-1, -1), (1, 1.7e+307) 2 x 1.7e+307]
+ROOT recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(4.5, 4.5), (81.5, 81.5) 77 x 77]
+ now2 [(4.5, 4.5), (81.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 4.5), (161.5, 81.5) 77 x 77]
+ now2 [(84.5, 4.5), (161.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 4.5), (241.5, 81.5) 77 x 77]
+ now2 [(164.5, 4.5), (241.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 4.5), (321.5, 81.5) 77 x 77]
+ now2 [(244.5, 4.5), (321.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 4.5), (401.5, 81.5) 77 x 77]
+ now2 [(324.5, 4.5), (401.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 4.5), (481.5, 81.5) 77 x 77]
+ now2 [(404.5, 4.5), (481.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 4.5), (561.5, 81.5) 77 x 77]
+ now2 [(484.5, 4.5), (561.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 4.5), (641.5, 81.5) 77 x 77]
+ now2 [(564.5, 4.5), (641.5, 81.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 84.5), (81.5, 161.5) 77 x 77]
+ now2 [(4.5, 84.5), (81.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 84.5), (161.5, 161.5) 77 x 77]
+ now2 [(84.5, 84.5), (161.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 84.5), (241.5, 161.5) 77 x 77]
+ now2 [(164.5, 84.5), (241.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 84.5), (321.5, 161.5) 77 x 77]
+ now2 [(244.5, 84.5), (321.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 84.5), (401.5, 161.5) 77 x 77]
+ now2 [(324.5, 84.5), (401.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 84.5), (481.5, 161.5) 77 x 77]
+ now2 [(404.5, 84.5), (481.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 84.5), (561.5, 161.5) 77 x 77]
+ now2 [(484.5, 84.5), (561.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 84.5), (641.5, 161.5) 77 x 77]
+ now2 [(564.5, 84.5), (641.5, 161.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 164.5), (81.5, 241.5) 77 x 77]
+ now2 [(4.5, 164.5), (81.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 164.5), (161.5, 241.5) 77 x 77]
+ now2 [(84.5, 164.5), (161.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 164.5), (241.5, 241.5) 77 x 77]
+ now2 [(164.5, 164.5), (241.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 164.5), (321.5, 241.5) 77 x 77]
+ now2 [(244.5, 164.5), (321.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 164.5), (401.5, 241.5) 77 x 77]
+ now2 [(324.5, 164.5), (401.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 164.5), (481.5, 241.5) 77 x 77]
+ now2 [(404.5, 164.5), (481.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 164.5), (561.5, 241.5) 77 x 77]
+ now2 [(484.5, 164.5), (561.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 164.5), (641.5, 241.5) 77 x 77]
+ now2 [(564.5, 164.5), (641.5, 241.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 244.5), (81.5, 321.5) 77 x 77]
+ now2 [(4.5, 244.5), (81.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 244.5), (161.5, 321.5) 77 x 77]
+ now2 [(84.5, 244.5), (161.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 244.5), (241.5, 321.5) 77 x 77]
+ now2 [(164.5, 244.5), (241.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 244.5), (321.5, 321.5) 77 x 77]
+ now2 [(244.5, 244.5), (321.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 244.5), (401.5, 321.5) 77 x 77]
+ now2 [(324.5, 244.5), (401.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 244.5), (481.5, 321.5) 77 x 77]
+ now2 [(404.5, 244.5), (481.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 244.5), (561.5, 321.5) 77 x 77]
+ now2 [(484.5, 244.5), (561.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 244.5), (641.5, 321.5) 77 x 77]
+ now2 [(564.5, 244.5), (641.5, 321.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 324.5), (81.5, 401.5) 77 x 77]
+ now2 [(4.5, 324.5), (81.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 324.5), (161.5, 401.5) 77 x 77]
+ now2 [(84.5, 324.5), (161.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 324.5), (241.5, 401.5) 77 x 77]
+ now2 [(164.5, 324.5), (241.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 324.5), (321.5, 401.5) 77 x 77]
+ now2 [(244.5, 324.5), (321.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 324.5), (401.5, 401.5) 77 x 77]
+ now2 [(324.5, 324.5), (401.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 324.5), (481.5, 401.5) 77 x 77]
+ now2 [(404.5, 324.5), (481.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 324.5), (561.5, 401.5) 77 x 77]
+ now2 [(484.5, 324.5), (561.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 324.5), (641.5, 401.5) 77 x 77]
+ now2 [(564.5, 324.5), (641.5, 401.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 404.5), (81.5, 481.5) 77 x 77]
+ now2 [(4.5, 404.5), (81.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 404.5), (161.5, 481.5) 77 x 77]
+ now2 [(84.5, 404.5), (161.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 404.5), (241.5, 481.5) 77 x 77]
+ now2 [(164.5, 404.5), (241.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 404.5), (321.5, 481.5) 77 x 77]
+ now2 [(244.5, 404.5), (321.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 404.5), (401.5, 481.5) 77 x 77]
+ now2 [(324.5, 404.5), (401.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 404.5), (481.5, 481.5) 77 x 77]
+ now2 [(404.5, 404.5), (481.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 404.5), (561.5, 481.5) 77 x 77]
+ now2 [(484.5, 404.5), (561.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 404.5), (641.5, 481.5) 77 x 77]
+ now2 [(564.5, 404.5), (641.5, 481.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 484.5), (81.5, 561.5) 77 x 77]
+ now2 [(4.5, 484.5), (81.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 484.5), (161.5, 561.5) 77 x 77]
+ now2 [(84.5, 484.5), (161.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 484.5), (241.5, 561.5) 77 x 77]
+ now2 [(164.5, 484.5), (241.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 484.5), (321.5, 561.5) 77 x 77]
+ now2 [(244.5, 484.5), (321.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 484.5), (401.5, 561.5) 77 x 77]
+ now2 [(324.5, 484.5), (401.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 484.5), (481.5, 561.5) 77 x 77]
+ now2 [(404.5, 484.5), (481.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 484.5), (561.5, 561.5) 77 x 77]
+ now2 [(484.5, 484.5), (561.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 484.5), (641.5, 561.5) 77 x 77]
+ now2 [(564.5, 484.5), (641.5, 561.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(4.5, 564.5), (81.5, 641.5) 77 x 77]
+ now2 [(4.5, 564.5), (81.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(84.5, 564.5), (161.5, 641.5) 77 x 77]
+ now2 [(84.5, 564.5), (161.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(164.5, 564.5), (241.5, 641.5) 77 x 77]
+ now2 [(164.5, 564.5), (241.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(244.5, 564.5), (321.5, 641.5) 77 x 77]
+ now2 [(244.5, 564.5), (321.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(324.5, 564.5), (401.5, 641.5) 77 x 77]
+ now2 [(324.5, 564.5), (401.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(404.5, 564.5), (481.5, 641.5) 77 x 77]
+ now2 [(404.5, 564.5), (481.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(484.5, 564.5), (561.5, 641.5) 77 x 77]
+ now2 [(484.5, 564.5), (561.5, 641.5) 77 x 77]
+ recomputing BBOX before returning it
+ now [(564.5, 564.5), (641.5, 641.5) 77 x 77]
+ now2 [(564.5, 564.5), (641.5, 641.5) 77 x 77]
+ROOT recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for box recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for box recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for box not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 0
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 0 recomputing BBOX before returning it
+Grid Grid for row 0 computing bbox
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 0 recomputing BBOX before returning it
+Grid Grid for row 0 computing bbox
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 0 recomputing BBOX before returning it
+Grid Grid for row 0 computing bbox
+row note for 0 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 0 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 0 computing bbox
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 1
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 1 recomputing BBOX before returning it
+Grid Grid for row 1 computing bbox
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 1 recomputing BBOX before returning it
+Grid Grid for row 1 computing bbox
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 1 recomputing BBOX before returning it
+Grid Grid for row 1 computing bbox
+row note for 1 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 1 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 1 computing bbox
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 2
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 2 recomputing BBOX before returning it
+Grid Grid for row 2 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 2 recomputing BBOX before returning it
+Grid Grid for row 2 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 2 recomputing BBOX before returning it
+Grid Grid for row 2 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 2 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 2 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 2 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 3
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 3 recomputing BBOX before returning it
+Grid Grid for row 3 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 3 recomputing BBOX before returning it
+Grid Grid for row 3 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 3 recomputing BBOX before returning it
+Grid Grid for row 3 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 3 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 3 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 3 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 3 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 3 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 3 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 3 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 3 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 4
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 4 recomputing BBOX before returning it
+Grid Grid for row 4 computing bbox
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 4 recomputing BBOX before returning it
+Grid Grid for row 4 computing bbox
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 4 recomputing BBOX before returning it
+Grid Grid for row 4 computing bbox
+row note for 4 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 4 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 4 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 4 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 4 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 4 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 4 computing bbox
+row note for 4 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 5
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 5 recomputing BBOX before returning it
+Grid Grid for row 5 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 5 recomputing BBOX before returning it
+Grid Grid for row 5 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 5 recomputing BBOX before returning it
+Grid Grid for row 5 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 5 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 5 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 5 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 5 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 5 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 5 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 5 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 5 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 6
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 6 recomputing BBOX before returning it
+Grid Grid for row 6 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 6 recomputing BBOX before returning it
+Grid Grid for row 6 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 6 recomputing BBOX before returning it
+Grid Grid for row 6 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 6 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 6 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 6 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 6 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 6 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 6 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 6 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 6 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+SwitchRow 7
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 7 recomputing BBOX before returning it
+Grid Grid for row 7 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 7 recomputing BBOX before returning it
+Grid Grid for row 7 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ children say [(0, 0), (0, 0) 0 x 0]
+ ... [(0, 0), (0, 0) 0 x 0]
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+ recomputing BBOX before returning it
+ now [(0, 0), (0, 0) 0 x 0]
+ now2 [(0, 0), (0, 0) 0 x 0]
+Grid for row 7 recomputing BBOX before returning it
+Grid Grid for row 7 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 7 recomputing BBOX before returning it
+ now [(0, 0), (32, 21) 32 x 21]
+ now2 [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 recomputing BBOX before returning it
+ now [(0, 0), (37, 21) 37 x 21]
+ now2 [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ children say [(0, 0), (37, 21) 37 x 21]
+ expanding from [(0, 0), (37, 21) 37 x 21] to [(-6, -6), (43, 27) 49 x 33]
+ ... [(-6, -6), (43, 27) 49 x 33]
+ now [(-6, -6), (43, 27) 49 x 33]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(0, 0), (37, 21) 37 x 21]
+ now2 [(-6, -6), (43, 27) 49 x 33]
+After setup, row 7 has bbox [(-6, -6), (43, 27) 49 x 33]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid Grid for row 7 computing bbox
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+child bbox was [(0, 0), (32, 21) 32 x 21] in parent space [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+child bbox was [(0, 0), (37, 21) 37 x 21] in parent space [(44, 0), (81, 21) 37 x 21]
+ children say [(0, 0), (81, 21) 81 x 21]
+ expanding from [(0, 0), (81, 21) 81 x 21] to [(-6, -6), (87, 27) 93 x 33]
+ ... [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+REPOS 9 uniform size [(0, 0), (0, 0) 0 x 0]
+bg rect for box not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for box placed within swvbox @ (0, 0) current = [(0, 0), (0, 0) 0 x 0]
+bg rect for box not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ item bbox for bg rect for box Rectangle was [(0, 0), (0, 0) 0 x 0] spacing = 0 and shift = 0
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 0 placed within swvbox @ (0, 0) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 0 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 placed within swvbox @ (0, 33) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 1 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 placed within swvbox @ (0, 66) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 2 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 placed within swvbox @ (0, 99) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 3 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 placed within swvbox @ (0, 132) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 4 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 placed within swvbox @ (0, 165) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 5 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 placed within swvbox @ (0, 198) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 6 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 placed within swvbox @ (0, 231) current = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+ item bbox for Grid for row 7 Grid was [(-6, -6), (87, 27) 93 x 33] spacing = 0 and shift = 33
+bg rect for box not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 27), (87, 60) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 60), (87, 93) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 93), (87, 126) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 126), (87, 159) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 159), (87, 192) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 192), (87, 225) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+child bbox was [(-6, -6), (87, 27) 93 x 33] in parent space [(-6, 225), (87, 258) 93 x 33]
+swvbox box bbox = [(-6, -6), (87, 258) 93 x 264] without padding etc.
+bg rect for box not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+bg rect for box recomputing BBOX before returning it
+ now [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+ now2 [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+ROOT recomputing BBOX before returning it
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+child bbox was [(-7, -7), (88, 259) 95 x 266] in parent space [(-7, -7), (88, 259) 95 x 266]
+ now [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+child bbox was [(-7, -7), (88, 259) 95 x 266] in parent space [(-7, -7), (88, 259) 95 x 266]
+ now2 [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 0 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 0 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 3 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 3 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 3 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 4 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 4 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 4 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 5 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 5 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 5 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 6 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 6 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 6 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid recomputing BBOX before returning it
+ now [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ now2 [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 1 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 1 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 2 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 2 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+ROOT not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+swvbox not dirty, bbox = [(-7, -7), (88, 259) 95 x 266]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 0 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 1 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 2 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 3 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 4 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 5 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 6 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for box not dirty, bbox = [(-8.5, -8.5), (89.5, 260.5) 98 x 269]
+Grid for row 7 not dirty, bbox = [(-6, -6), (87, 27) 93 x 33]
+bg rect for grid not dirty, bbox = [(-7.5, -7.5), (88.5, 28.5) 96 x 36]
+row note for 7 not dirty, bbox = [(0, 0), (32, 21) 32 x 21]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+ not dirty, bbox = [(0, 0), (0, 0) 0 x 0]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
+clear for 7 not dirty, bbox = [(0, 0), (37, 21) 37 x 21]
diff --git a/libs/ardour/ardour/midi_model.h.orig b/libs/ardour/ardour/midi_model.h.orig
new file mode 100644
index 0000000000..fb71f69085
--- /dev/null
+++ b/libs/ardour/ardour/midi_model.h.orig
@@ -0,0 +1,334 @@
+/*
+ Copyright (C) 2007 Paul Davis
+ Author: David Robillard
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef __ardour_midi_model_h__
+#define __ardour_midi_model_h__
+
+#include <deque>
+#include <queue>
+#include <utility>
+
+#include <boost/utility.hpp>
+#include <glibmm/threads.h>
+
+#include "pbd/command.h"
+
+#include "ardour/automatable_sequence.h"
+#include "ardour/libardour_visibility.h"
+#include "ardour/libardour_visibility.h"
+#include "ardour/types.h"
+#include "ardour/types.h"
+#include "ardour/variant.h"
+
+#include "evoral/Note.hpp"
+#include "evoral/Sequence.hpp"
+
+namespace ARDOUR {
+
+class Session;
+class MidiSource;
+
+/** This is a higher level (than MidiBuffer) model of MIDI data, with separate
+ * representations for notes (instead of just unassociated note on/off events)
+ * and controller data. Controller data is represented as part of the
+ * Automatable base (i.e. in a map of AutomationList, keyed by Parameter).
+ * Because of this MIDI controllers and automatable controllers/widgets/etc
+ * are easily interchangeable.
+ */
+class LIBARDOUR_API MidiModel : public AutomatableSequence<Evoral::Beats> {
+public:
+ typedef Evoral::Beats TimeType;
+
+ MidiModel (boost::shared_ptr<MidiSource>);
+
+ NoteMode note_mode() const { return (percussive() ? Percussive : Sustained); }
+ void set_note_mode(NoteMode mode) { set_percussive(mode == Percussive); };
+
+ class LIBARDOUR_API DiffCommand : public Command {
+ public:
+
+ DiffCommand (boost::shared_ptr<MidiModel> m, const std::string& name);
+
+ const std::string& name () const { return _name; }
+
+ virtual void operator() () = 0;
+ virtual void undo () = 0;
+
+ virtual int set_state (const XMLNode&, int version) = 0;
+ virtual XMLNode & get_state () = 0;
+
+ boost::shared_ptr<MidiModel> model() const { return _model; }
+
+ protected:
+ boost::shared_ptr<MidiModel> _model;
+ const std::string _name;
+
+ };
+
+ class LIBARDOUR_API NoteDiffCommand : public DiffCommand {
+ public:
+
+ NoteDiffCommand (boost::shared_ptr<MidiModel> m, const std::string& name) : DiffCommand (m, name) {}
+ NoteDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
+
+ enum Property {
+ NoteNumber,
+ Velocity,
+ StartTime,
+ Length,
+ Channel
+ };
+
+ void operator() ();
+ void undo ();
+
+ int set_state (const XMLNode&, int version);
+ XMLNode & get_state ();
+
+ void add (const NotePtr note);
+ void remove (const NotePtr note);
+ void side_effect_remove (const NotePtr note);
+
+ void change (const NotePtr note, Property prop, uint8_t new_value) {
+ change(note, prop, Variant(new_value));
+ }
+
+ void change (const NotePtr note, Property prop, TimeType new_time) {
+ change(note, prop, Variant(new_time));
+ }
+
+ void change (const NotePtr note, Property prop, const Variant& new_value);
+
+ bool adds_or_removes() const {
+ return !_added_notes.empty() || !_removed_notes.empty();
+ }
+
+ NoteDiffCommand& operator+= (const NoteDiffCommand& other);
+
+ static Variant get_value (const NotePtr note, Property prop);
+
+ static Variant::Type value_type (Property prop);
+
+ struct NoteChange {
+ NoteDiffCommand::Property property;
+ NotePtr note;
+ uint32_t note_id;
+ Variant old_value;
+ Variant new_value;
+ };
+
+ typedef std::list<NoteChange> ChangeList;
+ typedef std::list< boost::shared_ptr< Evoral::Note<TimeType> > > NoteList;
+
+ const ChangeList& changes() const { return _changes; }
+ const NoteList& added_notes() const { return _added_notes; }
+ const NoteList& removed_notes() const { return _removed_notes; }
+
+ private:
+ ChangeList _changes;
+ NoteList _added_notes;
+ NoteList _removed_notes;
+
+ std::set<NotePtr> side_effect_removals;
+
+ XMLNode &marshal_change(const NoteChange&);
+ NoteChange unmarshal_change(XMLNode *xml_note);
+
+ XMLNode &marshal_note(const NotePtr note);
+ NotePtr unmarshal_note(XMLNode *xml_note);
+ };
+
+ /* Currently this class only supports changes of sys-ex time, but could be expanded */
+ class LIBARDOUR_API SysExDiffCommand : public DiffCommand {
+ public:
+ SysExDiffCommand (boost::shared_ptr<MidiModel> m, const XMLNode& node);
+
+ enum Property {
+ Time,
+ };
+
+ int set_state (const XMLNode&, int version);
+ XMLNode & get_state ();
+
+ void remove (SysExPtr sysex);
+ void operator() ();
+ void undo ();
+
+ void change (boost::shared_ptr<Evoral::Event<TimeType> >, TimeType);
+
+ private:
+ struct Change {
+ Change () : sysex_id (0) {}
+ boost::shared_ptr<Evoral::Event<TimeType> > sysex;
+ gint sysex_id;
+ SysExDiffCommand::Property property;
+ TimeType old_time;
+ TimeType new_time;
+ };
+
+ typedef std::list<Change> ChangeList;
+ ChangeList _changes;
+
+ std::list<SysExPtr> _removed;
+
+ XMLNode & marshal_change (const Change &);
+ Change unmarshal_change (XMLNode *);
+ };
+
+ class LIBARDOUR_API PatchChangeDiffCommand : public DiffCommand {
+ public:
+ PatchChangeDiffCommand (boost::shared_ptr<MidiModel>, const std::string &);
+ PatchChangeDiffCommand (boost::shared_ptr<MidiModel>, const XMLNode &);
+
+ int set_state (const XMLNode &, int version);
+ XMLNode & get_state ();
+
+ void operator() ();
+ void undo ();
+
+ void add (PatchChangePtr);
+ void remove (PatchChangePtr);
+ void change_time (PatchChangePtr, TimeType);
+ void change_channel (PatchChangePtr, uint8_t);
+ void change_program (PatchChangePtr, uint8_t);
+ void change_bank (PatchChangePtr, int);
+
+ enum Property {
+ Time,
+ Channel,
+ Program,
+ Bank
+ };
+
+ private:
+ struct Change {
+ PatchChangePtr patch;
+ Property property;
+ gint patch_id;
+ TimeType old_time;
+ union {
+ uint8_t old_channel;
+ int old_bank;
+ uint8_t old_program;
+ };
+ TimeType new_time;
+ union {
+ uint8_t new_channel;
+ uint8_t new_program;
+ int new_bank;
+ };
+
+ Change() : patch_id (-1) {}
+ };
+
+ typedef std::list<Change> ChangeList;
+ ChangeList _changes;
+
+ std::list<PatchChangePtr> _added;
+ std::list<PatchChangePtr> _removed;
+
+ XMLNode & marshal_change (const Change &);
+ Change unmarshal_change (XMLNode *);
+
+ XMLNode & marshal_patch_change (constPatchChangePtr);
+ PatchChangePtr unmarshal_patch_change (XMLNode *);
+ };
+
+ MidiModel::NoteDiffCommand* new_note_diff_command (const std::string& name = "midi edit");
+ MidiModel::SysExDiffCommand* new_sysex_diff_command (const std::string& name = "midi edit");
+ MidiModel::PatchChangeDiffCommand* new_patch_change_diff_command (const std::string& name = "midi edit");
+ void apply_command (Session& session, Command* cmd);
+ void apply_command (Session* session, Command* cmd) { if (session) { apply_command (*session, cmd); } }
+ void apply_command_as_subcommand (Session& session, Command* cmd);
+
+ bool sync_to_source (const Glib::Threads::Mutex::Lock& source_lock);
+
+ bool write_to(boost::shared_ptr<MidiSource> source,
+ const Glib::Threads::Mutex::Lock& source_lock);
+
+ bool write_section_to(boost::shared_ptr<MidiSource> source,
+ const Glib::Threads::Mutex::Lock& source_lock,
+ Evoral::Beats begin = Evoral::MinBeats,
+ Evoral::Beats end = Evoral::MaxBeats,
+ bool offset_events = false);
+
+ // MidiModel doesn't use the normal AutomationList serialisation code
+ // since controller data is stored in the .mid
+ XMLNode& get_state();
+ int set_state(const XMLNode&) { return 0; }
+
+ PBD::Signal0<void> ContentsChanged;
+ PBD::Signal1<void, double> ContentsShifted;
+
+ boost::shared_ptr<const MidiSource> midi_source ();
+ void set_midi_source (boost::shared_ptr<MidiSource>);
+
+ boost::shared_ptr<Evoral::Note<TimeType> > find_note (NotePtr);
+ PatchChangePtr find_patch_change (Evoral::event_id_t);
+ boost::shared_ptr<Evoral::Note<TimeType> > find_note (gint note_id);
+ boost::shared_ptr<Evoral::Event<TimeType> > find_sysex (gint);
+
+ InsertMergePolicy insert_merge_policy () const;
+ void set_insert_merge_policy (InsertMergePolicy);
+
+ boost::shared_ptr<Evoral::Control> control_factory(const Evoral::Parameter& id);
+
+ void insert_silence_at_start (TimeType);
+ void transpose (NoteDiffCommand *, const NotePtr, int);
+
+protected:
+ int resolve_overlaps_unlocked (const NotePtr, void* arg = 0);
+
+private:
+ struct WriteLockImpl : public AutomatableSequence<TimeType>::WriteLockImpl {
+ WriteLockImpl(Glib::Threads::Mutex::Lock* slock, Glib::Threads::RWLock& s, Glib::Threads::Mutex& c)
+ : AutomatableSequence<TimeType>::WriteLockImpl(s, c)
+ , source_lock (slock)
+ {}
+ ~WriteLockImpl() {
+ delete source_lock;
+ }
+ Glib::Threads::Mutex::Lock* source_lock;
+ };
+
+public:
+ WriteLock edit_lock();
+
+private:
+ friend class DeltaCommand;
+
+ void source_interpolation_changed (Evoral::Parameter, Evoral::ControlList::InterpolationStyle);
+ void source_automation_state_changed (Evoral::Parameter, AutoState);
+ void control_list_interpolation_changed (Evoral::Parameter, Evoral::ControlList::InterpolationStyle);
+ void automation_list_automation_state_changed (Evoral::Parameter, AutoState);
+
+ void control_list_marked_dirty ();
+
+ PBD::ScopedConnectionList _midi_source_connections;
+
+ // We cannot use a boost::shared_ptr here to avoid a retain cycle
+ boost::weak_ptr<MidiSource> _midi_source;
+ InsertMergePolicy _insert_merge_policy;
+};
+
+} /* namespace ARDOUR */
+
+#endif /* __ardour_midi_model_h__ */
+
diff --git a/libs/ardour/ardour/midi_source.h.orig b/libs/ardour/ardour/midi_source.h.orig
new file mode 100644
index 0000000000..f8f1e429ac
--- /dev/null
+++ b/libs/ardour/ardour/midi_source.h.orig
@@ -0,0 +1,258 @@
+/*
+ Copyright (C) 2006 Paul Davis
+ Author: David Robillard
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __ardour_midi_source_h__
+#define __ardour_midi_source_h__
+
+#include <string>
+#include <time.h>
+#include <glibmm/threads.h>
+#include <boost/enable_shared_from_this.hpp>
+#include "pbd/stateful.h"
+#include "pbd/xml++.h"
+#include "evoral/Sequence.hpp"
+#include "evoral/Range.hpp"
+#include "ardour/ardour.h"
+#include "ardour/buffer.h"
+#include "ardour/midi_cursor.h"
+#include "ardour/source.h"
+#include "ardour/beats_frames_converter.h"
+
+namespace ARDOUR {
+
+class MidiChannelFilter;
+class MidiModel;
+class MidiStateTracker;
+
+template<typename T> class MidiRingBuffer;
+
+/** Source for MIDI data */
+class LIBARDOUR_API MidiSource : virtual public Source, public boost::enable_shared_from_this<MidiSource>
+{
+ public:
+ typedef Evoral::Beats TimeType;
+
+ MidiSource (Session& session, std::string name, Source::Flag flags = Source::Flag(0));
+ MidiSource (Session& session, const XMLNode&);
+ virtual ~MidiSource ();
+
+ /** Write the data in the given time range to another MidiSource
+ * \param newsrc MidiSource to which data will be written. Should be a
+ * new, empty source. If it already has contents, the results are
+ * undefined. Source must be writable.
+ * \param begin time of earliest event that can be written.
+ * \param end time of latest event that can be written.
+ * \return zero on success, non-zero if the write failed for any reason.
+ */
+ int write_to (const Lock& lock,
+ boost::shared_ptr<MidiSource> newsrc,
+ Evoral::Beats begin = Evoral::MinBeats,
+ Evoral::Beats end = Evoral::MaxBeats);
+
+ /** Export the midi data in the given time range to another MidiSource
+ * \param newsrc MidiSource to which data will be written. Should be a
+ * new, empty source. If it already has contents, the results are
+ * undefined. Source must be writable.
+ * \param begin time of earliest event that can be written.
+ * \param end time of latest event that can be written.
+ * \return zero on success, non-zero if the write failed for any reason.
+ */
+ int export_write_to (const Lock& lock,
+ boost::shared_ptr<MidiSource> newsrc,
+ Evoral::Beats begin,
+ Evoral::Beats end);
+
+ /** Read the data in a given time range from the MIDI source.
+ * All time stamps in parameters are in audio frames (even if the source has tempo time).
+ * \param dst Ring buffer where read events are written.
+ * \param source_start Start position of the SOURCE in this read context.
+ * \param start Start of range to be read.
+ * \param cnt Length of range to be read (in audio frames).
+ * \param loop_range If non-null, all event times will be mapped into this loop range.
+ * \param tracker an optional pointer to MidiStateTracker object, for note on/off tracking.
+ * \param filtered Parameters whose MIDI messages will not be returned.
+ */
+ virtual framecnt_t midi_read (const Lock& lock,
+ Evoral::EventSink<framepos_t>& dst,
+ framepos_t source_start,
+ framepos_t start,
+ framecnt_t cnt,
+ Evoral::Range<framepos_t>* loop_range,
+ MidiCursor& cursor,
+ MidiStateTracker* tracker,
+ MidiChannelFilter* filter,
+ const std::set<Evoral::Parameter>& filtered,
+ const double pulse,
+ const double start_beats) const;
+
+ /** Write data from a MidiRingBuffer to this source.
+ * @param source Source to read from.
+ * @param source_start This source's start position in session frames.
+ * @param cnt The length of time to write.
+ */
+ virtual framecnt_t midi_write (const Lock& lock,
+ MidiRingBuffer<framepos_t>& src,
+ framepos_t source_start,
+ framecnt_t cnt);
+
+ /** Append a single event with a timestamp in beats.
+ *
+ * Caller must ensure that the event is later than the last written event.
+ */
+ virtual void append_event_beats(const Lock& lock,
+ const Evoral::Event<Evoral::Beats>& ev) = 0;
+
+ /** Append a single event with a timestamp in frames.
+ *
+ * Caller must ensure that the event is later than the last written event.
+ */
+ virtual void append_event_frames(const Lock& lock,
+ const Evoral::Event<framepos_t>& ev,
+ framepos_t source_start) = 0;
+
+ virtual bool empty () const;
+ virtual framecnt_t length (framepos_t pos) const;
+ virtual void update_length (framecnt_t);
+
+ virtual void mark_streaming_midi_write_started (const Lock& lock, NoteMode mode);
+ virtual void mark_streaming_write_started (const Lock& lock);
+ virtual void mark_streaming_write_completed (const Lock& lock);
+
+ /** Mark write starting with the given time parameters.
+ *
+ * This is called by MidiDiskStream::process before writing to the capture
+ * buffer which will be later read by midi_read().
+ *
+ * @param position The timeline position the source now starts at.
+ * @param capture_length The current length of the capture, which may not
+ * be zero if record is armed while rolling.
+ * @param loop_length The loop length if looping, otherwise zero.
+ */
+ void mark_write_starting_now (framecnt_t position,
+ framecnt_t capture_length,
+ framecnt_t loop_length);
+
+ /* like ::mark_streaming_write_completed() but with more arguments to
+ * allow control over MIDI-specific behaviour. Expected to be used only
+ * when recording actual MIDI input, rather then when importing files
+ * etc.
+ */
+ virtual void mark_midi_streaming_write_completed (
+ const Lock& lock,
+ Evoral::Sequence<Evoral::Beats>::StuckNoteOption stuck_option,
+ Evoral::Beats when = Evoral::Beats());
+
+ virtual void session_saved();
+
+ std::string captured_for() const { return _captured_for; }
+ void set_captured_for (std::string str) { _captured_for = str; }
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ bool length_mutable() const { return true; }
+
+ void set_length_beats(TimeType l) { _length_beats = l; }
+ TimeType length_beats() const { return _length_beats; }
+
+ virtual void load_model(const Glib::Threads::Mutex::Lock& lock, bool force_reload=false) = 0;
+ virtual void destroy_model(const Glib::Threads::Mutex::Lock& lock) = 0;
+
+ /** Reset cached information (like iterators) when things have changed.
+ * @param lock Source lock, which must be held by caller.
+ */
+ void invalidate(const Glib::Threads::Mutex::Lock& lock);
+
+ /** Thou shalt not emit this directly, use invalidate() instead. */
+ mutable PBD::Signal1<void, bool> Invalidated;
+
+ void set_note_mode(const Glib::Threads::Mutex::Lock& lock, NoteMode mode);
+
+ boost::shared_ptr<MidiModel> model() { return _model; }
+ void set_model(const Glib::Threads::Mutex::Lock& lock, boost::shared_ptr<MidiModel>);
+ void drop_model(const Glib::Threads::Mutex::Lock& lock);
+
+ Evoral::ControlList::InterpolationStyle interpolation_of (Evoral::Parameter) const;
+ void set_interpolation_of (Evoral::Parameter, Evoral::ControlList::InterpolationStyle);
+ void copy_interpolation_from (boost::shared_ptr<MidiSource>);
+ void copy_interpolation_from (MidiSource *);
+
+ AutoState automation_state_of (Evoral::Parameter) const;
+ void set_automation_state_of (Evoral::Parameter, AutoState);
+ void copy_automation_state_from (boost::shared_ptr<MidiSource>);
+ void copy_automation_state_from (MidiSource *);
+
+ /** Emitted when a different MidiModel is set */
+ PBD::Signal0<void> ModelChanged;
+ /** Emitted when a parameter's interpolation style is changed */
+ PBD::Signal2<void, Evoral::Parameter, Evoral::ControlList::InterpolationStyle> InterpolationChanged;
+ /** Emitted when a parameter's automation state is changed */
+ PBD::Signal2<void, Evoral::Parameter, AutoState> AutomationStateChanged;
+
+ protected:
+ virtual void flush_midi(const Lock& lock) = 0;
+
+ virtual framecnt_t read_unlocked (const Lock& lock,
+ Evoral::EventSink<framepos_t>& dst,
+ framepos_t position,
+ framepos_t start,
+ framecnt_t cnt,
+ Evoral::Range<framepos_t>* loop_range,
+ MidiStateTracker* tracker,
+ MidiChannelFilter* filter) const = 0;
+
+ /** Write data to this source from a MidiRingBuffer.
+ * @param source Buffer to read from.
+ * @param position This source's start position in session frames.
+ * @param cnt The duration of this block to write for.
+ */
+ virtual framecnt_t write_unlocked (const Lock& lock,
+ MidiRingBuffer<framepos_t>& source,
+ framepos_t position,
+ framecnt_t cnt) = 0;
+
+ std::string _captured_for;
+
+ boost::shared_ptr<MidiModel> _model;
+ bool _writing;
+
+ Evoral::Beats _length_beats;
+
+ /** The total duration of the current capture. */
+ framepos_t _capture_length;
+
+ /** Length of transport loop during current capture, or zero. */
+ framepos_t _capture_loop_length;
+
+ /** Map of interpolation styles to use for Parameters; if they are not in this map,
+ * the correct interpolation style can be obtained from EventTypeMap::interpolation_of ()
+ */
+ typedef std::map<Evoral::Parameter, Evoral::ControlList::InterpolationStyle> InterpolationStyleMap;
+ InterpolationStyleMap _interpolation_style;
+
+ /** Map of automation states to use for Parameters; if they are not in this map,
+ * the correct automation state is Off.
+ */
+ typedef std::map<Evoral::Parameter, AutoState> AutomationStateMap;
+ AutomationStateMap _automation_state;
+};
+
+}
+
+#endif /* __ardour_midi_source_h__ */
diff --git a/libs/ardour/midi_source.cc.orig b/libs/ardour/midi_source.cc.orig
new file mode 100644
index 0000000000..ea0bd15b0c
--- /dev/null
+++ b/libs/ardour/midi_source.cc.orig
@@ -0,0 +1,580 @@
+/*
+ Copyright (C) 2006 Paul Davis
+ Author: David Robillard
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <float.h>
+#include <cerrno>
+#include <ctime>
+#include <cmath>
+#include <iomanip>
+#include <algorithm>
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include "pbd/xml++.h"
+#include "pbd/pthread_utils.h"
+#include "pbd/basename.h"
+
+#include "evoral/Control.hpp"
+#include "evoral/EventSink.hpp"
+
+#include "ardour/debug.h"
+#include "ardour/file_source.h"
+#include "ardour/midi_channel_filter.h"
+#include "ardour/midi_cursor.h"
+#include "ardour/midi_model.h"
+#include "ardour/midi_source.h"
+#include "ardour/midi_state_tracker.h"
+#include "ardour/session.h"
+#include "ardour/session_directory.h"
+#include "ardour/source_factory.h"
+#include "ardour/tempo.h"
+
+#include "pbd/i18n.h"
+
+namespace ARDOUR { template <typename T> class MidiRingBuffer; }
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+MidiSource::MidiSource (Session& s, string name, Source::Flag flags)
+ : Source(s, DataType::MIDI, name, flags)
+ , _writing(false)
+ , _length_beats(0.0)
+ , _capture_length(0)
+ , _capture_loop_length(0)
+{
+}
+
+MidiSource::MidiSource (Session& s, const XMLNode& node)
+ : Source(s, node)
+ , _writing(false)
+ , _length_beats(0.0)
+ , _capture_length(0)
+ , _capture_loop_length(0)
+{
+ if (set_state (node, Stateful::loading_state_version)) {
+ throw failed_constructor();
+ }
+}
+
+MidiSource::~MidiSource ()
+{
+ /* invalidate any existing iterators */
+ Invalidated (false);
+}
+
+XMLNode&
+MidiSource::get_state ()
+{
+ XMLNode& node (Source::get_state());
+
+ if (_captured_for.length()) {
+ node.set_property ("captured-for", _captured_for);
+ }
+
+ for (InterpolationStyleMap::const_iterator i = _interpolation_style.begin(); i != _interpolation_style.end(); ++i) {
+ XMLNode* child = node.add_child (X_("InterpolationStyle"));
+ child->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first));
+ child->set_property (X_("style"), enum_2_string (i->second));
+ }
+
+ for (AutomationStateMap::const_iterator i = _automation_state.begin(); i != _automation_state.end(); ++i) {
+ XMLNode* child = node.add_child (X_("AutomationState"));
+ child->set_property (X_("parameter"), EventTypeMap::instance().to_symbol (i->first));
+ child->set_property (X_("state"), enum_2_string (i->second));
+ }
+
+ return node;
+}
+
+int
+MidiSource::set_state (const XMLNode& node, int /*version*/)
+{
+ node.get_property ("captured-for", _captured_for);
+
+ std::string str;
+ XMLNodeList children = node.children ();
+ for (XMLNodeConstIterator i = children.begin(); i != children.end(); ++i) {
+ if ((*i)->name() == X_("InterpolationStyle")) {
+ if (!(*i)->get_property (X_("parameter"), str)) {
+ error << _("Missing parameter property on InterpolationStyle") << endmsg;
+ return -1;
+ }
+ Evoral::Parameter p = EventTypeMap::instance().from_symbol (str);
+
+ if (!(*i)->get_property (X_("style"), str)) {
+ error << _("Missing style property on InterpolationStyle") << endmsg;
+ return -1;
+ }
+ Evoral::ControlList::InterpolationStyle s =
+ static_cast<Evoral::ControlList::InterpolationStyle>(string_2_enum (str, s));
+ set_interpolation_of (p, s);
+
+ } else if ((*i)->name() == X_("AutomationState")) {
+ if (!(*i)->get_property (X_("parameter"), str)) {
+ error << _("Missing parameter property on AutomationState") << endmsg;
+ return -1;
+ }
+ Evoral::Parameter p = EventTypeMap::instance().from_symbol (str);
+
+ if (!(*i)->get_property (X_("state"), str)) {
+ error << _("Missing state property on AutomationState") << endmsg;
+ return -1;
+ }
+ AutoState s = static_cast<AutoState>(string_2_enum (str, s));
+ set_automation_state_of (p, s);
+ }
+ }
+
+ return 0;
+}
+
+bool
+MidiSource::empty () const
+{
+ return !_length_beats;
+}
+
+framecnt_t
+MidiSource::length (framepos_t pos) const
+{
+ if (!_length_beats) {
+ return 0;
+ }
+
+ BeatsFramesConverter converter(_session.tempo_map(), pos);
+ return converter.to(_length_beats);
+}
+
+void
+MidiSource::update_length (framecnt_t)
+{
+ // You're not the boss of me!
+}
+
+void
+MidiSource::invalidate (const Lock& lock)
+{
+ Invalidated(_session.transport_rolling());
+}
+
+framecnt_t
+MidiSource::midi_read (const Lock& lm,
+ Evoral::EventSink<framepos_t>& dst,
+ framepos_t source_start,
+ framepos_t start,
+ framecnt_t cnt,
+ Evoral::Range<framepos_t>* loop_range,
+ MidiCursor& cursor,
+ MidiStateTracker* tracker,
+ MidiChannelFilter* filter,
+ const std::set<Evoral::Parameter>& filtered,
+ const double pos_beats,
+ const double start_beats) const
+{
+ BeatsFramesConverter converter(_session.tempo_map(), source_start);
+
+ const double start_qn = pos_beats - start_beats;
+
+ DEBUG_TRACE (DEBUG::MidiSourceIO,
+ string_compose ("MidiSource::midi_read() %5 sstart %1 start %2 cnt %3 tracker %4\n",
+ source_start, start, cnt, tracker, name()));
+
+ if (!_model) {
+ return read_unlocked (lm, dst, source_start, start, cnt, loop_range, tracker, filter);
+ }
+
+ // Find appropriate model iterator
+ Evoral::Sequence<Evoral::Beats>::const_iterator& i = cursor.iter;
+ const bool linear_read = cursor.last_read_end != 0 && start == cursor.last_read_end;
+ if (!linear_read || !i.valid()) {
+ /* Cached iterator is invalid, search for the first event past start.
+ Note that multiple tracks can use a MidiSource simultaneously, so
+ all playback state must be in parameters (the cursor) and must not
+ be cached in the source of model itself.
+ See http://tracker.ardour.org/view.php?id=6541
+ */
+ cursor.connect(Invalidated);
+ cursor.iter = _model->begin(converter.from(start), false, filtered, &cursor.active_notes);
+ cursor.active_notes.clear();
+ }
+
+ cursor.last_read_end = start + cnt;
+
+ // Copy events in [start, start + cnt) into dst
+ for (; i != _model->end(); ++i) {
+
+ // Offset by source start to convert event time to session time
+
+ framepos_t time_frames = _session.tempo_map().frame_at_quarter_note (i->time().to_double() + start_qn);
+
+ if (time_frames < start + source_start) {
+ /* event too early */
+
+ continue;
+
+ } else if (time_frames >= start + cnt + source_start) {
+
+ DEBUG_TRACE (DEBUG::MidiSourceIO,
+ string_compose ("%1: reached end with event @ %2 vs. %3\n",
+ _name, time_frames, start+cnt));
+ break;
+
+ } else {
+
+ /* in range */
+
+ if (loop_range) {
+ time_frames = loop_range->squish (time_frames);
+ }
+
+ const uint8_t status = i->buffer()[0];
+ const bool is_channel_event = (0x80 <= (status & 0xF0)) && (status <= 0xE0);
+ if (filter && is_channel_event) {
+ /* Copy event so the filter can modify the channel. I'm not
+ sure if this is necessary here (channels are mapped later in
+ buffers anyway), but it preserves existing behaviour without
+ destroying events in the model during read. */
+ Evoral::Event<Evoral::Beats> ev(*i, true);
+ if (!filter->filter(ev.buffer(), ev.size())) {
+ dst.write(time_frames, ev.event_type(), ev.size(), ev.buffer());
+ } else {
+ DEBUG_TRACE (DEBUG::MidiSourceIO,
+ string_compose ("%1: filter event @ %2 type %3 size %4\n",
+ _name, time_frames, i->event_type(), i->size()));
+ }
+ } else {
+ dst.write (time_frames, i->event_type(), i->size(), i->buffer());
+ }
+
+#ifndef NDEBUG
+ if (DEBUG_ENABLED(DEBUG::MidiSourceIO)) {
+ DEBUG_STR_DECL(a);
+ DEBUG_STR_APPEND(a, string_compose ("%1 added event @ %2 sz %3 within %4 .. %5 ",
+ _name, time_frames, i->size(),
+ start + source_start, start + cnt + source_start));
+ for (size_t n=0; n < i->size(); ++n) {
+ DEBUG_STR_APPEND(a,hex);
+ DEBUG_STR_APPEND(a,"0x");
+ DEBUG_STR_APPEND(a,(int)i->buffer()[n]);
+ DEBUG_STR_APPEND(a,' ');
+ }
+ DEBUG_STR_APPEND(a,'\n');
+ DEBUG_TRACE (DEBUG::MidiSourceIO, DEBUG_STR(a).str());
+ }
+#endif
+
+ if (tracker) {
+ tracker->track (*i);
+ }
+ }
+ }
+
+ return cnt;
+}
+
+framecnt_t
+MidiSource::midi_write (const Lock& lm,
+ MidiRingBuffer<framepos_t>& source,
+ framepos_t source_start,
+ framecnt_t cnt)
+{
+ const framecnt_t ret = write_unlocked (lm, source, source_start, cnt);
+
+ if (cnt == max_framecnt) {
+ invalidate(lm);
+ } else {
+ _capture_length += cnt;
+ }
+
+ return ret;
+}
+
+void
+MidiSource::mark_streaming_midi_write_started (const Lock& lock, NoteMode mode)
+{
+ if (_model) {
+ _model->set_note_mode (mode);
+ _model->start_write ();
+ }
+
+ _writing = true;
+}
+
+void
+MidiSource::mark_write_starting_now (framecnt_t position,
+ framecnt_t capture_length,
+ framecnt_t loop_length)
+{
+ /* I'm not sure if this is the best way to approach this, but
+ _capture_length needs to be set up with the transport frame
+ when a record actually starts, as it is used by
+ SMFSource::write_unlocked to decide whether incoming notes
+ are within the correct time range.
+ mark_streaming_midi_write_started (perhaps a more logical
+ place to do this) is not called at exactly the time when
+ record starts, and I don't think it necessarily can be
+ because it is not RT-safe.
+ */
+
+ set_timeline_position(position);
+ _capture_length = capture_length;
+ _capture_loop_length = loop_length;
+
+ TempoMap& map (_session.tempo_map());
+ BeatsFramesConverter converter(map, position);
+ _length_beats = converter.from(capture_length);
+}
+
+void
+MidiSource::mark_streaming_write_started (const Lock& lock)
+{
+ NoteMode note_mode = _model ? _model->note_mode() : Sustained;
+ mark_streaming_midi_write_started (lock, note_mode);
+}
+
+void
+MidiSource::mark_midi_streaming_write_completed (const Lock& lock,
+ Evoral::Sequence<Evoral::Beats>::StuckNoteOption option,
+ Evoral::Beats end)
+{
+ if (_model) {
+ _model->end_write (option, end);
+
+ /* Make captured controls discrete to play back user input exactly. */
+ for (MidiModel::Controls::iterator i = _model->controls().begin(); i != _model->controls().end(); ++i) {
+ if (i->second->list()) {
+ i->second->list()->set_interpolation(Evoral::ControlList::Discrete);
+ _interpolation_style.insert(std::make_pair(i->second->parameter(), Evoral::ControlList::Discrete));
+ }
+ }
+ }
+
+ invalidate(lock);
+ _writing = false;
+}
+
+void
+MidiSource::mark_streaming_write_completed (const Lock& lock)
+{
+ mark_midi_streaming_write_completed (lock, Evoral::Sequence<Evoral::Beats>::DeleteStuckNotes);
+}
+
+int
+MidiSource::export_write_to (const Lock& lock, boost::shared_ptr<MidiSource> newsrc, Evoral::Beats begin, Evoral::Beats end)
+{
+ Lock newsrc_lock (newsrc->mutex ());
+
+ if (!_model) {
+ error << string_compose (_("programming error: %1"), X_("no model for MidiSource during export"));
+ return -1;
+ }
+
+ _model->write_section_to (newsrc, newsrc_lock, begin, end, true);
+
+ newsrc->flush_midi(newsrc_lock);
+
+ return 0;
+}
+
+int
+MidiSource::write_to (const Lock& lock, boost::shared_ptr<MidiSource> newsrc, Evoral::Beats begin, Evoral::Beats end)
+{
+ Lock newsrc_lock (newsrc->mutex ());
+
+ newsrc->set_timeline_position (_timeline_position);
+ newsrc->copy_interpolation_from (this);
+ newsrc->copy_automation_state_from (this);
+
+ if (_model) {
+ if (begin == Evoral::MinBeats && end == Evoral::MaxBeats) {
+ _model->write_to (newsrc, newsrc_lock);
+ } else {
+ _model->write_section_to (newsrc, newsrc_lock, begin, end);
+ }
+ } else {
+ error << string_compose (_("programming error: %1"), X_("no model for MidiSource during ::clone()"));
+ return -1;
+ }
+
+ newsrc->flush_midi(newsrc_lock);
+
+ /* force a reload of the model if the range is partial */
+
+ if (begin != Evoral::MinBeats || end != Evoral::MaxBeats) {
+ newsrc->load_model (newsrc_lock, true);
+ } else {
+ newsrc->set_model (newsrc_lock, _model);
+ }
+
+ /* this file is not removable (but since it is MIDI, it is mutable) */
+
+ boost::dynamic_pointer_cast<FileSource> (newsrc)->prevent_deletion ();
+
+ return 0;
+}
+
+void
+MidiSource::session_saved()
+{
+ Lock lm (_lock);
+
+ /* this writes a copy of the data to disk.
+ XXX do we need to do this every time?
+ */
+
+ if (_model && _model->edited()) {
+ /* The model is edited, write its contents into the current source
+ file (overwiting previous contents). */
+
+ /* Temporarily drop our reference to the model so that as the model
+ pushes its current state to us, we don't try to update it. */
+ boost::shared_ptr<MidiModel> mm = _model;
+ _model.reset ();
+
+ /* Flush model contents to disk. */
+ mm->sync_to_source (lm);
+
+ /* Reacquire model. */
+ _model = mm;
+
+ } else {
+ flush_midi(lm);
+ }
+}
+
+void
+MidiSource::set_note_mode(const Lock& lock, NoteMode mode)
+{
+ if (_model) {
+ _model->set_note_mode(mode);
+ }
+}
+
+void
+MidiSource::drop_model (const Lock& lock)
+{
+ _model.reset();
+ invalidate(lock);
+ ModelChanged (); /* EMIT SIGNAL */
+}
+
+void
+MidiSource::set_model (const Lock& lock, boost::shared_ptr<MidiModel> m)
+{
+ _model = m;
+ invalidate(lock);
+ ModelChanged (); /* EMIT SIGNAL */
+}
+
+Evoral::ControlList::InterpolationStyle
+MidiSource::interpolation_of (Evoral::Parameter p) const
+{
+ InterpolationStyleMap::const_iterator i = _interpolation_style.find (p);
+ if (i == _interpolation_style.end()) {
+ return EventTypeMap::instance().interpolation_of (p);
+ }
+
+ return i->second;
+}
+
+AutoState
+MidiSource::automation_state_of (Evoral::Parameter p) const
+{
+ AutomationStateMap::const_iterator i = _automation_state.find (p);
+ if (i == _automation_state.end()) {
+ /* default to `play', otherwise if MIDI is recorded /
+ imported with controllers etc. they are by default
+ not played back, which is a little surprising.
+ */
+ return Play;
+ }
+
+ return i->second;
+}
+
+/** Set interpolation style to be used for a given parameter. This change will be
+ * propagated to anyone who needs to know.
+ */
+void
+MidiSource::set_interpolation_of (Evoral::Parameter p, Evoral::ControlList::InterpolationStyle s)
+{
+ if (interpolation_of (p) == s) {
+ return;
+ }
+
+ if (EventTypeMap::instance().interpolation_of (p) == s) {
+ /* interpolation type is being set to the default, so we don't need a note in our map */
+ _interpolation_style.erase (p);
+ } else {
+ _interpolation_style[p] = s;
+ }
+
+ InterpolationChanged (p, s); /* EMIT SIGNAL */
+}
+
+void
+MidiSource::set_automation_state_of (Evoral::Parameter p, AutoState s)
+{
+ if (automation_state_of (p) == s) {
+ return;
+ }
+
+ if (s == Play) {
+ /* automation state is being set to the default, so we don't need a note in our map */
+ _automation_state.erase (p);
+ } else {
+ _automation_state[p] = s;
+ }
+
+ AutomationStateChanged (p, s); /* EMIT SIGNAL */
+}
+
+void
+MidiSource::copy_interpolation_from (boost::shared_ptr<MidiSource> s)
+{
+ copy_interpolation_from (s.get ());
+}
+
+void
+MidiSource::copy_automation_state_from (boost::shared_ptr<MidiSource> s)
+{
+ copy_automation_state_from (s.get ());
+}
+
+void
+MidiSource::copy_interpolation_from (MidiSource* s)
+{
+ _interpolation_style = s->_interpolation_style;
+
+ /* XXX: should probably emit signals here */
+}
+
+void
+MidiSource::copy_automation_state_from (MidiSource* s)
+{
+ _automation_state = s->_automation_state;
+
+ /* XXX: should probably emit signals here */
+}
diff --git a/libs/evoral/evoral/Beats.hpp.orig b/libs/evoral/evoral/Beats.hpp.orig
new file mode 100644
index 0000000000..e0277c4b3d
--- /dev/null
+++ b/libs/evoral/evoral/Beats.hpp.orig
@@ -0,0 +1,247 @@
+/* This file is part of Evoral.
+ * Copyright (C) 2008-2015 David Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ *
+ * Evoral is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EVORAL_BEATS_HPP
+#define EVORAL_BEATS_HPP
+
+#include <float.h>
+#include <math.h>
+#include <stdint.h>
+
+#include <iostream>
+#include <limits>
+
+#include "evoral/visibility.h"
+
+namespace Evoral {
+
+/** Musical time in beats. */
+class /*LIBEVORAL_API*/ Beats {
+public:
+ LIBEVORAL_API static const double PPQN;
+
+ Beats() : _time(0.0) {}
+
+ /** Create from a real number of beats. */
+ explicit Beats(double time) : _time(time) {}
+
+ /** Create from an integer number of beats. */
+ static Beats beats(int32_t beats) {
+ return Beats((double)beats);
+ }
+
+ /** Create from ticks at the standard PPQN. */
+ static Beats ticks(uint32_t ticks) {
+ return Beats(ticks / PPQN);
+ }
+
+ /** Create from ticks at a given rate.
+ *
+ * Note this can also be used to create from frames by setting ppqn to the
+ * number of samples per beat.
+ */
+ static Beats ticks_at_rate(uint64_t ticks, uint32_t ppqn) {
+ return Beats((double)ticks / (double)ppqn);
+ }
+
+ Beats& operator=(const Beats& other) {
+ _time = other._time;
+ return *this;
+ }
+
+ Beats round_up_to_beat() const {
+ return Evoral::Beats(ceil(_time));
+ }
+
+ Beats round_down_to_beat() const {
+ return Evoral::Beats(floor(_time));
+ }
+
+ Beats snap_to(const Evoral::Beats& snap) const {
+ return Beats(ceil(_time / snap._time) * snap._time);
+ }
+
+ inline bool operator==(const Beats& b) const {
+ /* Acceptable tolerance is 1 tick. */
+ return fabs(_time - b._time) <= (1.0 / PPQN);
+ }
+
+ inline bool operator==(double t) const {
+ /* Acceptable tolerance is 1 tick. */
+ return fabs(_time - t) <= (1.0 / PPQN);
+ }
+
+ inline bool operator==(int beats) const {
+ /* Acceptable tolerance is 1 tick. */
+ return fabs(_time - beats) <= (1.0 / PPQN);
+ }
+
+ inline bool operator!=(const Beats& b) const {
+ return !operator==(b);
+ }
+
+ inline bool operator<(const Beats& b) const {
+ /* Acceptable tolerance is 1 tick. */
+ if (fabs(_time - b._time) <= (1.0 / PPQN)) {
+ return false; /* Effectively identical. */
+ } else {
+ return _time < b._time;
+ }
+ }
+
+ inline bool operator<=(const Beats& b) const {
+ return operator==(b) || operator<(b);
+ }
+
+ inline bool operator>(const Beats& b) const {
+ /* Acceptable tolerance is 1 tick. */
+ if (fabs(_time - b._time) <= (1.0 / PPQN)) {
+ return false; /* Effectively identical. */
+ } else {
+ return _time > b._time;
+ }
+ }
+
+ inline bool operator>=(const Beats& b) const {
+ return operator==(b) || operator>(b);
+ }
+
+ inline bool operator<(double b) const {
+ /* Acceptable tolerance is 1 tick. */
+ if (fabs(_time - b) <= (1.0 / PPQN)) {
+ return false; /* Effectively identical. */
+ } else {
+ return _time < b;
+ }
+ }
+
+ inline bool operator<=(double b) const {
+ return operator==(b) || operator<(b);
+ }
+
+ inline bool operator>(double b) const {
+ /* Acceptable tolerance is 1 tick. */
+ if (fabs(_time - b) <= (1.0 / PPQN)) {
+ return false; /* Effectively identical. */
+ } else {
+ return _time > b;
+ }
+ }
+
+ inline bool operator>=(double b) const {
+ return operator==(b) || operator>(b);
+ }
+
+ Beats operator+(const Beats& b) const {
+ return Beats(_time + b._time);
+ }
+
+ Beats operator-(const Beats& b) const {
+ return Beats(_time - b._time);
+ }
+
+ Beats operator+(double d) const {
+ return Beats(_time + d);
+ }
+
+ Beats operator-(double d) const {
+ return Beats(_time - d);
+ }
+
+ Beats operator-() const {
+ return Beats(-_time);
+ }
+
+ template<typename Number>
+ Beats operator*(Number factor) const {
+ return Beats(_time * factor);
+ }
+
+ Beats& operator+=(const Beats& b) {
+ _time += b._time;
+ return *this;
+ }
+
+ Beats& operator-=(const Beats& b) {
+ _time -= b._time;
+ return *this;
+ }
+
+ double to_double() const { return _time; }
+ uint64_t to_ticks() const { return lrint(_time * PPQN); }
+ uint64_t to_ticks(uint32_t ppqn) const { return lrint(_time * ppqn); }
+
+ uint32_t get_beats() const { return floor(_time); }
+ uint32_t get_ticks() const { return (uint32_t)lrint(fmod(_time, 1.0) * PPQN); }
+
+ bool operator!() const { return _time == 0; }
+
+ static Beats min() { return Beats(DBL_MIN); }
+ static Beats max() { return Beats(DBL_MAX); }
+ static Beats tick() { return Beats(1.0 / PPQN); }
+
+private:
+ double _time;
+};
+
+extern LIBEVORAL_API const Beats MaxBeats;
+extern LIBEVORAL_API const Beats MinBeats;
+
+/*
+ TIL, several horrible hours later, that sometimes the compiler looks in the
+ namespace of a type (Evoral::Beats in this case) for an operator, and
+ does *NOT* look in the global namespace.
+
+ C++ is proof that hell exists and we are living in it. In any case, move
+ these to the global namespace and PBD::Property's loopy
+ virtual-method-in-a-template will bite you.
+*/
+
+inline std::ostream&
+operator<<(std::ostream& os, const Beats& t)
+{
+ os << t.to_double();
+ return os;
+}
+
+inline std::istream&
+operator>>(std::istream& is, Beats& t)
+{
+ double beats;
+ is >> beats;
+ t = Beats(beats);
+ return is;
+}
+
+} // namespace Evoral
+
+namespace PBD {
+ namespace DEBUG {
+ LIBEVORAL_API extern uint64_t Beats;
+ }
+}
+
+namespace std {
+ template<>
+ struct numeric_limits<Evoral::Beats> {
+ static Evoral::Beats min() { return Evoral::Beats::min(); }
+ static Evoral::Beats max() { return Evoral::Beats::max(); }
+ };
+}
+
+#endif // EVORAL_BEATS_HPP
diff --git a/libs/evoral/test/BeatsTest.cpp b/libs/evoral/test/BeatsTest.cpp
new file mode 100644
index 0000000000..0e3d29a6c6
--- /dev/null
+++ b/libs/evoral/test/BeatsTest.cpp
@@ -0,0 +1,170 @@
+#include <stdlib.h>
+
+#include "BeatsTest.hpp"
+#include "evoral/Beats.hpp"
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BeatsTest);
+
+using namespace Evoral;
+
+static const double delta = 1.5 / (double)Beats::PPQN;
+
+void
+BeatsTest::createTest()
+{
+ const Beats a(1, 2);
+ CPPUNIT_ASSERT_EQUAL(1, a.get_beats());
+ CPPUNIT_ASSERT_EQUAL(2, a.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1 + 2 / (double)Beats::PPQN, a.to_double(), delta);
+
+ const Beats b(1.5);
+ CPPUNIT_ASSERT_EQUAL(1, b.get_beats());
+ CPPUNIT_ASSERT_EQUAL(Beats::PPQN / 2, b.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, b.to_double(), delta);
+
+ const Beats c = Beats::beats(6);
+ CPPUNIT_ASSERT_EQUAL(6, c.get_beats());
+ CPPUNIT_ASSERT_EQUAL(0, c.get_ticks());
+
+ const Beats d = Beats::ticks(7);
+ CPPUNIT_ASSERT_EQUAL(0, d.get_beats());
+ CPPUNIT_ASSERT_EQUAL(7, d.get_ticks());
+
+ Beats e(8, 9);
+ e = d;
+ CPPUNIT_ASSERT_EQUAL(d, e);
+
+
+ // const Beats diff = n2 - n1;
+ // CPPUNIT_ASSERT_EQUAL(-44, diff.get_beats());
+ // CPPUNIT_ASSERT_EQUAL(44 / Beats::PPQN, diff.get_ticks());
+ // CPPUNIT_ASSERT_DOUBLES_EQUAL(diff.to_double(), -44.44, delta);
+}
+
+void
+BeatsTest::addTest()
+{
+ const Beats a(1, 2);
+ const Beats b(3, 4);
+
+ // Positive + positive
+ const Beats c = a + b;
+ CPPUNIT_ASSERT_EQUAL(4, c.get_beats());
+ CPPUNIT_ASSERT_EQUAL(6, c.get_ticks());
+
+ const Beats n1(-12.34);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
+
+ const Beats n2(-56.78);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
+
+ // Positive + negative
+ const Beats p1(1.0);
+ const Beats p_n = p1 + n1;
+ CPPUNIT_ASSERT_EQUAL(-11, p_n.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), p_n.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, p_n.to_double(), delta);
+
+ // Negative + positive
+ const Beats n_p = n1 + p1;
+ CPPUNIT_ASSERT_EQUAL(-11, n_p.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-11.34, n_p.to_double(), delta);
+
+ // Negative + negative
+ const Beats sum = n1 + n2;
+ CPPUNIT_ASSERT_EQUAL(-69, sum.get_beats());
+ //CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.12), n_p.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-69.12, sum.to_double(), delta);
+}
+
+void
+BeatsTest::subtractTest()
+{
+ const Beats a(1, 2);
+ const Beats b(3, 4);
+
+ // Positive - positive
+ const Beats c = b - a;
+ CPPUNIT_ASSERT_EQUAL(2, c.get_beats());
+ CPPUNIT_ASSERT_EQUAL(2, c.get_ticks());
+
+ const Beats n1(-12.34);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-12.34, n1.to_double(), delta);
+
+ const Beats n2(-56.78);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.78, n2.to_double(), delta);
+
+ // Positive - negative
+ const Beats p1(1.0);
+ const Beats p_n = p1 - n1;
+ CPPUNIT_ASSERT_EQUAL(13, p_n.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * 0.34), p_n.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(13.34, p_n.to_double(), delta);
+
+ // Negative - positive
+ const Beats n_p = n1 - p1;
+ CPPUNIT_ASSERT_EQUAL(-13, n_p.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)(Beats::PPQN * -0.34), n_p.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-13.34, n_p.to_double(), delta);
+
+ // Negative - negative
+ const Beats diff = n1 - n2;
+ CPPUNIT_ASSERT_EQUAL(44, diff.get_beats());
+ CPPUNIT_ASSERT_EQUAL((int32_t)lrint(Beats::PPQN * 0.44), diff.get_ticks());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(44.44, diff.to_double(), delta);
+}
+
+void
+BeatsTest::multiplyTest()
+{
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, (Beats(1.5) * 2.0).to_double(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(5.0) * -2.0).to_double(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, (Beats(-5.0) * 2.0).to_double(), delta);
+}
+
+void
+BeatsTest::roundTest()
+{
+ Beats a(1, 1);
+
+ // Round a up
+ const Beats au = a.round_up_to_beat();
+ CPPUNIT_ASSERT_EQUAL(au.get_beats(), 2);
+ CPPUNIT_ASSERT_EQUAL(au.get_ticks(), 0);
+
+ // Round a down
+ const Beats ad = a.round_down_to_beat();
+ CPPUNIT_ASSERT_EQUAL(ad.get_beats(), 1);
+ CPPUNIT_ASSERT_EQUAL(ad.get_ticks(), 0);
+
+ // Round result down again
+ const Beats add = ad.round_down_to_beat();
+ CPPUNIT_ASSERT_EQUAL(ad, add);
+
+ // Round result up
+ const Beats adu = ad.round_up_to_beat();
+ CPPUNIT_ASSERT_EQUAL(ad, adu);
+
+ // Snap to 1.5
+ const Beats snapped = a.snap_to(Beats(1.5));
+ CPPUNIT_ASSERT_EQUAL(snapped.get_beats(), 1);
+ CPPUNIT_ASSERT_EQUAL(snapped.get_ticks(), Beats::PPQN / 2);
+}
+
+void
+BeatsTest::convertTest()
+{
+ const Beats a = Beats::ticks_at_rate(72000, 48000);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1, a.get_beats(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN / 2, a.get_ticks(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.5, a.to_double(), delta);
+
+ const Beats b = Beats::ticks_at_rate(8, 48000);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0, b.get_beats(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(Beats::PPQN * 8 / 48000, b.get_ticks(), delta);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL((8 / 48000.0), b.to_double(), delta);
+
+ CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * Beats::PPQN), a.to_ticks());
+ CPPUNIT_ASSERT_EQUAL(int64_t(1.5 * 192), a.to_ticks(192));
+}
diff --git a/libs/evoral/test/BeatsTest.hpp b/libs/evoral/test/BeatsTest.hpp
new file mode 100644
index 0000000000..0db3831b49
--- /dev/null
+++ b/libs/evoral/test/BeatsTest.hpp
@@ -0,0 +1,22 @@
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class BeatsTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(BeatsTest);
+ CPPUNIT_TEST(createTest);
+ CPPUNIT_TEST(addTest);
+ CPPUNIT_TEST(subtractTest);
+ CPPUNIT_TEST(multiplyTest);
+ CPPUNIT_TEST(convertTest);
+ CPPUNIT_TEST(roundTest);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ void createTest();
+ void addTest();
+ void subtractTest();
+ void multiplyTest();
+ void convertTest();
+ void roundTest();
+};
diff --git a/libs/evoral/wscript.orig b/libs/evoral/wscript.orig
new file mode 100644
index 0000000000..9ceab182a5
--- /dev/null
+++ b/libs/evoral/wscript.orig
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+from waflib import Options
+import os
+
+# Version of this package (even if built as a child)
+EVORAL_VERSION = '0.0.0'
+
+# Library version (UNIX style major, minor, micro)
+# major increment <=> incompatible changes
+# minor increment <=> compatible changes (additions)
+# micro increment <=> no interface changes
+# Version history:
+# 0.0.0 = 0,0,0
+EVORAL_LIB_VERSION = '0.0.0'
+
+# Variables for 'waf dist'
+APPNAME = 'evoral'
+VERSION = EVORAL_VERSION
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+ opt.load('compiler_c')
+ opt.load('compiler_cxx')
+ autowaf.set_options(opt)
+ opt.add_option('--test', action='store_true', default=False, dest='build_tests',
+ help="Build unit tests")
+ opt.add_option('--test-coverage', action='store_true', default=False, dest='test_coverage',
+ help="Use gcov to test for code coverage")
+ opt.add_option('--internal-shared-libs', action='store_true', default=True, dest='internal_shared_libs',
+ help='Build internal libs as shared libraries')
+
+def configure(conf):
+ conf.load('compiler_c')
+ conf.load('compiler_cxx')
+ autowaf.configure(conf)
+ #autowaf.display_header('Evoral Configuration')
+
+ autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False)
+ autowaf.check_pkg(conf, 'glib-2.0', uselib_store='GLIB', atleast_version='2.2')
+ autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', atleast_version='2.14.0')
+ autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', atleast_version='2.14.0')
+ if not autowaf.is_child():
+ autowaf.check_pkg(conf, 'libpbd-4', uselib_store='LIBPBD', atleast_version='4.0.0', mandatory=True)
+
+ # Boost headers
+ autowaf.check_header(conf, 'cxx', 'boost/shared_ptr.hpp')
+ autowaf.check_header(conf, 'cxx', 'boost/weak_ptr.hpp')
+
+ conf.env['BUILD_TESTS'] = Options.options.build_tests
+ conf.env['TEST_COVERAGE'] = Options.options.test_coverage
+
+ if Options.options.internal_shared_libs:
+ conf.define('INTERNAL_SHARED_LIBS', 1)
+ #autowaf.display_msg(conf, "Unit tests", str(conf.env['BUILD_TESTS']))
+ #print
+
+def build(bld):
+ # Headers
+ #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.h')
+ #bld.install_files('${INCLUDEDIR}/evoral', 'evoral/*.hpp')
+
+ # Pkgconfig file
+ #autowaf.build_pc(bld, 'EVORAL', EVORAL_VERSION, 'GLIBMM GTHREAD')
+
+ libsmf = bld(features = 'c cstlib')
+ libsmf.source = '''
+ src/libsmf/smf.c
+ src/libsmf/smf_decode.c
+ src/libsmf/smf_load.c
+ src/libsmf/smf_save.c
+ src/libsmf/smf_tempo.c
+ '''
+ libsmf.export_includes = ['./src/libsmf']
+ libsmf.defines = ['SMF_VERSION="1.2"', 'LIBSMF_DLL_EXPORTS']
+ libsmf.includes = ['./src']
+ libsmf.name = 'libsmf'
+ libsmf.target = 'smf'
+ libsmf.uselib = 'GLIB'
+ libsmf.install_path = None
+ if bld.env['build_target'] != 'mingw':
+ libsmf.cxxflags = [ '-fPIC' ]
+ libsmf.cflags = [ '-fPIC' ]
+
+ lib_source = '''
+ src/Control.cpp
+ src/ControlList.cpp
+ src/ControlSet.cpp
+ src/Curve.cpp
+ src/Event.cpp
+ src/Note.cpp
+ src/SMF.cpp
+ src/Sequence.cpp
+ src/TimeConverter.cpp
+ src/debug.cpp
+ src/types.cpp
+ '''
+
+ # Library
+ if bld.is_defined ('INTERNAL_SHARED_LIBS'):
+ obj = bld.shlib(features = 'c cxx cshlib cxxshlib', source=lib_source)
+ # DLL exports for this library
+ obj.defines = [ 'LIBEVORAL_DLL_EXPORTS' ]
+ else:
+ obj = bld.stlib(features = 'c cxx cstlib cxxstlib', source=lib_source)
+ obj.cxxflags = [ '-fPIC' ]
+ obj.cflags = [ '-fPIC' ]
+ obj.defines = [ ]
+
+ obj.export_includes = ['.']
+ obj.includes = ['.', './src']
+ obj.name = 'libevoral'
+ obj.target = 'evoral'
+ obj.uselib = 'GLIBMM GTHREAD SMF XML LIBPBD'
+ obj.use = 'libsmf libpbd'
+ obj.vnum = EVORAL_LIB_VERSION
+ obj.install_path = bld.env['LIBDIR']
+ obj.defines += [ 'PACKAGE="libevoral"' ]
+
+ if bld.env['BUILD_TESTS'] and bld.is_defined('HAVE_CPPUNIT'):
+ # Static library (for unit test code coverage)
+ obj = bld(features = 'cxx cstlib')
+ obj.source = lib_source
+ obj.export_includes = ['.']
+ obj.includes = ['.', './src']
+ obj.name = 'libevoral_static'
+ obj.target = 'evoral_static'
+ obj.uselib = 'GLIBMM GTHREAD SMF XML LIBPBD'
+ obj.use = 'libsmf libpbd'
+ obj.vnum = EVORAL_LIB_VERSION
+ obj.install_path = ''
+ if bld.env['TEST_COVERAGE']:
+ obj.linkflags = ['--coverage']
+ obj.cflags = ['--coverage']
+ obj.cxxflags = ['--coverage']
+ obj.defines = ['PACKAGE="libevoral"']
+
+ # Unit tests
+ obj = bld(features = 'cxx cxxprogram')
+ obj.source = '''
+ test/SequenceTest.cpp
+ test/SMFTest.cpp
+ test/RangeTest.cpp
+ test/NoteTest.cpp
+ test/CurveTest.cpp
+ test/testrunner.cpp
+ '''
+ obj.includes = ['.', './src']
+ obj.use = 'libevoral_static'
+ obj.uselib = 'CPPUNIT SNDFILE LIBPBD'
+ obj.target = 'run-tests'
+ obj.name = 'libevoral-tests'
+ obj.install_path = ''
+ obj.defines = ['PACKAGE="libevoraltest"']
+ if bld.env['TEST_COVERAGE']:
+ obj.linkflags = ['--coverage']
+ obj.cflags = ['--coverage']
+ obj.cxxflags = ['--coverage']
+
+def test(ctx):
+ autowaf.pre_test(ctx, APPNAME)
+ print(os.getcwd())
+ os.environ['EVORAL_TEST_PATH'] = os.path.abspath('../test/testdata/')
+ autowaf.run_tests(ctx, APPNAME, ['./run-tests'])
+ autowaf.post_test(ctx, APPNAME)
diff --git a/nutemp/t b/nutemp/t
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/nutemp/t
diff --git a/nutemp/t.cc b/nutemp/t.cc
new file mode 100644
index 0000000000..8741377d7b
--- /dev/null
+++ b/nutemp/t.cc
@@ -0,0 +1,1243 @@
+#include "t.h"
+
+using namespace ARDOUR;
+using std::cerr;
+using std::cout;
+using std::endl;
+
+/* overloaded operator* that avoids floating point math when multiplying a superclock position by a number of quarter notes */
+superclock_t operator*(superclock_t sc, Evoral::Beats const & b) { return (sc * ((b.get_beats() * Evoral::Beats::PPQN) + b.get_ticks())) / Evoral::Beats::PPQN; }
+
+Timecode::BBT_Time
+Meter::bbt_add (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & add) const
+{
+ int32_t bars = bbt.bars;
+ int32_t beats = bbt.beats;
+ int32_t ticks = bbt.ticks;
+
+ if ((bars ^ add.bars) < 0) {
+ /* signed-ness varies */
+ if (abs(add.bars) >= abs(bars)) {
+ /* addition will change which side of "zero" the answer is on;
+ adjust bbt.bars towards zero to deal with "unusual" BBT math
+ */
+ if (bars < 0) {
+ bars++;
+ } else {
+ bars--;
+ }
+ }
+ }
+
+ if ((beats ^ add.beats) < 0) {
+ /* signed-ness varies */
+ if (abs (add.beats) >= abs (beats)) {
+ /* adjust bbt.beats towards zero to deal with "unusual" BBT math */
+ if (beats < 0) {
+ beats++;
+ } else {
+ beats--;
+ }
+ }
+ }
+
+ Timecode::BBT_Offset r (bars + add.bars, beats + add.beats, ticks + add.ticks);
+
+ if (r.ticks >= Evoral::Beats::PPQN) {
+ r.beats += r.ticks / Evoral::Beats::PPQN;
+ r.ticks %= Evoral::Beats::PPQN;
+ }
+
+ if (r.beats > _divisions_per_bar) {
+ r.bars += r.beats / _divisions_per_bar;
+ r.beats %= _divisions_per_bar;
+ }
+
+ if (r.beats == 0) {
+ r.beats = 1;
+ }
+
+ if (r.bars == 0) {
+ r.bars = 1;
+ }
+
+ return Timecode::BBT_Time (r.bars, r.beats, r.ticks);
+}
+
+Timecode::BBT_Time
+Meter::bbt_subtract (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & sub) const
+{
+ int32_t bars = bbt.bars;
+ int32_t beats = bbt.beats;
+ int32_t ticks = bbt.ticks;
+
+ if ((bars ^ sub.bars) < 0) {
+ /* signed-ness varies */
+ if (abs (sub.bars) >= abs (bars)) {
+ /* adjust bbt.bars towards zero to deal with "unusual" BBT math */
+ if (bars < 0) {
+ bars++;
+ } else {
+ bars--;
+ }
+ }
+ }
+
+ if ((beats ^ sub.beats) < 0) {
+ /* signed-ness varies */
+ if (abs (sub.beats) >= abs (beats)) {
+ /* adjust bbt.beats towards zero to deal with "unusual" BBT math */
+ if (beats < 0) {
+ beats++;
+ } else {
+ beats--;
+ }
+ }
+ }
+
+ Timecode::BBT_Offset r (bars - sub.bars, beats - sub.beats, ticks - sub.ticks);
+
+ if (r.ticks < 0) {
+ r.beats -= 1 - (r.ticks / Evoral::Beats::PPQN);
+ r.ticks = Evoral::Beats::PPQN + (r.ticks % Evoral::Beats::PPQN);
+ }
+
+ if (r.beats <= 0) {
+ r.bars -= 1 - (r.beats / _divisions_per_bar);
+ r.beats = _divisions_per_bar + (r.beats % _divisions_per_bar);
+ }
+
+ if (r.beats == 0) {
+ r.beats = 1;
+ }
+
+ if (r.bars <= 0) {
+ r.bars -= 1;
+ }
+
+ return Timecode::BBT_Time (r.bars, r.beats, r.ticks);
+}
+
+Timecode::BBT_Offset
+Meter::bbt_delta (Timecode::BBT_Time const & a, Timecode::BBT_Time const & b) const
+{
+ return Timecode::BBT_Offset (a.bars - b.bars, a.beats - b.beats, a.ticks - b.ticks);
+}
+
+Timecode::BBT_Time
+Meter::round_up_to_bar (Timecode::BBT_Time const & bbt) const
+{
+ Timecode::BBT_Time b = bbt.round_up_to_beat ();
+ if (b.beats > 1) {
+ b.bars++;
+ b.beats = 1;
+ }
+ return b;
+}
+
+Evoral::Beats
+Meter::to_quarters (Timecode::BBT_Offset const & offset) const
+{
+ Evoral::Beats b;
+
+ b += (offset.bars * _divisions_per_bar * 4) / _note_value;
+ cerr << offset.bars << " bars as quarters : " << b << " nv = " << (int) _note_value << endl;
+ b += (offset.beats * 4) / _note_value;
+ cerr << offset.beats << " beats as quarters : " << (offset.beats * 4) / _note_value << " nv = " << (int) _note_value << endl;
+ b += Evoral::Beats::ticks (offset.ticks);
+
+ return b;
+}
+
+superclock_t
+TempoMetric::superclock_per_note_type_at_superclock (superclock_t sc) const
+{
+ return superclocks_per_note_type () * expm1 (_c_per_superclock * sc);
+}
+
+superclock_t
+TempoMetric::superclocks_per_grid (framecnt_t sr) const
+{
+ return (superclock_ticks_per_second * Meter::note_value()) / (note_types_per_minute() / Tempo::note_type());
+}
+
+superclock_t
+TempoMetric::superclocks_per_bar (framecnt_t sr) const
+{
+ return superclocks_per_grid (sr) * _divisions_per_bar;
+}
+
+/*
+Ramp Overview
+
+ | *
+Tempo | *
+Tt----|-----------------*|
+Ta----|--------------|* |
+ | * | |
+ | * | |
+ | * | |
+T0----|* | |
+ * | | |
+ _______________|___|____
+ time a t (next tempo)
+ [ c ] defines c
+
+Duration in beats at time a is the integral of some Tempo function.
+In our case, the Tempo function (Tempo at time t) is
+T(t) = T0(e^(ct))
+
+>>1/S(t) = (1/S0)(e^ct) => (1/S)(t) = (e^(ct))/S0 => S(t) = S0/(e^(ct))
+
+with function constant
+c = log(Ta/T0)/a
+
+>>c = log ((1/Sa)/(1/S0)) / a => c = log (S0/Sa) / a
+
+so
+a = log(Ta/T0)/c
+
+>>a = log ((1/Ta)/(1/S0) / c => a = log (S0/Sa) / c
+
+The integral over t of our Tempo function (the beat function, which is the duration in beats at some time t) is:
+b(t) = T0(e^(ct) - 1) / c
+
+>>b(t) = 1/S0(e^(ct) - 1) / c => b(t) = (e^(ct) - 1) / (c * S0)
+
+To find the time t at beat duration b, we use the inverse function of the beat function (the time function) which can be shown to be:
+t(b) = log((c.b / T0) + 1) / c
+
+>>t(b) = log((c*b / (1/S0)) + 1) / c => t(b) = log ((c*b * S0) + 1) / c
+
+The time t at which Tempo T occurs is a as above:
+t(T) = log(T / T0) / c
+
+>> t(1/S) = log ((1/S) / (1/S0) /c => t(1/S) = log (S0/S) / c
+
+The beat at which a Tempo T occurs is:
+b(T) = (T - T0) / c
+
+>> b(1/S) = (1/S - 1/S0) / c
+
+The Tempo at which beat b occurs is:
+T(b) = b.c + T0
+
+>> T(b) = b.c + (1/S0)
+
+We define c for this tempo ramp by placing a new tempo section at some time t after this one.
+Our problem is that we usually don't know t.
+We almost always know the duration in beats between this and the new section, so we need to find c in terms of the beat function.
+Where a = t (i.e. when a is equal to the time of the next tempo section), the beat function reveals:
+t = b log (Ta / T0) / (T0 (e^(log (Ta / T0)) - 1))
+
+By substituting our expanded t as a in the c function above, our problem is reduced to:
+c = T0 (e^(log (Ta / T0)) - 1) / b
+
+>> c = (1/S0) (e^(log ((1/Sa) / (1/S0))) - 1) / b => c = (1/S0) (e^(log (S0/Sa)) - 1) / b => c (e^(log (S0/Sa)) - 1) / (b * S0)
+
+Of course the word 'beat' has been left loosely defined above.
+In music, a beat is defined by the musical pulse (which comes from the tempo)
+and the meter in use at a particular time (how many pulse divisions there are in one bar).
+It would be more accurate to substitute the work 'pulse' for 'beat' above.
+
+ */
+
+/* equation to compute c is:
+ *
+ * c = log (Ta / T0) / a
+ *
+ * where
+ *
+ * a : time into section (from section start
+ * T0 : tempo at start of section
+ * Ta : tempo at time a into section
+ *
+ * THE UNITS QUESTION
+ *
+ * log (Ta / T0) / (time-units) => C is in per-time-units (1/time-units)
+ *
+ * so the question is what are the units of a, and thus c?
+ *
+ * we could use ANY time units (because we can measure a in any time units)
+ * but whichever one we pick dictates how we can use c in the future since
+ * all subsequent computations will need to use the same time units.
+ *
+ * options:
+ *
+ * pulses ... whole notes, possibly useful, since we can use it for any other note_type
+ * quarter notes ... linearly related to pulses
+ * beats ... not a fixed unit of time
+ * minutes ... linearly related to superclocks
+ * samples ... needs sample rate
+ * superclocks ... frequently correct
+ *
+ * so one answer might be to compute c in two different units so that we have both available.
+ *
+ * hence, compute_c_superclocks() and compute_c_pulses()
+ */
+
+void
+TempoMetric::compute_c_superclock (framecnt_t sr, superclock_t end_scpqn, superclock_t superclock_duration)
+{
+ if ((superclocks_per_quarter_note() == end_scpqn) || !ramped()) {
+ _c_per_superclock = 0.0;
+ return;
+ }
+
+ _c_per_superclock = log ((double) superclocks_per_quarter_note () / end_scpqn) / superclock_duration;
+}
+void
+TempoMetric::compute_c_quarters (framecnt_t sr, superclock_t end_scpqn, Evoral::Beats const & quarter_duration)
+{
+ if ((superclocks_per_quarter_note () == end_scpqn) || !ramped()) {
+ _c_per_quarter = 0.0;
+ return;
+ }
+
+ _c_per_quarter = log (superclocks_per_quarter_note () / (double) end_scpqn) / quarter_duration.to_double();
+}
+
+superclock_t
+TempoMetric::superclock_at_qn (Evoral::Beats const & qn) const
+{
+ if (_c_per_quarter == 0.0) {
+ /* not ramped, use linear */
+ return llrint (superclocks_per_quarter_note () * qn.to_double());
+ }
+
+ return llrint (superclocks_per_quarter_note() * (log1p (_c_per_quarter * qn.to_double()) / _c_per_quarter));
+}
+
+Evoral::Beats
+TempoMapPoint::quarters_at (superclock_t sc) const
+{
+ /* This TempoMapPoint must already have a fully computed metric and position */
+
+ if (!ramped()) {
+ return _quarters + Evoral::Beats ((sc - _sclock) / (double) (metric().superclocks_per_quarter_note ()));
+ }
+
+ return _quarters + Evoral::Beats (expm1 (metric().c_per_superclock() * (sc - _sclock)) / (metric().c_per_superclock() * metric().superclocks_per_quarter_note ()));
+}
+
+Evoral::Beats
+TempoMapPoint::quarters_at (Timecode::BBT_Time const & bbt) const
+{
+ /* This TempoMapPoint must already have a fully computed metric and position */
+
+ Timecode::BBT_Offset offset = metric().bbt_delta (bbt, _bbt);
+ cerr << "QA BBT DELTA between " << bbt << " and " << _bbt << " = " << offset << " as quarters for " << static_cast<Meter> (metric()) << " = " << metric().to_quarters (offset) << endl;
+ return _quarters + metric().to_quarters (offset);
+}
+
+Timecode::BBT_Time
+TempoMapPoint::bbt_at (Evoral::Beats const & qn) const
+{
+ /* This TempoMapPoint must already have a fully computed metric and position */
+
+ Evoral::Beats quarters_delta = qn - _quarters;
+ int32_t ticks_delta = quarters_delta.to_ticks (Evoral::Beats::PPQN);
+ return metric().bbt_add (_bbt, Timecode::BBT_Offset (0, 0, ticks_delta));
+}
+
+TempoMap::TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, framecnt_t sr)
+ : _sample_rate (sr)
+{
+ TempoMapPoint tmp (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo), initial_tempo, initial_meter, 0, Evoral::Beats(), Timecode::BBT_Time(), AudioTime);
+ _points.push_back (tmp);
+}
+
+Meter const &
+TempoMap::meter_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return meter_at_locked (sc);
+}
+
+Meter const &
+TempoMap::meter_at (Evoral::Beats const & b) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return meter_at_locked (b);
+}
+
+Meter const &
+TempoMap::meter_at (Timecode::BBT_Time const & bbt) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return meter_at_locked (bbt);
+}
+
+Tempo const &
+TempoMap::tempo_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return tempo_at_locked (sc);
+}
+
+Tempo const &
+TempoMap::tempo_at (Evoral::Beats const &b) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return tempo_at_locked (b);
+}
+
+Tempo const &
+TempoMap::tempo_at (Timecode::BBT_Time const & bbt) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return tempo_at_locked (bbt);
+}
+
+void
+TempoMap::rebuild (superclock_t limit)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ rebuild_locked (limit);
+}
+
+void
+TempoMap::rebuild_locked (superclock_t limit)
+{
+ /* step one: remove all implicit points after a dirty explicit point */
+
+ restart:
+ TempoMapPoints::iterator tmp = _points.begin();
+ TempoMapPoints::iterator first_explicit_dirty = _points.end();
+
+ while ((tmp != _points.end()) && (tmp->is_implicit() || (tmp->is_explicit() && !tmp->dirty ()))) {
+ ++tmp;
+ }
+
+ first_explicit_dirty = tmp;
+
+ /* remove all implicit points, because we're going to recalculate them all */
+
+ while (tmp != _points.end()) {
+ TempoMapPoints::iterator next = tmp;
+ ++next;
+
+ if (tmp->is_implicit()) {
+ _points.erase (tmp);
+ }
+
+ tmp = next;
+ }
+
+ /* compute C for all ramped sections */
+
+ for (tmp = first_explicit_dirty; tmp != _points.end(); ) {
+ TempoMapPoints::iterator nxt = tmp;
+ ++nxt;
+
+ if (tmp->ramped() && (nxt != _points.end())) {
+ tmp->metric().compute_c_quarters (_sample_rate, nxt->metric().superclocks_per_quarter_note (), nxt->quarters() - tmp->quarters());
+ }
+
+ tmp = nxt;
+ }
+
+ TempoMapPoints::iterator prev = _points.end();
+
+ /* Compute correct quarter-note and superclock times for all music-time locked explicit points */
+
+ for (tmp = first_explicit_dirty; tmp != _points.end(); ) {
+
+ TempoMapPoints::iterator next = tmp;
+ ++next;
+
+ if (prev != _points.end()) {
+ if ((tmp->lock_style() == MusicTime)) {
+ /* determine superclock and quarter note time for this (music-time) locked point */
+
+ cerr << "MT-lock, prev = " << *prev << endl;
+
+ Evoral::Beats qn = prev->quarters_at (tmp->bbt());
+ cerr << "MT-lock @ " << tmp->bbt() << " => " << qn << endl;
+ superclock_t sc = prev->sclock() + prev->metric().superclock_at_qn (qn - prev->quarters());
+ cerr << "MT-lock sc is " << prev->metric().superclock_at_qn (qn - prev->quarters()) << " after " << prev->sclock() << " = " << sc
+ << " secs = " << prev->metric().superclock_at_qn (qn - prev->quarters()) / (double) superclock_ticks_per_second
+ << endl;
+
+ if (qn != tmp->quarters() || tmp->sclock() != sc) {
+ cerr << "Ned to move " << *tmp << endl;
+ tmp->set_quarters (qn);
+ tmp->set_sclock (sc);
+ cerr << "using " << *prev << " moved music-time-locked @ " << tmp->bbt() << " to " << sc << " aka " << qn << endl;
+ _points.sort (TempoMapPoint::SuperClockComparator());
+ cerr << "Restart\n";
+ goto restart;
+ }
+ }
+ }
+
+ prev = tmp;
+ tmp = next;
+ }
+
+ /* _points is guaranteed sorted in superclock and quarter note order. It may not be sorted BBT order because of re-ordering
+ * of music-time locked points.
+ */
+
+ cerr << "POST-SORT\n";
+ dump (cerr);
+
+ prev = _points.end();
+
+ /* step two: add new implicit points between each pair of explicit
+ * points, after the dirty explicit point
+ */
+
+ for (tmp = _points.begin(); tmp != _points.end(); ) {
+
+ if (!tmp->dirty()) {
+ ++tmp;
+ continue;
+ }
+
+ TempoMapPoints::iterator next = tmp;
+ ++next;
+
+ if (prev != _points.end()) {
+ if ((tmp->lock_style() == AudioTime)) {
+ cerr << "AT: check " << *tmp << endl
+ << "\t\tusing " << *prev << endl;
+ /* audio-locked explicit point: recompute it's BBT and quarter-note position since this may have changed */
+ tmp->set_quarters (prev->quarters_at (tmp->sclock()));
+ cerr << "AT - recompute quarters at " << tmp->quarters () << endl;
+ if (static_cast<Meter>(tmp->metric()) != static_cast<Meter>(prev->metric())) {
+ /* new meter, must be on bar/measure start */
+ tmp->set_bbt (prev->metric().round_up_to_bar (prev->bbt_at (tmp->quarters())));
+ } else {
+ /* no meter change, tempo change required to be on beat */
+ tmp->set_bbt (prev->bbt_at (tmp->quarters()).round_up_to_beat());
+ }
+ cerr << "AT - recompute bbt at " << tmp->bbt () << endl;
+ }
+ }
+
+ superclock_t sc = tmp->sclock();
+ Evoral::Beats qn (tmp->quarters ());
+ Timecode::BBT_Time bbt (tmp->bbt());
+ const bool ramped = tmp->ramped () && next != _points.end();
+
+ /* Evoral::Beats are really quarter notes. This counts how many quarter notes
+ there are between grid points in this section of the tempo map.
+ */
+ const Evoral::Beats qn_step = (Evoral::Beats (1) * 4) / tmp->metric().note_value();
+
+ /* compute implicit points as far as the next explicit point, or limit,
+ whichever comes first.
+ */
+
+ const superclock_t sc_limit = (next == _points.end() ? limit : (*next).sclock());
+
+ while (1) {
+
+ /* define next beat in superclocks, beats and bbt */
+
+ qn += qn_step;
+ bbt = tmp->metric().bbt_add (bbt, Timecode::BBT_Offset (0, 1, 0));
+
+ if (!ramped) {
+ sc += tmp->metric().superclocks_per_note_type();
+ } else {
+ sc = tmp->sclock() + tmp->metric().superclock_at_qn (qn - tmp->quarters());
+ }
+
+ if (sc >= sc_limit) {
+ break;
+ }
+
+ _points.insert (next, TempoMapPoint (*tmp, sc, qn, bbt));
+ }
+
+ (*tmp).set_dirty (false);
+ prev = tmp;
+ tmp = next;
+ }
+}
+
+bool
+TempoMap::set_tempo (Tempo const & t, superclock_t sc, bool ramp)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ assert (!_points.empty());
+
+ /* special case: first map entry is later than the new point */
+
+ if (_points.front().sclock() > sc) {
+ /* first point is later than sc. There's no iterator to reference a point at or before sc */
+
+ /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats b = _points.front().quarters_at (sc).round_to_beat();
+ Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
+
+ _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitTempo, t, _points.front().metric(), sc, b, bbt, AudioTime, ramp));
+ return true;
+ }
+
+ /* special case #3: only one map entry, at the same time as the new point.
+ This is the common case when editing tempo/meter in a session with a single tempo/meter
+ */
+
+ if (_points.size() == 1 && _points.front().sclock() == sc) {
+ /* change tempo */
+ *((Tempo*) &_points.front().metric()) = t;
+ _points.front().make_explicit (TempoMapPoint::ExplicitTempo);
+ return true;
+ }
+
+ /* Remember: iterator_at() returns an iterator that references the TempoMapPoint at or BEFORE sc */
+
+ TempoMapPoints::iterator i = iterator_at (sc);
+ TempoMapPoints::iterator nxt = i;
+ ++nxt;
+
+ if (i->sclock() == sc) {
+ /* change tempo */
+ *((Tempo*) &i->metric()) = t;
+ i->make_explicit (TempoMapPoint::ExplicitTempo);
+ /* done */
+ return true;
+ }
+
+ if (sc - i->sclock() < i->metric().superclocks_per_note_type()) {
+ cerr << "new tempo too close to previous ...\n";
+ return false;
+ }
+
+ Meter const & meter (i->metric());
+
+ if (i->metric().ramped()) {
+ /* need to adjust ramp constants for preceding explict point, since the new point will be positioned right after it
+ and thus defines the new ramp distance.
+ */
+ i->metric().compute_c_superclock (_sample_rate, t.superclocks_per_quarter_note (), sc);
+ }
+
+ /* determine beats and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats qn = i->quarters_at (sc).round_to_beat();
+
+ /* rule: all Tempo changes must be on-beat. So determine the nearest later beat to "sc"
+ */
+
+ Timecode::BBT_Time bbt = i->bbt_at (qn).round_up_to_beat ();
+
+ /* Modify the iterator to reference the point AFTER this new one, because STL insert is always "insert-before"
+ */
+
+ if (i != _points.end()) {
+ ++i;
+ }
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, t, meter, sc, qn, bbt, AudioTime, ramp));
+ return true;
+}
+
+bool
+TempoMap::set_tempo (Tempo const & t, Timecode::BBT_Time const & bbt, bool ramp)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+
+ /* tempo changes are required to be on-beat */
+
+ Timecode::BBT_Time on_beat = bbt.round_up_to_beat();
+
+ cerr << "Try to set tempo @ " << on_beat << " to " << t << endl;
+
+ assert (!_points.empty());
+
+ if (_points.front().bbt() > on_beat) {
+ cerr << "Cannot insert tempo at " << bbt << " before first point at " << _points.front().bbt() << endl;
+ return false;
+ }
+
+ if (_points.size() == 1 && _points.front().bbt() == on_beat) {
+ /* change Meter */
+ *((Tempo*) &_points.front().metric()) = t;
+ _points.front().make_explicit (TempoMapPoint::ExplicitTempo);
+ return true;
+ }
+
+ TempoMapPoints::iterator i = iterator_at (on_beat);
+
+ if (i->bbt() == on_beat) {
+ *((Tempo*) &i->metric()) = t;
+ i->make_explicit (TempoMapPoint::ExplicitTempo);
+ return true;
+ }
+
+ Meter const & meter (i->metric());
+ ++i;
+
+ /* stick a prototype music-locked point up front and let ::rebuild figure out the superclock and quarter time */
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitTempo, t, meter, 0, Evoral::Beats(), on_beat, MusicTime, ramp));
+ return true;
+}
+
+bool
+TempoMap::set_meter (Meter const & m, Timecode::BBT_Time const & bbt)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ Timecode::BBT_Time measure_start (m.round_up_to_bar (bbt));
+
+ cerr << "Try to set meter @ " << measure_start << " to " << m << endl;
+
+ assert (!_points.empty());
+
+ if (_points.front().bbt() > measure_start) {
+ cerr << "Cannot insert meter at " << bbt << " before first point at " << _points.front().bbt() << endl;
+ return false;
+ }
+
+ if (_points.size() == 1 && _points.front().bbt() == measure_start) {
+ /* change Meter */
+ cerr << "Found the single point\n";
+ *((Meter*) &_points.front().metric()) = m;
+ cerr << "Updated meter to " << m << endl;
+ _points.front().make_explicit (TempoMapPoint::ExplicitMeter);
+ return true;
+ }
+
+ TempoMapPoints::iterator i = iterator_at (measure_start);
+
+ if (i->bbt() == measure_start) {
+ *((Meter*) &i->metric()) = m;
+ cerr << "Updated meter to " << m << endl;
+ i->make_explicit (TempoMapPoint::ExplicitMeter);
+ return true;
+ }
+
+ Evoral::Beats qn = i->quarters_at (measure_start);
+ superclock_t sc = i->sclock() + i->metric().superclock_at_qn (qn);
+
+ Tempo const & tempo (i->metric());
+ ++i;
+
+ cerr << "NEW METER, provisionally @ "
+ << TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, qn, measure_start, MusicTime)
+ << endl;
+
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, qn, measure_start, MusicTime));
+ return true;
+}
+
+bool
+TempoMap::set_meter (Meter const & m, superclock_t sc)
+{
+ Glib::Threads::RWLock::WriterLock lm (_lock);
+ assert (!_points.empty());
+
+ /* special case #2: first map entry is later than the new point */
+
+ if (_points.front().sclock() > sc) {
+ /* determine quarters and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats b = _points.front().quarters_at (sc).round_to_beat();
+ Timecode::BBT_Time bbt = _points.front().bbt_at (b).round_to_beat ();
+
+ _points.insert (_points.begin(), TempoMapPoint (TempoMapPoint::ExplicitMeter, _points.front().metric(), m, sc, b, bbt, AudioTime));
+ return true;
+ }
+
+ /* special case #3: only one map entry, at the same time as the new point.
+
+ This is the common case when editing tempo/meter in a session with a single tempo/meter
+ */
+
+ if (_points.size() == 1 && _points.front().sclock() == sc) {
+ /* change meter */
+ *((Meter*) &_points.front().metric()) = m;
+ _points.front().make_explicit (TempoMapPoint::ExplicitMeter);
+ return true;
+ }
+
+ TempoMapPoints::iterator i = iterator_at (sc);
+
+ if (i->sclock() == sc) {
+ /* change meter */
+ *((Meter*) &i->metric()) = m;
+
+ /* enforce rule described below regarding meter change positions */
+
+ if (i->bbt().beats != 1) {
+ i->set_bbt (Timecode::BBT_Time (i->bbt().bars + 1, 1, 0));
+ }
+
+ i->make_explicit (TempoMapPoint::ExplicitMeter);
+
+ return true;
+ }
+
+ if (sc - i->sclock() < i->metric().superclocks_per_note_type()) {
+ cerr << "new tempo too close to previous ...\n";
+ return false;
+ }
+
+ /* determine quarters and BBT time for this new tempo point. Note that tempo changes (points) must be deemed to be on beat,
+ even if the user moves them later. Even after moving, the TempoMapPoint that was beat N is still beat N, and is not
+ fractional.
+ */
+
+ Evoral::Beats b = i->quarters_at (sc).round_to_beat();
+
+ /* rule: all Meter changes must start a new measure. So determine the nearest, lower beat to "sc". If this is not
+ the first division of the measure, move to the next measure.
+ */
+
+ Timecode::BBT_Time bbt = i->bbt_at (b).round_down_to_beat ();
+
+ if (bbt.beats != 1) {
+ bbt.bars += 1;
+ bbt.beats = 1;
+ bbt.ticks = 0;
+ }
+
+ Tempo const & tempo (i->metric());
+ ++i;
+
+ _points.insert (i, TempoMapPoint (TempoMapPoint::ExplicitMeter, tempo, m, sc, b, bbt, AudioTime));
+ return true;
+}
+
+TempoMapPoints::iterator
+TempoMap::iterator_at (superclock_t sc)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ if (_points.empty()) {
+ throw EmptyTempoMapException();
+ }
+
+ if (_points.size() == 1) {
+ return _points.begin();
+ }
+
+ /* Construct an arbitrary TempoMapPoint. The only property we care about is it's superclock time,
+ so other values used in the constructor are arbitrary and irrelevant.
+ */
+
+ TempoMetric const & metric (_points.front().metric());
+ const TempoMapPoint tp (TempoMapPoint::Flag (0), metric, metric, sc, Evoral::Beats(), Timecode::BBT_Time(), AudioTime);
+ TempoMapPoint::SuperClockComparator scmp;
+
+ TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, scmp);
+
+ if (tmp != _points.begin()) {
+ return --tmp;
+ }
+
+ return tmp;
+}
+
+TempoMapPoints::iterator
+TempoMap::iterator_at (Evoral::Beats const & qn)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ if (_points.empty()) {
+ throw EmptyTempoMapException();
+ }
+
+ if (_points.size() == 1) {
+ return _points.begin();
+ }
+
+ /* Construct an arbitrary TempoMapPoint. The only property we care about is its quarters time,
+ so other values used in the constructor are arbitrary and irrelevant.
+ */
+
+ TempoMetric const & metric (_points.front().metric());
+ const TempoMapPoint tp (TempoMapPoint::Flag (0), metric, metric, 0, qn, Timecode::BBT_Time(), AudioTime);
+ TempoMapPoint::QuarterComparator bcmp;
+
+ TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, bcmp);
+
+ if (tmp != _points.begin()) {
+ return --tmp;
+ }
+
+ return tmp;
+}
+
+TempoMapPoints::iterator
+TempoMap::iterator_at (Timecode::BBT_Time const & bbt)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ if (_points.empty()) {
+ throw EmptyTempoMapException();
+ }
+
+ if (_points.size() == 1) {
+ return _points.begin();
+ }
+
+ /* Construct an arbitrary TempoMapPoint. The only property we care about is its bbt time,
+ so other values used in the constructor are arbitrary and irrelevant.
+ */
+
+ TempoMetric const & metric (_points.front().metric());
+ const TempoMapPoint tp (TempoMapPoint::Flag(0), metric, metric, 0, Evoral::Beats(), bbt, MusicTime);
+ TempoMapPoint::BBTComparator bcmp;
+
+ TempoMapPoints::iterator tmp = upper_bound (_points.begin(), _points.end(), tp, bcmp);
+
+ if (tmp != _points.begin()) {
+ return --tmp;
+ }
+
+ return tmp;
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return bbt_at_locked (sc);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_locked (superclock_t sc) const
+{
+ TempoMapPoint point (const_point_at (sc));
+ Evoral::Beats b ((sc - point.sclock()) / (double) point.metric().superclocks_per_quarter_note());
+ return point.metric().bbt_add (point.bbt(), Timecode::BBT_Offset (0, b.get_beats(), b.get_ticks()));
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at (Evoral::Beats const & qn) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return bbt_at_locked (qn);
+}
+
+Timecode::BBT_Time
+TempoMap::bbt_at_locked (Evoral::Beats const & qn) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ TempoMapPoint const & point (const_point_at (qn));
+ Evoral::Beats delta (qn - point.quarters());
+ return point.metric().bbt_add (point.bbt(), Timecode::BBT_Offset (0, delta.get_beats(), delta.get_ticks()));
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at (superclock_t sc) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return quarter_note_at_locked (sc);
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at_locked (superclock_t sc) const
+{
+ return const_point_at (sc).quarters_at (sc);
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at (Timecode::BBT_Time const & bbt) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return quarter_note_at_locked (bbt);
+}
+
+Evoral::Beats
+TempoMap::quarter_note_at_locked (Timecode::BBT_Time const & bbt) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ TempoMapPoint const & point (const_point_at (bbt));
+
+ Timecode::BBT_Time bbt_delta (point.metric().bbt_subtract (bbt, point.bbt()));
+ /* XXX need to convert the metric division to quarters to match Evoral::Beats == Evoral::Quarters */
+ return point.quarters() + Evoral::Beats ((point.metric().divisions_per_bar() * bbt_delta.bars) + bbt_delta.beats, bbt_delta.ticks);
+}
+
+superclock_t
+TempoMap::superclock_at (Evoral::Beats const & qn) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return superclock_at_locked (qn);
+}
+
+superclock_t
+TempoMap::superclock_at_locked (Evoral::Beats const & qn) const
+{
+ assert (!_points.empty());
+
+ /* only the quarters property matters, since we're just using it to compare */
+ const TempoMapPoint tmp (TempoMapPoint::Flag (0), Tempo (120), Meter (4, 4), 0, qn, Timecode::BBT_Time(), MusicTime);
+ TempoMapPoint::QuarterComparator bcmp;
+
+ TempoMapPoints::const_iterator i = upper_bound (_points.begin(), _points.end(), tmp, bcmp);
+
+ if (i == _points.end()) {
+ --i;
+ }
+
+ /* compute distance from reference point to b. Remember that Evoral::Beats is always interpreted as quarter notes */
+
+ const Evoral::Beats q_delta = qn - i->quarters();
+ superclock_t sclock_delta = i->metric().superclocks_per_quarter_note () * q_delta;
+ return i->sclock() + sclock_delta;
+}
+
+superclock_t
+TempoMap::superclock_at (Timecode::BBT_Time const & bbt) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ return superclock_at_locked (bbt);
+}
+
+superclock_t
+TempoMap::superclock_at_locked (Timecode::BBT_Time const & bbt) const
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ assert (!_points.empty());
+
+ /* only the bbt property matters, since we're just using it to compare */
+ const TempoMapPoint tmp (TempoMapPoint::Flag (0), Tempo (120), Meter (4, 4), 0, Evoral::Beats(), bbt, MusicTime);
+ TempoMapPoint::BBTComparator bcmp;
+
+ TempoMapPoints::const_iterator i = upper_bound (_points.begin(), _points.end(), tmp, bcmp);
+
+ if (i == _points.end()) {
+ --i;
+ }
+
+ /* compute distance from reference point to b. Remember that Evoral::Beats is always interpreted as quarter notes */
+
+ //const Evoral::Beats delta = b - i->beats();
+ //return i->sclock() + samples_to_superclock ((delta / i->metric().quarter_notes_per_minute ()).to_double() * _sample_rate, _sample_rate);
+ return 0;
+}
+
+void
+TempoMap::set_sample_rate (framecnt_t new_sr)
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ double ratio = new_sr / (double) _sample_rate;
+
+ for (TempoMapPoints::iterator i = _points.begin(); i != _points.end(); ++i) {
+ i->map_reset_set_sclock_for_sr_change (llrint (ratio * i->sclock()));
+ }
+}
+ void
+TempoMap::dump (std::ostream& ostr)
+{
+ //Glib::Threads::RWLock::ReaderLock lm (_lock);
+ ostr << "\n\n------------\n";
+ for (TempoMapPoints::iterator i = _points.begin(); i != _points.end(); ++i) {
+ ostr << *i << std::endl;
+ }
+}
+
+void
+TempoMap::remove_explicit_point (superclock_t sc)
+{
+ //Glib::Threads::RWLock::WriterLock lm (_lock);
+ TempoMapPoints::iterator p = iterator_at (sc);
+
+ if (p->sclock() == sc) {
+ _points.erase (p);
+ }
+}
+
+void
+TempoMap::move_explicit (superclock_t current, superclock_t destination)
+{
+ //Glib::Threads::RWLock::WriterLock lm (_lock);
+ TempoMapPoints::iterator p = iterator_at (current);
+
+ if (p->sclock() != current) {
+ return;
+ }
+
+ move_explicit_to (p, destination);
+}
+
+void
+TempoMap::move_explicit_to (TempoMapPoints::iterator p, superclock_t destination)
+{
+ /* CALLER MUST HOLD LOCK */
+
+ TempoMapPoint point (*p);
+ point.set_sclock (destination);
+
+ TempoMapPoints::iterator prev;
+
+ prev = p;
+ if (p != _points.begin()) {
+ --prev;
+ }
+
+ /* remove existing */
+
+ _points.erase (p);
+ prev->set_dirty (true);
+
+ /* find insertion point */
+
+ p = iterator_at (destination);
+
+ /* STL insert semantics are always "insert-before", whereas ::iterator_at() returns iterator-at-or-before */
+ ++p;
+
+ _points.insert (p, point);
+}
+
+void
+TempoMap::move_implicit (superclock_t current, superclock_t destination)
+{
+ //Glib::Threads::RWLock::WriterLock lm (_lock);
+ TempoMapPoints::iterator p = iterator_at (current);
+
+ if (p->sclock() != current) {
+ return;
+ }
+
+ if (p->is_implicit()) {
+ p->make_explicit (TempoMapPoint::Flag (TempoMapPoint::ExplicitMeter|TempoMapPoint::ExplicitTempo));
+ }
+
+ move_explicit_to (p, destination);
+}
+
+/*******/
+
+#define SAMPLERATE 48000
+#define SECONDS_TO_SUPERCLOCK(s) (superclock_ticks_per_second * s)
+
+using std::cerr;
+using std::cout;
+using std::endl;
+
+void
+test_bbt_math ()
+{
+ using namespace Timecode;
+
+ BBT_Time a;
+ BBT_Time b1 (1,1,1919);
+ BBT_Time n1 (-1,1,1919);
+ std::vector<Meter> meters;
+
+ meters.push_back (Meter (4, 4));
+ meters.push_back (Meter (5, 8));
+ meters.push_back (Meter (11, 7));
+ meters.push_back (Meter (3, 4));
+
+#define PRINT_RESULT(m,str,op,op1,Bars,Beats,Ticks) cout << m << ' ' << (op1) << ' ' << str << ' ' << BBT_Offset ((Bars),(Beats),(Ticks)) << " = " << m.op ((op1), BBT_Offset ((Bars), (Beats), (Ticks))) << endl;
+
+ for (std::vector<Meter>::iterator m = meters.begin(); m != meters.end(); ++m) {
+ for (int B = 1; B < 4; ++B) {
+ for (int b = 1; b < 13; ++b) {
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, 0);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, 1);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT((*m), "+", bbt_add, a, B, b, Evoral::Beats::PPQN - 2);
+
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, 0);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, 1);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT((*m), "+", bbt_add, b1, B, b, Evoral::Beats::PPQN - 2);
+
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, 0);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, 1);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT((*m), "+", bbt_add, n1, B, b, Evoral::Beats::PPQN - 2);
+ }
+ }
+
+ for (int B = 1; B < 4; ++B) {
+ for (int b = 1; b < 13; ++b) {
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, 0);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, 1);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN/2);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN - 1);
+ PRINT_RESULT ((*m), "-", bbt_subtract, a, B, b, Evoral::Beats::PPQN - 2);
+ }
+ }
+ }
+}
+
+int
+main ()
+{
+ TempoMap tmap (Tempo (140), Meter (4,4), SAMPLERATE);
+
+ //test_bbt_math ();
+ //return 0;
+
+ tmap.set_tempo (Tempo (7), SECONDS_TO_SUPERCLOCK(7));
+ tmap.set_tempo (Tempo (23), SECONDS_TO_SUPERCLOCK(23));
+ tmap.set_tempo (Tempo (24), SECONDS_TO_SUPERCLOCK(24), true);
+ tmap.set_tempo (Tempo (40), SECONDS_TO_SUPERCLOCK(28), true);
+ tmap.set_tempo (Tempo (100), SECONDS_TO_SUPERCLOCK(100));
+ tmap.set_tempo (Tempo (123), SECONDS_TO_SUPERCLOCK(23));
+
+ tmap.set_meter (Meter (3, 4), SECONDS_TO_SUPERCLOCK(23));
+ tmap.set_meter (Meter (5, 8), SECONDS_TO_SUPERCLOCK(100));
+ tmap.set_meter (Meter (5, 7), SECONDS_TO_SUPERCLOCK(7));
+ tmap.set_meter (Meter (4, 4), SECONDS_TO_SUPERCLOCK(24));
+ tmap.set_meter (Meter (11, 7), SECONDS_TO_SUPERCLOCK(23));
+
+ tmap.set_meter (Meter (3, 8), Timecode::BBT_Time (17, 1, 0));
+
+ tmap.rebuild (SECONDS_TO_SUPERCLOCK (120));
+ tmap.dump (std::cout);
+
+ return 0;
+}
+
+std::ostream&
+operator<<(std::ostream& str, Meter const & m)
+{
+ return str << m.divisions_per_bar() << '/' << m.note_value();
+}
+
+std::ostream&
+operator<<(std::ostream& str, Tempo const & t)
+{
+ return str << t.note_types_per_minute() << " 1/" << t.note_type() << " notes per minute (" << t.superclocks_per_note_type() << " sc-per-1/" << t.note_type() << ')';
+}
+
+std::ostream&
+operator<<(std::ostream& str, TempoMapPoint const & tmp)
+{
+ str << '@' << std::setw (12) << tmp.sclock() << ' ' << tmp.sclock() / (double) superclock_ticks_per_second
+ << (tmp.is_explicit() ? " EXP" : " imp")
+ << " qn " << tmp.quarters ()
+ << " bbt " << tmp.bbt()
+ << " lock to " << tmp.lock_style()
+ ;
+
+ if (tmp.is_explicit()) {
+ str << " tempo " << *((Tempo*) &tmp.metric())
+ << " meter " << *((Meter*) &tmp.metric())
+ ;
+ }
+
+ if (tmp.is_explicit() && tmp.ramped()) {
+ str << " ramp c/sc = " << tmp.metric().c_per_superclock() << " c/qn " << tmp.metric().c_per_quarter();
+ }
+ return str;
+}
diff --git a/nutemp/t.h b/nutemp/t.h
new file mode 100644
index 0000000000..645893f51c
--- /dev/null
+++ b/nutemp/t.h
@@ -0,0 +1,402 @@
+#ifndef __ardour_tempo_h__
+#define __ardour_tempo_h__
+
+#include <list>
+#include <string>
+#include <vector>
+#include <cmath>
+#include <exception>
+
+#include <glibmm/threads.h>
+
+#include "evoral/Beats.hpp"
+
+#include "ardour/ardour.h"
+#include "ardour/superclock.h"
+
+#include "timecode/bbt_time.h"
+
+namespace ARDOUR {
+
+class Meter;
+class TempoMap;
+
+/** Tempo, the speed at which musical time progresses (BPM).
+ */
+
+class LIBARDOUR_API Tempo {
+ public:
+ /**
+ * @param npm Note Types per minute
+ * @param type Note Type (default `4': quarter note)
+ */
+ Tempo (double npm, int type = 4) : _superclocks_per_note_type (double_npm_to_sc (npm)), _note_type (type) {}
+
+ /* these two methods should only be used to show and collect information to the user (for whom
+ * bpm as a floating point number is the obvious representation)
+ */
+ double note_types_per_minute () const { return (superclock_ticks_per_second * 60.0) / _superclocks_per_note_type; }
+ void set_note_types_per_minute (double npm) { _superclocks_per_note_type = double_npm_to_sc (npm); }
+
+ int note_type () const { return _note_type; }
+
+ superclock_t superclocks_per_note_type () const {
+ return _superclocks_per_note_type;
+ }
+ superclock_t superclocks_per_note_type (int note_type) const {
+ return (_superclocks_per_note_type * _note_type) / note_type;
+ }
+ superclock_t superclocks_per_quarter_note () const {
+ return superclocks_per_note_type (4);
+ }
+
+ Tempo& operator=(Tempo const& other) {
+ if (&other != this) {
+ _superclocks_per_note_type = other._superclocks_per_note_type;
+ _note_type = other._note_type;
+ }
+ return *this;
+ }
+
+ protected:
+ superclock_t _superclocks_per_note_type;
+ int8_t _note_type;
+
+ static inline double sc_to_double_npm (superclock_t sc) { return (superclock_ticks_per_second * 60.0) / sc; }
+ static inline superclock_t double_npm_to_sc (double npm) { return llrint ((superclock_ticks_per_second / npm) * 60.0); }
+};
+
+/** Meter, or time signature (subdivisions per bar, and which note type is a single subdivision). */
+class LIBARDOUR_API Meter {
+ public:
+ Meter (int8_t dpb, int8_t nv) : _note_value (nv), _divisions_per_bar (dpb) {}
+
+ int divisions_per_bar () const { return _divisions_per_bar; }
+ int note_value() const { return _note_value; }
+
+ inline bool operator==(const Meter& other) { return _divisions_per_bar == other.divisions_per_bar() && _note_value == other.note_value(); }
+ inline bool operator!=(const Meter& other) { return _divisions_per_bar != other.divisions_per_bar() || _note_value != other.note_value(); }
+
+ Meter& operator=(Meter const & other) {
+ if (&other != this) {
+ _divisions_per_bar = other._divisions_per_bar;
+ _note_value = other._note_value;
+ }
+ return *this;
+ }
+
+ Timecode::BBT_Time bbt_add (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & add) const;
+ Timecode::BBT_Time bbt_subtract (Timecode::BBT_Time const & bbt, Timecode::BBT_Offset const & sub) const;
+ Timecode::BBT_Offset bbt_delta (Timecode::BBT_Time const & bbt, Timecode::BBT_Time const & sub) const;
+
+ Timecode::BBT_Time round_up_to_bar (Timecode::BBT_Time const &) const;
+ Timecode::BBT_Time round_down_to_bar (Timecode::BBT_Time const &) const;
+ Timecode::BBT_Time round_to_bar (Timecode::BBT_Time const &) const;
+
+ Evoral::Beats to_quarters (Timecode::BBT_Offset const &) const;
+
+ protected:
+ /** The type of "note" that a division represents. For example, 4 is
+ a quarter (crotchet) note, 8 is an eighth (quaver) note, etc.
+ */
+ int8_t _note_value;
+ /* how many of '_note_value' make up a bar or measure */
+ int8_t _divisions_per_bar;
+};
+
+/** Helper class to keep track of the Meter *AND* Tempo in effect
+ at a given point in time.
+*/
+class LIBARDOUR_API TempoMetric : public Tempo, public Meter {
+ public:
+ TempoMetric (Tempo const & t, Meter const & m, bool ramp) : Tempo (t), Meter (m), _c_per_quarter (0.0), _c_per_superclock (0.0), _ramped (ramp) {}
+ ~TempoMetric () {}
+
+ double c_per_superclock () const { return _c_per_superclock; }
+ double c_per_quarter () const { return _c_per_quarter; }
+
+ void compute_c_superclock (framecnt_t sr, superclock_t end_superclocks_per_note_type, superclock_t duration);
+ void compute_c_quarters (framecnt_t sr, superclock_t end_superclocks_per_note_type, Evoral::Beats const & duration);
+
+ superclock_t superclocks_per_bar (framecnt_t sr) const;
+ superclock_t superclocks_per_grid (framecnt_t sr) const;
+
+ superclock_t superclock_at_qn (Evoral::Beats const & qn) const;
+ superclock_t superclock_per_note_type_at_superclock (superclock_t) const;
+
+ bool ramped () const { return _ramped; }
+ void set_ramped (bool yn) { _ramped = yn; } /* caller must mark something dirty to force recompute */
+
+ private:
+ double _c_per_quarter;
+ double _c_per_superclock;
+ bool _ramped;
+};
+
+/** Tempo Map - mapping of timecode to musical time.
+ * convert audio-samples, sample-rate to Bar/Beat/Tick, Meter/Tempo
+ */
+
+/* TempoMap concepts
+
+ we have several different ways of talking about time:
+
+ * PULSE : whole notes, just because. These are linearly related to any other
+ note type, so if you know a number of pulses (whole notes), you
+ know the corresponding number of any other note type (e.g. quarter
+ notes).
+
+ * QUARTER NOTES : just what the name says. A lot of MIDI software and
+ concepts assume that a "beat" is a quarter-note.
+
+ * BEAT : a fraction of a PULSE. Defined by the meter in effect, so requires
+ meter (time signature) information to convert to/from PULSE or QUARTER NOTES.
+ In a 5/8 time, a BEAT is 1/8th note. In a 4/4 time, a beat is quarter note.
+ This means that measuring time in BEATS is potentially non-linear (if
+ the time signature changes, there will be a different number of BEATS
+ corresponding to a given time in any other unit).
+
+ * SUPERCLOCK : a very high resolution clock whose frequency
+ has as factors all common sample rates and all common note
+ type divisors. Related to MINUTES or SAMPLES only when a
+ sample rate is known. Related to PULSE or QUARTER NOTES only
+ when a tempo is known.
+
+ * MINUTES : wallclock time measurement. related to SAMPLES or SUPERCLOCK
+ only when a sample rate is known.
+
+
+ * SAMPLES : audio time measurement. Related to MINUTES or SUPERCLOCK only
+ when a sample rate is known
+
+ * BBT : bars|beats|ticks ... linearly related to BEATS but with the added
+ semantics of bars ("measures") added, in which beats are broken up
+ into groups of bars ("measures"). Requires meter (time signature)
+ information to compute to/from a given BEATS value. Contains no
+ additional time information compared to BEATS, but does have
+ additional semantic information.
+
+ Nick sez: not every note onset is on a tick
+ Paul wonders: if it's 8 samples off, does it matter?
+ Nick sez: it should not phase with existing audio
+
+ */
+
+class LIBARDOUR_API TempoMapPoint
+{
+ public:
+ enum Flag {
+ ExplicitTempo = 0x1,
+ ExplicitMeter = 0x2,
+ };
+
+ TempoMapPoint (Flag f, Tempo const& t, Meter const& m, superclock_t sc, Evoral::Beats const & q, Timecode::BBT_Time const & bbt, PositionLockStyle psl, bool ramp = false)
+ : _flags (f), _explicit (t, m, psl, ramp), _sclock (sc), _quarters (q), _bbt (bbt), _dirty (true) {}
+ TempoMapPoint (TempoMapPoint const & tmp, superclock_t sc, Evoral::Beats const & q, Timecode::BBT_Time const & bbt)
+ : _flags (Flag (0)), _reference (&tmp), _sclock (sc), _quarters (q), _bbt (bbt), _dirty (true) {}
+ ~TempoMapPoint () {}
+
+ bool is_explicit() const { return _flags != Flag (0); }
+ bool is_implicit() const { return _flags == Flag (0); }
+
+ superclock_t superclocks_per_note_type (int8_t note_type) const {
+ if (is_explicit()) {
+ return _explicit.metric.superclocks_per_note_type (note_type);
+ }
+ return _reference->superclocks_per_note_type (note_type);
+ }
+
+ struct BadTempoMetricLookup : public std::exception {
+ virtual const char* what() const throw() { return "cannot obtain non-const Metric from implicit map point"; }
+ };
+
+ bool dirty() const { return _dirty; }
+
+ superclock_t sclock() const { return _sclock; }
+ Evoral::Beats const & quarters() const { return _quarters; }
+ Timecode::BBT_Time const & bbt() const { return _bbt; }
+ bool ramped() const { return metric().ramped(); }
+ TempoMetric const & metric() const { return is_explicit() ? _explicit.metric : _reference->metric(); }
+ /* Implicit points are not allowed to return non-const references to their reference metric */
+ TempoMetric & metric() { if (is_explicit()) { return _explicit.metric; } throw BadTempoMetricLookup(); }
+ PositionLockStyle lock_style() const { return is_explicit() ? _explicit.lock_style : _reference->lock_style(); }
+
+ /* None of these properties can be set for an Implicit point, because
+ * they are determined by the TempoMapPoint pointed to by _reference.
+ */
+
+ void set_sclock (superclock_t sc) { if (is_explicit()) { _sclock = sc; _dirty = true; } }
+ void set_quarters (Evoral::Beats const & q) { if (is_explicit()) { _quarters = q; _dirty = true; } }
+ void set_bbt (Timecode::BBT_Time const & bbt) { if (is_explicit()) { _bbt = bbt; _dirty = true; } }
+ void set_dirty (bool yn) { if (is_explicit()) { _dirty = yn; } }
+ void set_lock_style (PositionLockStyle psl) { if (is_explicit()) { _explicit.lock_style = psl; _dirty = true; } }
+
+ void make_explicit (Flag f) {
+ _flags = Flag (_flags|f);
+ /* since _metric and _reference are part of an anonymous union,
+ avoid possible compiler glitches by copying to a stack
+ variable first, then assign.
+ */
+ TempoMetric tm (_explicit.metric);
+ _explicit.metric = tm;
+ _dirty = true;
+ }
+
+ void make_implicit (TempoMapPoint & tmp) { _flags = Flag (0); _reference = &tmp; }
+
+ Evoral::Beats quarters_at (superclock_t sc) const;
+ Evoral::Beats quarters_at (Timecode::BBT_Time const &) const;
+
+ Timecode::BBT_Time bbt_at (Evoral::Beats const &) const;
+
+#if 0
+ XMLNode& get_state() const;
+ int set_state (XMLNode const&, int version);
+#endif
+
+ struct SuperClockComparator {
+ bool operator() (TempoMapPoint const & a, TempoMapPoint const & b) const { return a.sclock() < b.sclock(); }
+ };
+
+ struct QuarterComparator {
+ bool operator() (TempoMapPoint const & a, TempoMapPoint const & b) const { return a.quarters() < b.quarters(); }
+ };
+
+ struct BBTComparator {
+ bool operator() (TempoMapPoint const & a, TempoMapPoint const & b) const { return a.bbt() < b.bbt(); }
+ };
+
+ protected:
+ friend class TempoMap;
+ void map_reset_set_sclock_for_sr_change (superclock_t sc) { _sclock = sc; }
+
+ private:
+ struct ExplicitInfo {
+ ExplicitInfo (Tempo const & t, Meter const & m, PositionLockStyle psl, bool ramp) : metric (t, m, ramp), lock_style (psl) {}
+
+ TempoMetric metric;
+ PositionLockStyle lock_style;
+ };
+
+ Flag _flags;
+ union {
+ TempoMapPoint const * _reference;
+ ExplicitInfo _explicit;
+ };
+ superclock_t _sclock;
+ Evoral::Beats _quarters;
+ Timecode::BBT_Time _bbt;
+ bool _dirty;
+};
+
+typedef std::list<TempoMapPoint> TempoMapPoints;
+
+class LIBARDOUR_API TempoMap
+{
+ public:
+ TempoMap (Tempo const & initial_tempo, Meter const & initial_meter, framecnt_t sr);
+
+ void set_sample_rate (framecnt_t sr);
+ framecnt_t sample_rate() const { return _sample_rate; }
+
+ void remove_explicit_point (superclock_t);
+
+ void move_implicit (superclock_t current, superclock_t destination);
+ void move_explicit (superclock_t current, superclock_t destination);
+
+ //bool set_tempo_at (Tempo const &, Evoral::Beats const &, PositionLockStyle psl, bool ramp = false);
+ bool set_tempo (Tempo const &, Timecode::BBT_Time const &, bool ramp = false);
+ bool set_tempo (Tempo const &, superclock_t, bool ramp = false);
+
+ //bool set_meter_at (Meter const &, Evoral::Beats const &);
+
+ bool set_meter (Meter const &, Timecode::BBT_Time const &);
+ bool set_meter (Meter const &, superclock_t);
+
+ Meter const & meter_at (superclock_t sc) const;
+ Meter const & meter_at (Evoral::Beats const & b) const;
+ Meter const & meter_at (Timecode::BBT_Time const & bbt) const;
+ Tempo const & tempo_at (superclock_t sc) const;
+ Tempo const & tempo_at (Evoral::Beats const &b) const;
+ Tempo const & tempo_at (Timecode::BBT_Time const & bbt) const;
+
+ Timecode::BBT_Time bbt_at (superclock_t sc) const;
+ Timecode::BBT_Time bbt_at (Evoral::Beats const &) const;
+ Evoral::Beats quarter_note_at (superclock_t sc) const;
+ Evoral::Beats quarter_note_at (Timecode::BBT_Time const &) const;
+ superclock_t superclock_at (Evoral::Beats const &) const;
+ superclock_t superclock_at (Timecode::BBT_Time const &) const;
+
+ struct EmptyTempoMapException : public std::exception {
+ virtual const char* what() const throw() { return "TempoMap is empty"; }
+ };
+
+ void dump (std::ostream&);
+ void rebuild (superclock_t limit);
+
+ private:
+ TempoMapPoints _points;
+ framecnt_t _sample_rate;
+ mutable Glib::Threads::RWLock _lock;
+
+ /* these return an iterator that refers to the TempoMapPoint at or most immediately preceding the given position.
+ *
+ * Conceptually, these could be const methods, but C++ prevents them returning a non-const iterator in that case.
+ *
+ * Note that they cannot return an invalid iterator (e.g. _points.end()) because:
+ *
+ * - if the map is empty, an exception is thrown
+ * - if the given time is before the first map entry, _points.begin() is returned
+ * - if the given time is after the last map entry, the equivalent of _points.rbegin() is returned
+ * - if the given time is within the map entries, a valid iterator will be returned
+ */
+
+ TempoMapPoints::iterator iterator_at (superclock_t sc);
+ TempoMapPoints::iterator iterator_at (Evoral::Beats const &);
+ TempoMapPoints::iterator iterator_at (Timecode::BBT_Time const &);
+
+ TempoMapPoints::const_iterator const_iterator_at (superclock_t sc) const { return const_cast<TempoMap*>(this)->iterator_at (sc); }
+ TempoMapPoints::const_iterator const_iterator_at (Evoral::Beats const & b) const { return const_cast<TempoMap*>(this)->iterator_at (b); }
+ TempoMapPoints::const_iterator const_iterator_at (Timecode::BBT_Time const & bbt) const { return const_cast<TempoMap*>(this)->iterator_at (bbt); }
+
+ /* Returns the TempoMapPoint at or most immediately preceding the given time. If the given time is
+ * before the first map entry, then the first map entry will be returned, which underlies the semantics
+ * that the first map entry's values propagate backwards in time if not at absolute zero.
+ *
+ * As for iterator_at(), define both const+const and non-const variants, because C++ won't let us return a non-const iterator
+ from a const method (which is a bit silly, but presumably aids compiler reasoning).
+ */
+
+ TempoMapPoint & point_at (superclock_t sc) { return *iterator_at (sc); }
+ TempoMapPoint & point_at (Evoral::Beats const & b) { return *iterator_at (b); }
+ TempoMapPoint & point_at (Timecode::BBT_Time const & bbt) { return *iterator_at (bbt); }
+
+ TempoMapPoint const & const_point_at (superclock_t sc) const { return *const_iterator_at (sc); }
+ TempoMapPoint const & const_point_at (Evoral::Beats const & b) const { return *const_iterator_at (b); }
+ TempoMapPoint const & const_point_at (Timecode::BBT_Time const & bbt) const { return *const_iterator_at (bbt); }
+
+ Meter const & meter_at_locked (superclock_t sc) const { return const_point_at (sc).metric(); }
+ Meter const & meter_at_locked (Evoral::Beats const & b) const { return const_point_at (b).metric(); }
+ Meter const & meter_at_locked (Timecode::BBT_Time const & bbt) const { return const_point_at (bbt).metric(); }
+ Tempo const & tempo_at_locked (superclock_t sc) const { return const_point_at (sc).metric(); }
+ Tempo const & tempo_at_locked (Evoral::Beats const &b) const { return const_point_at (b).metric(); }
+ Tempo const & tempo_at_locked (Timecode::BBT_Time const & bbt) const { return const_point_at (bbt).metric(); }
+ Timecode::BBT_Time bbt_at_locked (superclock_t sc) const;
+ Timecode::BBT_Time bbt_at_locked (Evoral::Beats const &) const;
+ Evoral::Beats quarter_note_at_locked (superclock_t sc) const;
+ Evoral::Beats quarter_note_at_locked (Timecode::BBT_Time const &) const;
+ superclock_t superclock_at_locked (Evoral::Beats const &) const;
+ superclock_t superclock_at_locked (Timecode::BBT_Time const &) const;
+
+ void move_explicit_to (TempoMapPoints::iterator, superclock_t destination);
+
+ void rebuild_locked (superclock_t limit);
+};
+
+}
+
+std::ostream& operator<<(std::ostream&, ARDOUR::TempoMapPoint const &);
+std::ostream& operator<<(std::ostream&, ARDOUR::Tempo const &);
+std::ostream& operator<<(std::ostream&, ARDOUR::Meter const &);
+
+#endif /* __ardour_tempo_h__ */