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