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