put back revised version of Route::has_external_redirects() and use it to give a...
[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_diskstream.h"
28 #include "ardour/midi_playlist.h"
29 #include "ardour/midi_region.h"
30 #include "ardour/midi_source.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/region_factory.h"
33 #include "ardour/smf_source.h"
34 #include "ardour/session.h"
35
36 #include "ardour_ui.h"
37 #include "canvas-simplerect.h"
38 #include "global_signals.h"
39 #include "gui_thread.h"
40 #include "lineset.h"
41 #include "midi_region_view.h"
42 #include "midi_streamview.h"
43 #include "midi_time_axis.h"
44 #include "midi_util.h"
45 #include "public_editor.h"
46 #include "region_selection.h"
47 #include "region_view.h"
48 #include "rgb_macros.h"
49 #include "selection.h"
50 #include "simplerect.h"
51 #include "utils.h"
52
53 using namespace std;
54 using namespace ARDOUR;
55 using namespace PBD;
56 using namespace Editing;
57
58 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
59         : StreamView (tv)
60         , note_range_adjustment(0.0f, 0.0f, 0.0f)
61         , _range_dirty(false)
62         , _range_sum_cache(-1.0)
63         , _lowest_note(60)
64         , _highest_note(71)
65         , _data_note_min(60)
66         , _data_note_max(71)
67         , _note_lines (0)
68         , _updates_suspended (false)
69 {
70         /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
71         midi_underlay_group = new ArdourCanvas::Group (*_canvas_group);
72         midi_underlay_group->lower_to_bottom();
73
74         /* put the note lines in the timeaxisview's group, so it
75            can be put below ghost regions from MIDI underlays*/
76         _note_lines = new ArdourCanvas::LineSet(*_canvas_group,
77                                                 ArdourCanvas::LineSet::Horizontal);
78
79         _note_lines->property_x1() = 0;
80         _note_lines->property_y1() = 0;
81         _note_lines->property_x2() = DBL_MAX;
82         _note_lines->property_y2() = 0;
83
84         _note_lines->signal_event().connect(
85                 sigc::bind(sigc::mem_fun(_trackview.editor(),
86                                          &PublicEditor::canvas_stream_view_event),
87                            _note_lines, &_trackview));
88
89         _note_lines->lower_to_bottom();
90
91         color_handler ();
92
93         ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
94
95         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
96         note_range_adjustment.set_value(_lowest_note);
97
98         note_range_adjustment.signal_value_changed().connect(
99                 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
100 }
101
102 MidiStreamView::~MidiStreamView ()
103 {
104 }
105
106 RegionView*
107 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
108 {
109         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
110
111         if (region == 0) {
112                 return 0;
113         }
114
115         RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
116                                                       _samples_per_unit, region_color);
117
118         region_view->init (region_color, false);
119
120         return region_view;
121 }
122
123 RegionView*
124 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
125 {
126         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
127
128         if (region == 0) {
129                 return 0;
130         }
131
132         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
133                 if ((*i)->region() == r) {
134
135                         /* great. we already have a MidiRegionView for this Region. use it again. */
136
137                         (*i)->set_valid (true);
138
139                         display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
140
141                         return 0;
142                 }
143         }
144
145         MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
146         if (region_view == 0) {
147                 return 0;
148         }
149
150         region_views.push_front (region_view);
151
152         if (_trackview.editor().internal_editing()) {
153                 region_view->hide_rect ();
154         } else {
155                 region_view->show_rect ();
156         }
157
158         /* display events and find note range */
159         display_region (region_view, wfd);
160
161         /* catch regionview going away */
162         region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
163
164         RegionViewAdded (region_view);
165
166         return region_view;
167 }
168
169 void
170 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
171 {
172         if (!region_view) {
173                 return;
174         }
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
184         _range_dirty = update_data_note_range(
185                 source->model()->lowest_note(),
186                 source->model()->highest_note());
187
188         // Display region contents
189         region_view->set_height (child_height());
190         region_view->display_model(source->model());
191 }
192
193 void
194 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
195 {
196         StreamView::display_track (tr);
197
198         draw_note_lines();
199
200         NoteRangeChanged();
201 }
202
203 void
204 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
205 {
206         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
207         if (mr) {
208                 mr->midi_source(0)->load_model();
209                 _range_dirty = update_data_note_range(
210                         mr->model()->lowest_note(),
211                         mr->model()->highest_note());
212         }
213 }
214
215 bool
216 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
217 {
218         bool dirty = false;
219         if (min < _data_note_min) {
220                 _data_note_min = min;
221                 dirty = true;
222         }
223         if (max > _data_note_max) {
224                 _data_note_max = max;
225                 dirty = true;
226         }
227         return dirty;
228 }
229
230 void
231 MidiStreamView::redisplay_track ()
232 {
233         if (!_trackview.is_midi_track()) {
234                 return;
235         }
236
237         list<RegionView*>::iterator i;
238
239         // Load models if necessary, and find note range of all our contents
240         _range_dirty = false;
241         _data_note_min = 127;
242         _data_note_max = 0;
243         _trackview.track()->playlist()->foreach_region(
244                 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
245
246         // No notes, use default range
247         if (!_range_dirty) {
248                 _data_note_min = 60;
249                 _data_note_max = 71;
250         }
251
252         // Flag region views as invalid and disable drawing
253         for (i = region_views.begin(); i != region_views.end(); ++i) {
254                 (*i)->set_valid(false);
255                 (*i)->enable_display(false);
256         }
257
258         // Add and display region views, and flag them as valid
259         _trackview.track()->playlist()->foreach_region(
260                 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
261
262         // Stack regions by layer, and remove invalid regions
263         layer_regions();
264
265         // Update note range (not regions which are correct) and draw note lines
266         apply_note_range(_lowest_note, _highest_note, false);
267 }
268
269
270 void
271 MidiStreamView::update_contents_height ()
272 {
273         StreamView::update_contents_height();
274         _note_lines->property_y2() = child_height ();
275
276         apply_note_range (lowest_note(), highest_note(), true);
277 }
278
279 void
280 MidiStreamView::draw_note_lines()
281 {
282         if (!_note_lines || _updates_suspended) {
283                 return;
284         }
285
286         double y;
287         double prev_y = contents_height();
288         uint32_t color;
289
290         _note_lines->clear();
291
292         if (child_height() < 140 || note_height() < 3) {
293                 /* track is too small for note lines, or there are too many */
294                 return;
295         }
296
297         for (int i = lowest_note(); i <= highest_note(); ++i) {
298                 y = floor(note_to_y(i));
299
300                 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
301
302                 switch (i % 12) {
303                 case 1:
304                 case 3:
305                 case 6:
306                 case 8:
307                 case 10:
308                         color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
309                         break;
310                 default:
311                         color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
312                         break;
313                 }
314
315                 if (i == highest_note()) {
316                         _note_lines->add_line(y, prev_y - y, color);
317                 } else {
318                         _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
319                 }
320
321                 prev_y = y;
322         }
323 }
324
325 void
326 MidiStreamView::set_note_range(VisibleNoteRange r)
327 {
328         if (r == FullRange) {
329                 _lowest_note = 0;
330                 _highest_note = 127;
331         } else {
332                 _lowest_note = _data_note_min;
333                 _highest_note = _data_note_max;
334         }
335
336         apply_note_range(_lowest_note, _highest_note, true);
337 }
338
339 void
340 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
341 {
342         _highest_note = highest;
343         _lowest_note = lowest;
344
345         int const max_note_height = 20;  // This should probably be based on text size...
346         int const range = _highest_note - _lowest_note;
347         int const pixels_per_note = floor (child_height () / range);
348
349         /* do not grow note height beyond 10 pixels */
350         if (pixels_per_note > max_note_height) {
351
352                 int const available_note_range = floor (child_height() / max_note_height);
353                 int additional_notes = available_note_range - range;
354
355                 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
356                 for (int i = 0; i < additional_notes; i++){
357
358                         if (i % 2 && _highest_note < 127){
359                                 _highest_note++;
360                         }
361                         else if (i % 2) {
362                                 _lowest_note--;
363                         }
364                         else if (_lowest_note > 0){
365                                 _lowest_note--;
366                         }
367                         else {
368                                 _highest_note++;
369                         }
370                 }
371         }
372
373         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
374         note_range_adjustment.set_value(_lowest_note);
375
376         draw_note_lines();
377
378         if (to_region_views) {
379                 apply_note_range_to_regions ();
380         }
381
382         NoteRangeChanged();
383 }
384
385 void
386 MidiStreamView::apply_note_range_to_regions ()
387 {
388         if (!_updates_suspended) {
389                 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
390                         ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
391                 }
392         }
393 }
394
395 void
396 MidiStreamView::update_note_range(uint8_t note_num)
397 {
398         assert(note_num <= 127);
399         _data_note_min = min(_data_note_min, note_num);
400         _data_note_max = max(_data_note_max, note_num);
401 }
402
403 void
404 MidiStreamView::setup_rec_box ()
405 {
406         // cerr << _trackview.name() << " streamview SRB\n";
407
408         if (_trackview.session()->transport_rolling()) {
409
410                 if (!rec_active &&
411                     _trackview.session()->record_status() == Session::Recording &&
412                     _trackview.track()->record_enabled()) {
413
414                         if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
415
416                                 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
417
418                                 MidiRegion::SourceList sources;
419
420                                 rec_data_ready_connections.drop_connections ();
421
422                                 sources.push_back (_trackview.midi_track()->write_source());
423
424                                 // handle multi
425
426                                 framepos_t start = 0;
427                                 if (rec_regions.size() > 0) {
428                                         start = rec_regions.back().first->start()
429                                                 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
430                                 }
431
432                                 if (!rec_regions.empty()) {
433                                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
434                                         mrv->end_write ();
435                                 }
436
437                                 PropertyList plist;
438
439                                 plist.add (ARDOUR::Properties::start, start);
440                                 plist.add (ARDOUR::Properties::length, 1);
441                                 /* Just above we're setting this nascent region's length to 1.  I think this
442                                    is so that the RegionView gets created with a non-zero width, as apparently
443                                    creating a RegionView with a zero width causes it never to be displayed
444                                    (there is a warning in TimeAxisViewItem::init about this).  However, we
445                                    must also set length_beats to something non-zero, otherwise the frame length
446                                    of 1 causes length_beats to be set to some small quantity << 1.  Then
447                                    when the position is set up below, this length_beats is used to recompute
448                                    length using BeatsFramesConverter::to, which is slightly innacurate for small
449                                    beats values because it converts floating point beats to bars, beats and
450                                    integer ticks.  The upshot of which being that length gets set back to 0,
451                                    meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
452                                 */
453                                 plist.add (ARDOUR::Properties::length_beats, 1);
454                                 plist.add (ARDOUR::Properties::name, string());
455                                 plist.add (ARDOUR::Properties::layer, 0);
456
457                                 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
458                                                                       (RegionFactory::create (sources, plist, false)));
459
460                                 assert(region);
461                                 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0));
462                                 region->set_position (_trackview.track()->current_capture_start());
463                                 RegionView* rv = add_region_view_internal (region, false);
464                                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
465                                 mrv->begin_write ();
466
467                                 rec_regions.push_back (make_pair (region, rv));
468
469                                 // rec regions are destroyed in setup_rec_box
470
471                                 /* we add the region later */
472
473                                 setup_new_rec_layer_time (region);
474                         }
475
476                         /* start a new rec box */
477
478                         boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
479                         framepos_t const frame_pos = mt->current_capture_start ();
480                         gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
481                         gdouble const xend = xstart;
482                         uint32_t fill_color;
483
484                         assert(_trackview.midi_track()->mode() == Normal);
485
486                         fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
487
488                         ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
489                         rec_rect->property_x1() = xstart;
490                         rec_rect->property_y1() = 1.0;
491                         rec_rect->property_x2() = xend;
492                         rec_rect->property_y2() = (double) _trackview.current_height() - 1;
493                         rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
494                         rec_rect->property_fill_color_rgba() = fill_color;
495                         rec_rect->lower_to_bottom();
496
497                         RecBoxInfo recbox;
498                         recbox.rectangle = rec_rect;
499                         recbox.start = _trackview.session()->transport_frame();
500                         recbox.length = 0;
501
502                         rec_rects.push_back (recbox);
503
504                         screen_update_connection.disconnect();
505                         screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
506                                 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
507                         rec_updating = true;
508                         rec_active = true;
509
510                 } else if (rec_active &&
511                            (_trackview.session()->record_status() != Session::Recording ||
512                             !_trackview.track()->record_enabled())) {
513                         screen_update_connection.disconnect();
514                         rec_active = false;
515                         rec_updating = false;
516                 }
517
518         } else {
519
520                 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
521
522                 if (!rec_rects.empty() || !rec_regions.empty()) {
523
524                         /* disconnect rapid update */
525                         screen_update_connection.disconnect();
526                         rec_data_ready_connections.drop_connections ();
527
528                         rec_updating = false;
529                         rec_active = false;
530
531                         /* remove temp regions */
532
533                         for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
534                                 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
535
536                                 tmp = iter;
537                                 ++tmp;
538
539                                 (*iter).first->drop_references ();
540
541                                 iter = tmp;
542                         }
543
544                         rec_regions.clear();
545
546                         // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
547
548                         /* transport stopped, clear boxes */
549                         for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
550                                 RecBoxInfo &rect = (*iter);
551                                 delete rect.rectangle;
552                         }
553
554                         rec_rects.clear();
555
556                 }
557         }
558 }
559
560 void
561 MidiStreamView::color_handler ()
562 {
563         draw_note_lines ();
564
565         if (_trackview.is_midi_track()) {
566                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
567         } else {
568                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
569         }
570 }
571
572 void
573 MidiStreamView::note_range_adjustment_changed()
574 {
575         double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
576         int lowest = (int) floor(note_range_adjustment.get_value());
577         int highest;
578
579         if (sum == _range_sum_cache) {
580                 //cerr << "cached" << endl;
581                 highest = (int) floor(sum);
582         } else {
583                 //cerr << "recalc" << endl;
584                 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
585                 _range_sum_cache = sum;
586         }
587
588         if (lowest == _lowest_note && highest == _highest_note) {
589                 return;
590         }
591
592         //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
593         //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;
594
595         _lowest_note = lowest;
596         _highest_note = highest;
597         apply_note_range(lowest, highest, true);
598 }
599
600 void
601 MidiStreamView::update_rec_box ()
602 {
603         StreamView::update_rec_box ();
604
605         if (rec_regions.empty()) {
606                 return;
607         }
608
609         /* Update the region being recorded to reflect where we currently are */
610         boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
611         region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
612
613         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
614         mrv->extend_active_notes ();
615 }
616
617 uint8_t
618 MidiStreamView::y_to_note (double y) const
619 {
620         int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
621                 + lowest_note();
622
623         if (n < 0) {
624                 return 0;
625         } else if (n > 127) {
626                 return 127;
627         }
628
629         return n;
630 }
631
632 /** Suspend updates to the regions' note ranges and our
633  *  note lines until resume_updates() is called.
634  */
635 void
636 MidiStreamView::suspend_updates ()
637 {
638         _updates_suspended = true;
639 }
640
641 /** Resume updates to region note ranges and note lines,
642  *  and update them now.
643  */
644 void
645 MidiStreamView::resume_updates ()
646 {
647         _updates_suspended = false;
648
649         draw_note_lines ();
650         apply_note_range_to_regions ();
651 }