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