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