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