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