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