From cba53a20233531ef3e6c3692993eac8f74e991a1 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Wed, 13 Sep 2017 19:31:42 -0400 Subject: add _locked() variants to new tempo experiment --- gtk2_ardour/editor_ops.cc.orig | 8224 +++++++++++++++++++++++++++++++++ gtk2_ardour/midi_region_view.cc.orig | 4428 ++++++++++++++++++ gtk2_ardour/midi_region_view.cc.rej | 11 + gtk2_ardour/out | 5182 +++++++++++++++++++++ libs/ardour/ardour/midi_model.h.orig | 334 ++ libs/ardour/ardour/midi_source.h.orig | 258 ++ libs/ardour/midi_source.cc.orig | 580 +++ libs/evoral/evoral/Beats.hpp.orig | 247 + libs/evoral/test/BeatsTest.cpp | 170 + libs/evoral/test/BeatsTest.hpp | 22 + libs/evoral/wscript.orig | 168 + nutemp/t | 0 nutemp/t.cc | 1243 +++++ nutemp/t.h | 402 ++ 14 files changed, 21269 insertions(+) create mode 100644 gtk2_ardour/editor_ops.cc.orig create mode 100644 gtk2_ardour/midi_region_view.cc.orig create mode 100644 gtk2_ardour/midi_region_view.cc.rej create mode 100644 gtk2_ardour/out create mode 100644 libs/ardour/ardour/midi_model.h.orig create mode 100644 libs/ardour/ardour/midi_source.h.orig create mode 100644 libs/ardour/midi_source.cc.orig create mode 100644 libs/evoral/evoral/Beats.hpp.orig create mode 100644 libs/evoral/test/BeatsTest.cpp create mode 100644 libs/evoral/test/BeatsTest.hpp create mode 100644 libs/evoral/wscript.orig create mode 100644 nutemp/t create mode 100644 nutemp/t.cc create mode 100644 nutemp/t.h 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 + +#include +#include +#include +#include +#include +#include + +#include + +#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 > used_playlists; + list 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 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 (&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 region_added_connections; + + for (list::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 >::iterator i = used_playlists.begin(); + (*i)->thaw(); + used_playlists.pop_front(); + } + + for (vector::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 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(*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 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(*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 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 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 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 interesting_points; + boost::shared_ptr 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 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::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::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 +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 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 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 (*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 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 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 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 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 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 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 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(*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(*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 rl = _session->get_routes(); + for (RouteList::iterator r = rl->begin(); r != rl->end(); ++r) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*r); + if (tr) { + boost::shared_ptr pl = tr->playlist(); + if (pl) { + pair 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(*(_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(*(_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(*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(*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(*(_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 = (*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(*(_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 = 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(*(_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(*(_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(*(_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(*(_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; + + if (clicked_routeview != 0) { + tv = clicked_routeview; + } else if (!selection->tracks.empty()) { + if ((tv = dynamic_cast(selection->tracks.front())) == 0) { + return; + } + } else if (entered_track != 0) { + if ((tv = dynamic_cast(entered_track)) == 0) { + return; + } + } else { + return; + } + + if ((playlist = tv->playlist()) == 0) { + return; + } + + boost::shared_ptr 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()); + + _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 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 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 > playlists = selection->regions.playlists (); + for (set >::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 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 >::iterator i = playlists.begin(); i != playlists.end(); ++i) { + vector 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) +{ + _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 current; + boost::shared_ptr 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 (RegionFactory::create (current, plist)); + } +} + +void +Editor::create_region_from_selection (vector >& 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 current; + boost::shared_ptr 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 > v; + + for (list::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; + 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 ((*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::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 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; + 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 playlists; + + RegionSelection rs; + + rs = get_regions_from_selection_and_entered(); + + if (!_session || rs.empty()) { + return; + } + + begin_reversible_command (_("separate region under")); + + list > 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 >::iterator rl = regions_to_remove.begin(); rl != regions_to_remove.end(); ++rl) { + + boost::shared_ptr playlist = (*rl)->playlist(); + + if (!playlist) { + // is this check necessary? + continue; + } + + vector::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::iterator pl; + + for (pl = playlists.begin(); pl != playlists.end(); ++pl) { + (*pl).playlist->thaw (); + _session->add_command(new MementoCommand(*(*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::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 > playlists; + boost::shared_ptr 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 ((*i)); + + if (!rtv) { + continue; + } + + boost::shared_ptr 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 >::iterator i = playlists.begin(); i != playlists.end(); ++i) { + + /* Only the top regions at start and end have to be cropped */ + boost::shared_ptr region_at_start = (*i)->top_region_at(start); + boost::shared_ptr region_at_end = (*i)->top_region_at(end); + + vector > 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 >::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; + 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 r ((*i)->region()); + + TimeAxisView& tv = (*i)->get_time_axis_view(); + RouteTimeAxisView* rtv = dynamic_cast (&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 ((*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 sorted; + rs.by_position (sorted); + + boost::shared_ptr 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::iterator i = sorted.begin(); i != sorted.end(); ++i) { + + boost::shared_ptr 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, 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, 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::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 (&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 next_region; + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + + AudioRegionView* arv = dynamic_cast (*x); + + if (!arv) { + continue; + } + + AudioTimeAxisView* atav = dynamic_cast (&arv->get_time_axis_view()); + + if (!atav) { + continue; + } + + boost::shared_ptr region = arv->region(); + boost::shared_ptr 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(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 (_("%1\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 (*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 (*i); + + if (!rtv) { + continue; + } + + boost::shared_ptr playlist; + + if ((playlist = rtv->playlist()) == 0) { + continue; + } + + InterThreadInfo itt; + + playlist->clear_changes (); + playlist->clear_owned_changes (); + + boost::shared_ptr 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(), false); + } + + if (!r) { + continue; + } + + if (replace) { + list 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 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 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, 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 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::max(), 0); + for (PointSelection::iterator sel_point = selection->points.begin(); sel_point != selection->points.end(); ++sel_point) { + boost::shared_ptr 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::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 &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 al = line.the_list(); + + bool erase = true; + + if (dynamic_cast (&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 al = i->first; + al->thaw (); + _session->add_command (new MementoCommand (*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(*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 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 = 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()); + + /* We might have removed regions, which alters other regions' layering_index, + so we need to do a recursive diff here. + */ + vector 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 > 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 > playlists; + + for (list >::iterator rl = regions_to_remove.begin(); rl != regions_to_remove.end(); ++rl) { + + boost::shared_ptr 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()); + + } + + vector >::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 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 pmap; + + framepos_t first_position = max_framepos; + + typedef set > 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 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::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 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 npl; + RegionSelection::iterator tmp; + + tmp = x; + ++tmp; + + if (op != Delete) { + + vector::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 r = (*x)->region(); + boost::shared_ptr _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()); + 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()); + 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()); + break; + } + + x = tmp; + } + + if (op != Delete) { + + list > foo; + + /* the pmap is in the same order as the tracks in which selected regions occurred */ + + for (vector::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 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(*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(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; + 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 r ((*i)->region()); + + TimeAxisView& tv = (*i)->get_time_axis_view(); + RouteTimeAxisView* rtv = dynamic_cast (&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; + + 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->clear_changes (); + playlist->clear (); + _session->add_command (new StatefulDiffCommand (playlist)); +} + +void +Editor::nudge_track (bool use_edit, bool forwards) +{ + boost::shared_ptr 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 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 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 max_amps; + list 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 (*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::const_iterator a = max_amps.begin (); + list::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 (*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(*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(*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(*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 audio_only; + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + AudioRegionView* const arv = dynamic_cast (*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::Notes selected; + mrv.selection_as_notelist (selected, true); + + vector::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 (*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(*r); + + if (mrv) { + try { + boost::shared_ptr playlist = mrv->region()->playlist(); + boost::shared_ptr new_source = _session->create_midi_source_by_stealing_name (mrv->midi_view()->track()); + boost::shared_ptr 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 (rs.front ()); + + Evoral::PatchChange 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 (*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(*r); + if (arv) { + boost::shared_ptr 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 >::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 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(*i); + if (arv) { + boost::shared_ptr 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(*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 (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(*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(*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 a = Glib::RefPtr::cast_dynamic (_region_actions->get_action("toggle-region-lock-style")); + vector proxies = a->get_proxies(); + Gtk::CheckMenuItem* cmi = dynamic_cast (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(*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 cl (new ControlList); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + StripableTimeAxisView *stav = dynamic_cast(*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 cl (new ControlList); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + StripableTimeAxisView *stav = dynamic_cast(*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 (*x); + + if (!tmp) { + continue; + } + + boost::shared_ptr 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(*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 (*x); + + if (!tmp) { + continue; + } + + boost::shared_ptr 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(*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 (*x); + + if (!tmp) { + continue; + } + + boost::shared_ptr 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(*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 (*x); + + if (!tmp) { + continue; + } + + + boost::shared_ptr 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 (*x); + + if (!tmp) { + continue; + } + + boost::shared_ptr 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 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((*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((*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(*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(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(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(*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 (*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 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(_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 ar = boost::dynamic_pointer_cast ((*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 r, AnalysisFeatureList& positions, bool can_ferret, bool select_new) +{ + bool use_rhythmic_rodent = false; + + boost::shared_ptr pl = r->playlist(); + + list > 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 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 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 cmds; + pl->rdiff (cmds); + _session->add_commands (cmds); + + _session->add_command (new StatefulDiffCommand (pl)); + + if (select_new) { + + for (list >::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 (item); + assert (_line); + + AudioRegionView* _arv = reinterpret_cast (item->get_data ("regionview")); + _arv->remove_transient (*(float*) _line->get_data ("position")); +} + +void +Editor::snap_regions_to_grid () +{ + list > 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 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 >::iterator i = used_playlists.begin(); + (*i)->thaw(); + used_playlists.pop_front(); + } + + commit_reversible_command (); +} + +void +Editor::close_region_gaps () +{ + list > 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 last_region; + + rs.sort_by_position_and_track(); + + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) { + + boost::shared_ptr 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 >::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 (*t); + + if (rtv) { + boost::shared_ptr tr = rtv->track(); + if (tr) { + boost::shared_ptr 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(*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 choices; + string prompt; + int ntracks = 0; + int nbusses = 0; + int nvcas = 0; + const char* trackstr; + const char* busstr; + const char* vcastr; + vector > routes; + vector > vcas; + bool special_bus = false; + + for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) { + VCATimeAxisView* vtv = dynamic_cast (*x); + if (vtv) { + vcas.push_back (vtv->vca()); + ++nvcas; + continue; + } + RouteTimeAxisView* rtv = dynamic_cast (*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 rl (new RouteList); + for (vector >::iterator x = routes.begin(); x != routes.end(); ++x) { + rl->push_back (*x); + } + _session->remove_routes (rl); + + for (vector >::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 > pl; + + if (all_playlists) { + RouteTimeAxisView* rtav = dynamic_cast (*x); + if (rtav && rtav->track ()) { + vector > all = _session->playlists->playlists_for_track (rtav->track ()); + for (vector >::iterator p = all.begin(); p != all.end(); ++p) { + pl.insert (*p); + } + } + } else { + if ((*x)->playlist ()) { + pl.insert ((*x)->playlist ()); + } + } + + for (set >::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 cmds; + (*i)->rdiff (cmds); + _session->add_commands (cmds); + + _session->add_command (new StatefulDiffCommand (*i)); + } + + /* automation */ + RouteTimeAxisView* rtav = dynamic_cast (*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(*_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(_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 pl = (*x)->playlist(); + + if (pl) { + + XMLNode &before = pl->get_state(); + + if (!in_command) { + begin_reversible_command (_("remove time")); + in_command = true; + } + + std::list 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 (*pl, &before, &after)); + } + + /* automation */ + RouteTimeAxisView* rtav = dynamic_cast (*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 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::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(*_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(_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 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 RTVS; + RTVS tracks; + + if (selection->regions.empty()) { + return; + } + + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + RouteTimeAxisView* rtv = dynamic_cast(&(*i)->get_time_axis_view()); + + if (rtv) { + tracks.insert (rtv); + } + } + + begin_reversible_command (_("combine regions")); + + vector 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::iterator i = new_selection.begin(); i != new_selection.end(); ++i) { + selection->add (*i); + } + + commit_reversible_command (); +} + +void +Editor::uncombine_regions () +{ + typedef set RTVS; + RTVS tracks; + + if (selection->regions.empty()) { + return; + } + + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + RouteTimeAxisView* rtv = dynamic_cast(&(*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 rl (new RouteList); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + RouteTimeAxisView *rtav = dynamic_cast(*i); + + if (!rtav) { + continue; + } + + boost::shared_ptr 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 +#include +#include + +#include + +#include "gtkmm2ext/gtk_ui.h" + +#include + +#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 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 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 region) + : RegionView (other, boost::shared_ptr (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 (&trackview); + return route_ui->route()->instrument_info(); +} + +const boost::shared_ptr +MidiRegionView::midi_region() const +{ + return boost::dynamic_pointer_cast(_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 (&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), group, this), (GdkEvent *) ev); + } else { + editor->drags()->set (new NoteCreateDrag (dynamic_cast (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), 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), 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(&trackview); + MidiStreamView* const view = mtv->midi_view(); + boost::shared_ptr mr = boost::dynamic_pointer_cast (_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 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::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { + if ((gr = dynamic_cast(*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 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 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 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 +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(); +} + +boost::shared_ptr +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(); +} + +void +MidiRegionView::get_events (Events& e, Evoral::Sequence::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 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::iterator j = ghosts.begin(); j != ghosts.end(); ++j) { + MidiGhostRegion* gr = dynamic_cast (*j); + if (gr) { + gr->remove_note (cne); + } + } + + delete cne; + i = _events.erase (i); + + } else { + bool visible = cne->item()->visible(); + + if ((sus = dynamic_cast(cne))) { + + if (visible) { + update_sustained (sus); + } + + } else if ((hit = dynamic_cast(cne))) { + + if (visible) { + update_hit (hit); + } + + } + ++i; + } + } + } + + for (MidiModel::Notes::iterator n = missing_notes.begin(); n != missing_notes.end(); ++n) { + boost::shared_ptr 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::iterator it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { + if ((*it) == note->id()) { + add_to_selection (cne); + } + } + } + + for (vector::iterator j = ghosts.begin(); j != ghosts.end(); ++j) { + MidiGhostRegion* gr = dynamic_cast (*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(&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 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 = find_canvas_sys_ex (sysex_ptr); + + if (!sysex) { + sysex = boost::shared_ptr( + 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(&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 note) +{ + if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) { + return; + } + + RouteUI* route_ui = dynamic_cast (&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 note) +{ + const std::vector< boost::shared_ptr > notes(1, note); + start_playing_midi_chord(notes); +} + +void +MidiRegionView::start_playing_midi_chord (vector > notes) +{ + if (_no_sound_notes || !UIConfiguration::instance().get_sound_midi_notes()) { + return; + } + + RouteUI* route_ui = dynamic_cast (&trackview); + + if (!route_ui || !route_ui->midi_track()) { + return; + } + + NotePlayer* player = new NotePlayer (route_ui->midi_track()); + + for (vector >::iterator n = notes.begin(); n != notes.end(); ++n) { + player->add (*n); + } + + player->play (); +} + + +bool +MidiRegionView::note_in_region_range (const boost::shared_ptr note, bool& visible) const +{ + const boost::shared_ptr 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))) { + update_sustained(sus, update_ghost_regions); + } else if ((hit = dynamic_cast(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 mr = midi_region(); + boost::shared_ptr 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 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 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::iterator g = ghosts.begin(); g != ghosts.end(); ++g) { + if ((gr = dynamic_cast(*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(&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 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(&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 patch_change = boost::shared_ptr( + 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 & 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 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 ( + 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 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 notes) +{ + NoteBase* cne; + list::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 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 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 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(*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 > PossibleChord; + Editor* editor = dynamic_cast (&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(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 (*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 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 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 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 > PossibleChord; + Editor* editor = dynamic_cast (&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(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 (*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 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 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 (*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::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::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::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(&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(&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(&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 (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 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(&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(&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(&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 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(&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(&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::PitchGreaterThanOrEqual, (uint8_t) floor (note), chn_mask); + } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + get_events (e, Evoral::Sequence::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 w) +{ + if (!_active_notes) { + /* we aren't actively being recorded to */ + return; + } + + boost::shared_ptr src = w.lock (); + if (!src || src != midi_region()->midi_source()) { + /* recorded data was not destined for our source */ + return; + } + + MidiTimeAxisView* mtv = dynamic_cast (&trackview); + + boost::shared_ptr 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& 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 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 n, uint8_t note_value) const +{ + using namespace MIDI::Name; + std::string name; + + MidiTimeAxisView* mtv = dynamic_cast(&trackview); + if (mtv) { + boost::shared_ptr 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 current_note, + uint8_t new_value) const +{ + MidiTimeAxisView* mtv = dynamic_cast(&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 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 (&trackview); + return rtav->midi_track()->get_playback_channel_mode(); +} + +uint16_t +MidiRegionView::get_selected_channels () const +{ + RouteTimeAxisView* rtav = dynamic_cast (&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 > PossibleChord; + PossibleChord to_play; +- Evoral::Beats earliest = Evoral::MaxBeats; ++ Evoral::Beats earliest = std::numeric_limits::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 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 +#include +#include + +#include +#include + +#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 { +public: + typedef Evoral::Beats TimeType; + + MidiModel (boost::shared_ptr); + + 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 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 model() const { return _model; } + + protected: + boost::shared_ptr _model; + const std::string _name; + + }; + + class LIBARDOUR_API NoteDiffCommand : public DiffCommand { + public: + + NoteDiffCommand (boost::shared_ptr m, const std::string& name) : DiffCommand (m, name) {} + NoteDiffCommand (boost::shared_ptr 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 ChangeList; + typedef std::list< boost::shared_ptr< Evoral::Note > > 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 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 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 >, TimeType); + + private: + struct Change { + Change () : sysex_id (0) {} + boost::shared_ptr > sysex; + gint sysex_id; + SysExDiffCommand::Property property; + TimeType old_time; + TimeType new_time; + }; + + typedef std::list ChangeList; + ChangeList _changes; + + std::list _removed; + + XMLNode & marshal_change (const Change &); + Change unmarshal_change (XMLNode *); + }; + + class LIBARDOUR_API PatchChangeDiffCommand : public DiffCommand { + public: + PatchChangeDiffCommand (boost::shared_ptr, const std::string &); + PatchChangeDiffCommand (boost::shared_ptr, 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 ChangeList; + ChangeList _changes; + + std::list _added; + std::list _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 source, + const Glib::Threads::Mutex::Lock& source_lock); + + bool write_section_to(boost::shared_ptr 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 ContentsChanged; + PBD::Signal1 ContentsShifted; + + boost::shared_ptr midi_source (); + void set_midi_source (boost::shared_ptr); + + boost::shared_ptr > find_note (NotePtr); + PatchChangePtr find_patch_change (Evoral::event_id_t); + boost::shared_ptr > find_note (gint note_id); + boost::shared_ptr > find_sysex (gint); + + InsertMergePolicy insert_merge_policy () const; + void set_insert_merge_policy (InsertMergePolicy); + + boost::shared_ptr 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::WriteLockImpl { + WriteLockImpl(Glib::Threads::Mutex::Lock* slock, Glib::Threads::RWLock& s, Glib::Threads::Mutex& c) + : AutomatableSequence::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 _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 +#include +#include +#include +#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 class MidiRingBuffer; + +/** Source for MIDI data */ +class LIBARDOUR_API MidiSource : virtual public Source, public boost::enable_shared_from_this +{ + 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 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 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& dst, + framepos_t source_start, + framepos_t start, + framecnt_t cnt, + Evoral::Range* loop_range, + MidiCursor& cursor, + MidiStateTracker* tracker, + MidiChannelFilter* filter, + const std::set& 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& 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& 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& 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::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 Invalidated; + + void set_note_mode(const Glib::Threads::Mutex::Lock& lock, NoteMode mode); + + boost::shared_ptr model() { return _model; } + void set_model(const Glib::Threads::Mutex::Lock& lock, boost::shared_ptr); + 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); + 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); + void copy_automation_state_from (MidiSource *); + + /** Emitted when a different MidiModel is set */ + PBD::Signal0 ModelChanged; + /** Emitted when a parameter's interpolation style is changed */ + PBD::Signal2 InterpolationChanged; + /** Emitted when a parameter's automation state is changed */ + PBD::Signal2 AutomationStateChanged; + + protected: + virtual void flush_midi(const Lock& lock) = 0; + + virtual framecnt_t read_unlocked (const Lock& lock, + Evoral::EventSink& dst, + framepos_t position, + framepos_t start, + framecnt_t cnt, + Evoral::Range* 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& source, + framepos_t position, + framecnt_t cnt) = 0; + + std::string _captured_for; + + boost::shared_ptr _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 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 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 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(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(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& dst, + framepos_t source_start, + framepos_t start, + framecnt_t cnt, + Evoral::Range* loop_range, + MidiCursor& cursor, + MidiStateTracker* tracker, + MidiChannelFilter* filter, + const std::set& 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::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 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& 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::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::DeleteStuckNotes); +} + +int +MidiSource::export_write_to (const Lock& lock, boost::shared_ptr 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 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 (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 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 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 s) +{ + copy_interpolation_from (s.get ()); +} + +void +MidiSource::copy_automation_state_from (boost::shared_ptr 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 + * 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 +#include +#include + +#include +#include + +#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 + 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 { + 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 + +#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 +#include + +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 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 (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(tmp->metric()) != static_cast(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 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::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 +#include +#include +#include +#include + +#include + +#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 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(this)->iterator_at (sc); } + TempoMapPoints::const_iterator const_iterator_at (Evoral::Beats const & b) const { return const_cast(this)->iterator_at (b); } + TempoMapPoints::const_iterator const_iterator_at (Timecode::BBT_Time const & bbt) const { return const_cast(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__ */ -- cgit v1.2.3