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