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