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