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