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