Cleanups and a few comments.
[ardour.git] / gtk2_ardour / midi_streamview.cc
1 /*
2     Copyright (C) 2001-2007 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <cmath>
20 #include <cassert>
21 #include <utility>
22
23 #include <gtkmm.h>
24
25 #include <gtkmm2ext/gtk_ui.h>
26
27 #include <ardour/midi_playlist.h>
28 #include <ardour/midi_region.h>
29 #include <ardour/midi_source.h>
30 #include <ardour/midi_diskstream.h>
31 #include <ardour/midi_track.h>
32 #include <ardour/smf_source.h>
33 #include <ardour/region_factory.h>
34
35 #include "midi_streamview.h"
36 #include "region_view.h"
37 #include "midi_region_view.h"
38 #include "midi_time_axis.h"
39 #include "canvas-simplerect.h"
40 #include "region_selection.h"
41 #include "selection.h"
42 #include "public_editor.h"
43 #include "ardour_ui.h"
44 #include "rgb_macros.h"
45 #include "gui_thread.h"
46 #include "utils.h"
47 #include "simplerect.h"
48 #include "lineset.h"
49
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53 using namespace Editing;
54
55 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
56         : StreamView (tv)
57         , note_range_adjustment(0.0f, 0.0f, 0.0f)
58         , _range_dirty(false)
59         , _range_sum_cache(-1.0)
60         , _lowest_note(60)
61         , _highest_note(71)
62         , _data_note_min(60)
63         , _data_note_max(71)
64 {
65         if (tv.is_track())
66                 stream_base_color = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
67         else
68                 stream_base_color = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();
69
70         use_rec_regions = tv.editor().show_waveforms_recording ();
71
72         /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
73         midi_underlay_group = new ArdourCanvas::Group (*canvas_group);
74         midi_underlay_group->lower_to_bottom();
75
76         /* put the note lines in the timeaxisview's group, so it 
77            can be put below ghost regions from MIDI underlays*/
78         _note_lines = new ArdourCanvas::LineSet(*canvas_group, ArdourCanvas::LineSet::Horizontal);
79
80         _note_lines->property_x1() = 0;
81         _note_lines->property_y1() = 0;
82         _note_lines->property_x2() = trackview().editor().frame_to_pixel (max_frames);
83         _note_lines->property_y2() = 0;
84
85         _note_lines->signal_event().connect (bind (mem_fun (_trackview.editor(), &PublicEditor::canvas_stream_view_event), _note_lines, &_trackview));
86         _note_lines->lower_to_bottom();
87
88         ColorsChanged.connect(mem_fun(*this, &MidiStreamView::draw_note_lines));
89         
90         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
91         note_range_adjustment.set_value(_lowest_note);
92         
93         note_range_adjustment.signal_value_changed().connect (mem_fun (*this, &MidiStreamView::note_range_adjustment_changed));
94 }
95
96 MidiStreamView::~MidiStreamView ()
97 {
98 }
99
100 static void
101 veto_note_range(uint8_t& min, uint8_t& max)
102 {
103         /* Legal notes, thanks */
104         if (max > 127)
105                 max = 127;
106         if (min > 127)
107                 min = 127;
108         
109         /* Always display at least one octave in [0, 127] */
110         if (max == 127) {
111                 if (min > (127 - 11)) {
112                         min = 127 - 11;
113                 }
114         } else if (max < min + 11) {
115                 uint8_t d = 11 - (max - min);
116                 if (max + d/2 > 127) {
117                         min -= d;
118                 } else {
119                         min -= d / 2;
120                         max += d / 2;
121                 }
122         }
123         assert(max - min >= 11);
124         assert(max < 127);
125         assert(min < 127);
126 }
127
128 RegionView*
129 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
130 {
131         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
132
133         if (region == 0) {
134                 return NULL;
135         }
136
137         MidiRegionView *region_view;
138         list<RegionView *>::iterator i;
139
140         for (i = region_views.begin(); i != region_views.end(); ++i) {
141                 if ((*i)->region() == r) {
142                         
143                         /* great. we already have a MidiRegionView for this Region. use it again. */
144
145                         (*i)->set_valid (true);
146                         
147                         display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
148
149                         return NULL;
150                 }
151         }
152         
153         region_view = new MidiRegionView (canvas_group, _trackview, region, 
154                         _samples_per_unit, region_color);
155                 
156         region_view->init (region_color, false);
157         region_views.push_front (region_view);
158                         
159         /* display events and find note range */
160         display_region(region_view, wfd);
161
162         /* catch regionview going away */
163         region->GoingAway.connect (bind (mem_fun (*this, &MidiStreamView::remove_region_view), region));
164         
165         RegionViewAdded (region_view);
166
167         return region_view;
168 }
169
170 void
171 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
172 {
173         if ( ! region_view)
174                 return;
175                         
176         region_view->enable_display(true);
177
178         boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
179
180         if (load_model)
181                 source->load_model();
182
183         _range_dirty = update_data_note_range(
184                         source->model()->lowest_note(),
185                         source->model()->highest_note());
186
187         // Display region contents
188         region_view->display_model(source->model());
189 }
190
191 void
192 MidiStreamView::display_diskstream (boost::shared_ptr<Diskstream> ds)
193 {
194         StreamView::display_diskstream(ds);
195         draw_note_lines();
196         NoteRangeChanged();
197 }
198                         
199 bool
200 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
201 {
202         bool dirty = false;
203         if (min < _data_note_min) {
204                 _data_note_min = min;
205                 dirty = true;
206         }
207         if (max > _data_note_max) {
208                 _data_note_max = max;
209                 dirty = true;
210         }
211         return dirty;
212 }
213
214 // FIXME: code duplication with AudioStreamView
215 void
216 MidiStreamView::redisplay_diskstream ()
217 {
218         list<RegionView *>::iterator i, tmp;
219
220         _range_dirty = false;
221         _data_note_min = 127;
222         _data_note_max = 0;
223
224         for (i = region_views.begin(); i != region_views.end(); ++i) {
225                 (*i)->set_valid (false);
226                 (*i)->enable_display (false);
227                 
228                 // Load model if it isn't already, to get note range
229                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*i);
230                 if (mrv) {
231                         mrv->midi_region()->midi_source(0)->load_model();
232                         _range_dirty = update_data_note_range(
233                                         mrv->midi_region()->model()->lowest_note(),
234                                         mrv->midi_region()->model()->highest_note());
235                 }
236         }
237
238         // No notes, use default range
239         if (!_range_dirty) {
240                 _data_note_min = 60;
241                 _data_note_max = 71;
242         }
243         
244         bool range_changed = false;
245
246         // Extend visible range to show newly recorded data, if necessary
247         if (_data_note_min < _lowest_note) {
248                 _lowest_note = _data_note_min;
249                 range_changed = true;
250         }
251         if (_data_note_max > _highest_note) {
252                 _highest_note = _data_note_max;
253                 range_changed = true;
254         }
255         
256         veto_note_range(_lowest_note, _highest_note);
257         
258         if (_trackview.is_midi_track()) {
259                 _trackview.get_diskstream()->playlist()->foreach_region (
260                                 static_cast<StreamView*>(this), &StreamView::add_region_view);
261         }
262
263         RegionViewList copy;
264         
265         /* Place regions */
266         for (i = region_views.begin(); i != region_views.end(); ) {
267                 tmp = i;
268                 tmp++;
269
270                 if (!(*i)->is_valid()) {
271                         delete *i;
272                         region_views.erase (i);
273                         i = tmp;
274                         continue;
275                 } else {
276                         (*i)->enable_display(true);
277                         (*i)->set_height(height); // apply note range
278                 }
279                 
280                 /* Sort regionviews by layer so that when we call region_layered ()
281                    the canvas layering works out (in non-stacked mode). */
282
283                 if (copy.size() == 0) {
284                         copy.push_front((*i));
285                         i = tmp;
286                         continue;
287                 }
288
289                 RegionViewList::iterator k = copy.begin();
290                 RegionViewList::iterator l = copy.end();
291                 l--;
292
293                 if ((*i)->region()->layer() <= (*k)->region()->layer()) {
294                         copy.push_front((*i));
295                         i = tmp;
296                         continue;
297                 } else if ((*i)->region()->layer() >= (*l)->region()->layer()) {
298                         copy.push_back((*i));
299                         i = tmp;
300                         continue;
301                 }
302
303                 for (RegionViewList::iterator j = copy.begin(); j != copy.end(); ++j) {
304                         if ((*j)->region()->layer() >= (*i)->region()->layer()) {
305                                 copy.insert(j, (*i));
306                                 break;
307                         }
308                 }
309
310                 i = tmp;
311         }
312         
313         /* Fix canvas layering */
314         for (RegionViewList::iterator j = copy.begin(); j != copy.end(); ++j) {
315                 region_layered (*j);
316         }
317         
318         /* Update note range and re-draw note lines if necessary */
319         apply_note_range(_lowest_note, _highest_note);
320         NoteRangeChanged();
321 }
322
323
324 void
325 MidiStreamView::update_contents_height ()
326 {
327         StreamView::update_contents_height();
328         _note_lines->property_y2() = height;
329         draw_note_lines();
330 }
331         
332 void
333 MidiStreamView::draw_note_lines()
334 {
335         double y;
336         double prev_y = contents_height();
337         uint32_t color;
338
339         _note_lines->clear();
340
341         for (int i = lowest_note(); i <= highest_note(); ++i) {
342                 y = floor(note_to_y(i));
343                 
344                 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
345
346                 switch (i % 12) {
347                 case 1:
348                 case 3:
349                 case 6:
350                 case 8:
351                 case 10:
352                         color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
353                         break;
354                 default:
355                         color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
356                         break;
357                 }
358
359                 if (i == highest_note()) {
360                         _note_lines->add_line(y, prev_y - y, color);
361                 } else {
362                         _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
363                 }
364
365                 prev_y = y;
366         }
367 }
368
369 void
370 MidiStreamView::set_note_range(VisibleNoteRange r)
371 {
372         if (r == FullRange) {
373                 _lowest_note = 0;
374                 _highest_note = 127;
375         } else {
376                 _lowest_note = _data_note_min;
377                 _highest_note = _data_note_max;
378         }
379
380         apply_note_range(_lowest_note, _highest_note);
381 }
382
383 void
384 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest)
385 {
386         _highest_note = highest;
387         _lowest_note = lowest;
388         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
389         note_range_adjustment.set_value(_lowest_note);
390         draw_note_lines();
391         
392         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
393                 ((MidiRegionView*)(*i))->apply_note_range(lowest, highest);
394         }
395
396         NoteRangeChanged();
397 }
398
399 void 
400 MidiStreamView::update_note_range(uint8_t note_num)
401 {
402         assert(note_num <= 127);
403         _data_note_min = min(_data_note_min, note_num);
404         _data_note_max = max(_data_note_max, note_num);
405 }
406         
407 void
408 MidiStreamView::setup_rec_box ()
409 {
410         // cerr << _trackview.name() << " streamview SRB\n";
411
412         if (_trackview.session().transport_rolling()) {
413
414                 if (!rec_active && 
415                     _trackview.session().record_status() == Session::Recording && 
416                     _trackview.get_diskstream()->record_enabled()) {
417
418                         if (use_rec_regions && rec_regions.size() == rec_rects.size()) {
419
420                                 /* add a new region, but don't bother if they set use_rec_regions mid-record */
421
422                                 MidiRegion::SourceList sources;
423                                 
424                                 for (list<sigc::connection>::iterator prc = rec_data_ready_connections.begin(); prc != rec_data_ready_connections.end(); ++prc) {
425                                         (*prc).disconnect();
426                                 }
427                                 rec_data_ready_connections.clear();
428
429                                 // FIXME
430                                 boost::shared_ptr<MidiDiskstream> mds = boost::dynamic_pointer_cast<MidiDiskstream>(_trackview.get_diskstream());
431                                 assert(mds);
432
433                                 sources.push_back(mds->write_source());
434                                 
435                                 rec_data_ready_connections.push_back (mds->write_source()->ViewDataRangeReady.connect (bind (mem_fun (*this, &MidiStreamView::rec_data_range_ready), boost::weak_ptr<Source>(mds->write_source())))); 
436
437                                 // handle multi
438                                 
439                                 jack_nframes_t start = 0;
440                                 if (rec_regions.size() > 0) {
441                                         start = rec_regions.back().first->position() + _trackview.get_diskstream()->get_captured_frames(rec_regions.size()-1);
442                                 }
443                                 
444                                 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
445                                         (RegionFactory::create (sources, start, 1 , "", 0, (Region::Flag)(Region::DefaultFlags | Region::DoNotSaveState), false)));
446                                 assert(region);
447                                 region->set_position (_trackview.session().transport_frame(), this);
448                                 rec_regions.push_back (make_pair(region, (RegionView*)0));
449                                 
450                                 // rec regions are destroyed in setup_rec_box
451
452                                 /* we add the region later */
453                         }
454                         
455                         /* start a new rec box */
456
457                         boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
458                         boost::shared_ptr<MidiDiskstream> ds = mt->midi_diskstream();
459                         jack_nframes_t frame_pos = ds->current_capture_start ();
460                         gdouble xstart = _trackview.editor().frame_to_pixel (frame_pos);
461                         gdouble xend;
462                         uint32_t fill_color;
463
464                         assert(_trackview.midi_track()->mode() == Normal);
465                         
466                         xend = xstart;
467                         fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
468                         
469                         ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*canvas_group);
470                         rec_rect->property_x1() = xstart;
471                         rec_rect->property_y1() = 1.0;
472                         rec_rect->property_x2() = xend;
473                         rec_rect->property_y2() = (double) _trackview.current_height() - 1;
474                         rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
475                         rec_rect->property_fill_color_rgba() = fill_color;
476                         rec_rect->lower_to_bottom();
477                         
478                         RecBoxInfo recbox;
479                         recbox.rectangle = rec_rect;
480                         recbox.start = _trackview.session().transport_frame();
481                         recbox.length = 0;
482                         
483                         rec_rects.push_back (recbox);
484                         
485                         screen_update_connection.disconnect();
486                         screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (mem_fun (*this, &MidiStreamView::update_rec_box));    
487                         rec_updating = true;
488                         rec_active = true;
489
490                 } else if (rec_active &&
491                            (_trackview.session().record_status() != Session::Recording ||
492                             !_trackview.get_diskstream()->record_enabled())) {
493
494                         screen_update_connection.disconnect();
495                         rec_active = false;
496                         rec_updating = false;
497
498                 }
499                 
500         } else {
501
502                 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
503
504                 if (!rec_rects.empty() || !rec_regions.empty()) {
505
506                         /* disconnect rapid update */
507                         screen_update_connection.disconnect();
508
509                         for (list<sigc::connection>::iterator prc = rec_data_ready_connections.begin(); prc != rec_data_ready_connections.end(); ++prc) {
510                                 (*prc).disconnect();
511                         }
512                         rec_data_ready_connections.clear();
513
514                         rec_updating = false;
515                         rec_active = false;
516                         
517                         /* remove temp regions */
518                         
519                         for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
520                                 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
521                                 
522                                 tmp = iter;
523                                 ++tmp;
524
525                                 (*iter).first->drop_references ();
526
527                                 iter = tmp;
528                         }
529                         
530                         rec_regions.clear();
531
532                         // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
533
534                         /* transport stopped, clear boxes */
535                         for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
536                                 RecBoxInfo &rect = (*iter);
537                                 delete rect.rectangle;
538                         }
539                         
540                         rec_rects.clear();
541                         
542                 }
543         }
544 }
545
546 void
547 MidiStreamView::update_rec_regions (boost::shared_ptr<MidiModel> data, nframes_t start, nframes_t dur)
548 {
549         ENSURE_GUI_THREAD (bind (mem_fun (*this, &MidiStreamView::update_rec_regions), data, start, dur));
550
551         if (use_rec_regions) {
552
553                 uint32_t n = 0;
554                 bool     update_range = false;
555
556                 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end(); n++) {
557
558                         list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
559
560                         tmp = iter;
561                         ++tmp;
562                         
563                         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion>(iter->first);
564                         if (!region || !iter->second) {
565                                 iter = tmp;
566                                 continue;
567                         }
568                         
569                         if (!canvas_item_visible (rec_rects[n].rectangle)) {
570                                 /* rect already hidden, this region is done */
571                                 iter = tmp;
572                                 continue;
573                         }
574                         
575                         nframes_t origlen = region->length();
576                         
577                         if (region == rec_regions.back().first && rec_active) {
578
579                                 if (start >= region->midi_source(0)->timeline_position()) {
580                                 
581                                         nframes_t nlen = start + dur - region->position();
582
583                                         if (nlen != region->length()) {
584                                         
585                                                 region->freeze ();
586                                                 region->set_position (_trackview.get_diskstream()->get_capture_start_frame(n), this);
587                                                 region->set_length (start + dur - region->position(), this);
588                                                 region->thaw ("updated");
589                                                 
590                                                 if (origlen == 1) {
591                                                         /* our special initial length */
592                                                         iter->second = add_region_view_internal (region, false);
593                                                         ((MidiRegionView*)iter->second)->begin_write();
594                                                 }
595
596                                                 /* also update rect */
597                                                 ArdourCanvas::SimpleRect * rect = rec_rects[n].rectangle;
598                                                 gdouble xend = _trackview.editor().frame_to_pixel (region->position() + region->length());
599                                                 rect->property_x2() = xend;
600
601                                                 /* draw events */
602                                                 MidiRegionView* mrv = (MidiRegionView*)iter->second;
603                                                 for (size_t i=0; i < data->n_notes(); ++i) {
604
605                                                         // FIXME: slooooooooow!
606
607                                                         const boost::shared_ptr<Evoral::Note> note = data->note_at(i);
608                                                         
609                                                         if (note->length() > 0 && note->end_time() + region->position() > start)
610                                                                 mrv->resolve_note(note->note(), note->end_time());
611
612                                                         if (note->time() + region->position() < start)
613                                                                 continue;
614
615                                                         if (note->time() + region->position() > start + dur)
616                                                                 break;
617
618                                                         mrv->add_note(note);
619
620                                                         if (note->note() < _lowest_note) {
621                                                                 _lowest_note = note->note();
622                                                                 update_range = true;
623                                                         } else if (note->note() > _highest_note) {
624                                                                 _highest_note = note->note();
625                                                                 update_range = true;
626                                                         }
627                                                 }
628                                                 
629                                                 mrv->extend_active_notes();
630                                         }
631                                 }
632
633                         } else {
634                                 
635                                 nframes_t nlen = _trackview.get_diskstream()->get_captured_frames(n);
636
637                                 if (nlen != region->length()) {
638
639                                         if (region->source(0)->length() >= region->position() + nlen) {
640
641                                                 region->freeze ();
642                                                 region->set_position (_trackview.get_diskstream()->get_capture_start_frame(n), this);
643                                                 region->set_length (nlen, this);
644                                                 region->thaw ("updated");
645                                                 
646                                                 if (origlen == 1) {
647                                                         /* our special initial length */
648                                                         iter->second = add_region_view_internal (region, false);
649                                                 }
650                                                 
651                                                 /* also hide rect */
652                                                 ArdourCanvas::Item * rect = rec_rects[n].rectangle;
653                                                 rect->hide();
654
655                                         }
656                                 }
657                         }
658
659                         iter = tmp;
660                 }
661
662                 if (update_range)
663                         update_contents_height();
664         }
665 }
666
667 void
668 MidiStreamView::rec_data_range_ready (jack_nframes_t start, jack_nframes_t dur, boost::weak_ptr<Source> weak_src)
669 {
670         // this is called from the butler thread for now
671         
672         ENSURE_GUI_THREAD(bind (mem_fun (*this, &MidiStreamView::rec_data_range_ready), start, dur, weak_src));
673         
674         boost::shared_ptr<SMFSource> src (boost::dynamic_pointer_cast<SMFSource>(weak_src.lock()));
675         
676         this->update_rec_regions (src->model(), start, dur);
677 }
678
679 void
680 MidiStreamView::color_handler ()
681 {
682         //case cMidiTrackBase:
683         if (_trackview.is_midi_track()) {
684                 //canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
685         } 
686
687         //case cMidiBusBase:
688         if (!_trackview.is_midi_track()) {
689                 //canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
690         }
691 }
692
693 void
694 MidiStreamView::note_range_adjustment_changed()
695 {
696         double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
697         int lowest = (int) floor(note_range_adjustment.get_value());
698         int highest;
699
700         if (sum == _range_sum_cache) {
701                 //cerr << "cached" << endl;
702                 highest = (int) floor(sum);
703         } else {
704                 //cerr << "recalc" << endl;
705                 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
706                 _range_sum_cache = sum;
707         }
708
709         if (lowest == _lowest_note && highest == _highest_note) {
710                 return;
711         }
712
713         //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
714         //cerr << "  val=" << v_zoom_adjustment.get_value() << " page=" << v_zoom_adjustment.get_page_size() << " sum=" << v_zoom_adjustment.get_value() + v_zoom_adjustment.get_page_size() << endl;
715
716         _lowest_note = lowest;
717         _highest_note = highest;
718         apply_note_range(lowest, highest);
719 }
720