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