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