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