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