experiment with making MIDI region boxes vanish when in note/internal edit mode ...
[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_diskstream.h"
28 #include "ardour/midi_playlist.h"
29 #include "ardour/midi_region.h"
30 #include "ardour/midi_source.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/region_factory.h"
33 #include "ardour/smf_source.h"
34 #include "ardour/session.h"
35
36 #include "ardour_ui.h"
37 #include "canvas-simplerect.h"
38 #include "global_signals.h"
39 #include "gui_thread.h"
40 #include "lineset.h"
41 #include "midi_region_view.h"
42 #include "midi_streamview.h"
43 #include "midi_time_axis.h"
44 #include "midi_util.h"
45 #include "public_editor.h"
46 #include "region_selection.h"
47 #include "region_view.h"
48 #include "rgb_macros.h"
49 #include "selection.h"
50 #include "simplerect.h"
51 #include "utils.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 {
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, ArdourCanvas::LineSet::Horizontal);
76
77         _note_lines->property_x1() = 0;
78         _note_lines->property_y1() = 0;
79         _note_lines->property_x2() = DBL_MAX;
80         _note_lines->property_y2() = 0;
81
82         _note_lines->signal_event().connect(sigc::bind(
83                         sigc::mem_fun(_trackview.editor(), &PublicEditor::canvas_stream_view_event),
84                         _note_lines, &_trackview));
85
86         _note_lines->lower_to_bottom();
87
88         color_handler ();
89
90         ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
91
92         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
93         note_range_adjustment.set_value(_lowest_note);
94
95         note_range_adjustment.signal_value_changed().connect(
96                         sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
97 }
98
99 MidiStreamView::~MidiStreamView ()
100 {
101 }
102
103 static void
104 veto_note_range(uint8_t& min, uint8_t& max)
105 {
106         /* Legal notes, thanks */
107         clamp_to_0_127(min);
108         clamp_to_0_127(max);
109
110         /* Always display at least one octave in [0, 127] */
111         if (max == 127) {
112                 if (min > (127 - 11)) {
113                         min = 127 - 11;
114                 }
115         } else if (max < min + 11) {
116                 uint8_t d = 11 - (max - min);
117                 if (max + d/2 > 127) {
118                         min -= d;
119                 } else {
120                         min -= d / 2;
121                         max += d / 2;
122                 }
123         }
124         assert(max - min >= 11);
125         assert(max <= 127);
126         assert(min <= 127);
127 }
128
129 RegionView*
130 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
131 {
132         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
133
134         if (region == 0) {
135                 return 0;
136         }
137
138         RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
139                                                       _samples_per_unit, region_color);
140         
141         region_view->init (region_color, false);
142
143         return region_view;
144 }
145
146 RegionView*
147 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
148 {
149         boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
150
151         if (region == 0) {
152                 return 0;
153         }
154
155         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
156                 if ((*i)->region() == r) {
157
158                         /* great. we already have a MidiRegionView for this Region. use it again. */
159
160                         (*i)->set_valid (true);
161
162                         display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
163
164                         return 0;
165                 }
166         }
167
168         MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
169         if (region_view == 0) {
170                 return 0;
171         }
172
173         region_views.push_front (region_view);
174
175         /* display events and find note range */
176         display_region (region_view, wfd);
177
178         /* catch regionview going away */
179         region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
180
181         RegionViewAdded (region_view);
182
183         return region_view;
184 }
185
186 void
187 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
188 {
189         if ( ! region_view)
190                 return;
191
192         region_view->enable_display(true);
193
194         boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
195
196         if (load_model) {
197                 source->load_model();
198         }
199
200         _range_dirty = update_data_note_range(
201                         source->model()->lowest_note(),
202                         source->model()->highest_note());
203
204         // Display region contents
205         region_view->set_height (child_height());
206         region_view->display_model(source->model());
207 }
208
209 void
210 MidiStreamView::enter_internal_edit_mode ()
211 {
212         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
213                 (*i)->hide_rect ();
214         }
215 }
216
217 void
218 MidiStreamView::leave_internal_edit_mode ()
219 {
220         for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
221                 (*i)->show_rect ();
222         }
223 }
224
225 void
226 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
227 {
228         StreamView::display_track (tr);
229
230         draw_note_lines();
231         
232         NoteRangeChanged();
233 }
234
235 void
236 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
237 {
238         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
239         if (mr) {
240                 mr->midi_source(0)->load_model();
241                 _range_dirty = update_data_note_range(
242                                 mr->model()->lowest_note(),
243                                 mr->model()->highest_note());
244         }
245 }
246
247 bool
248 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
249 {
250         bool dirty = false;
251         if (min < _data_note_min) {
252                 _data_note_min = min;
253                 dirty = true;
254         }
255         if (max > _data_note_max) {
256                 _data_note_max = max;
257                 dirty = true;
258         }
259         return dirty;
260 }
261
262 void
263 MidiStreamView::redisplay_track ()
264 {
265         if (!_trackview.is_midi_track()) {
266                 return;
267         }
268
269         list<RegionView*>::iterator i;
270
271         // Load models if necessary, and find note range of all our contents
272         _range_dirty = false;
273         _data_note_min = 127;
274         _data_note_max = 0;
275         _trackview.track()->playlist()->foreach_region(
276                 sigc::mem_fun (*this, &StreamView::update_contents_metrics)
277                 );
278
279         // No notes, use default range
280         if (!_range_dirty) {
281                 _data_note_min = 60;
282                 _data_note_max = 71;
283         }
284
285         // Extend visible range to show newly recorded data, if necessary
286         _lowest_note  = std::min(_lowest_note, _data_note_min);
287         _highest_note = std::max(_highest_note, _data_note_max);
288
289         veto_note_range(_lowest_note, _highest_note);
290
291         // Flag region views as invalid and disable drawing
292         for (i = region_views.begin(); i != region_views.end(); ++i) {
293                 (*i)->set_valid(false);
294                 (*i)->enable_display(false);
295         }
296
297         // Add and display region views, and flag them as valid
298         _trackview.track()->playlist()->foreach_region(
299                 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view))
300                 );
301
302         // Stack regions by layer, and remove invalid regions
303         layer_regions();
304
305         // Update note range (not regions which are correct) and draw note lines
306         apply_note_range(_lowest_note, _highest_note, false);
307 }
308
309
310 void
311 MidiStreamView::update_contents_height ()
312 {
313         StreamView::update_contents_height();
314         _note_lines->property_y2() = child_height ();
315
316         apply_note_range (lowest_note(), highest_note(), true);
317 }
318
319 void
320 MidiStreamView::draw_note_lines()
321 {
322         if (!_note_lines) {
323                 return;
324         }
325
326         double y;
327         double prev_y = contents_height();
328         uint32_t color;
329
330         _note_lines->clear();
331         
332         if (child_height() < 140){
333                 return;
334         }
335
336         for (int i = lowest_note(); i <= highest_note(); ++i) {
337                 y = floor(note_to_y(i));
338
339                 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
340
341                 switch (i % 12) {
342                 case 1:
343                 case 3:
344                 case 6:
345                 case 8:
346                 case 10:
347                         color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
348                         break;
349                 default:
350                         color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
351                         break;
352                 }
353
354                 if (i == highest_note()) {
355                         _note_lines->add_line(y, prev_y - y, color);
356                 } else {
357                         _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
358                 }
359
360                 prev_y = y;
361         }
362 }
363
364 void
365 MidiStreamView::set_note_range(VisibleNoteRange r)
366 {
367         if (r == FullRange) {
368                 _lowest_note = 0;
369                 _highest_note = 127;
370         } else {
371                 _lowest_note = _data_note_min;
372                 _highest_note = _data_note_max;
373         }
374
375         apply_note_range(_lowest_note, _highest_note, true);
376 }
377
378 void
379 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
380 {
381         _highest_note = highest;
382         _lowest_note = lowest;
383         
384         int const range = _highest_note - _lowest_note;  
385         int const pixels_per_note = floor (child_height () / range);
386         
387         /* do not grow note height beyond 10 pixels */
388         if (pixels_per_note > 10) {
389                 
390                 int const available_note_range = floor (child_height() / 10);
391                 int additional_notes = available_note_range - range;
392                 
393                 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
394                 for (int i = 0; i < additional_notes; i++){
395                         
396                         if (i % 2 && _highest_note < 127){
397                                 _highest_note++;
398                         }
399                         else if (i % 2) {
400                                 _lowest_note--;
401                         }
402                         else if (_lowest_note > 0){
403                                 _lowest_note--;
404                         }
405                         else {
406                                 _highest_note++;
407                         }
408                 }
409         }
410         
411         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
412         note_range_adjustment.set_value(_lowest_note);
413         
414         draw_note_lines();
415
416         if (to_region_views) {
417                 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
418                         ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
419                 }
420         }
421
422         NoteRangeChanged();
423 }
424
425 void
426 MidiStreamView::update_note_range(uint8_t note_num)
427 {
428         assert(note_num <= 127);
429         _data_note_min = min(_data_note_min, note_num);
430         _data_note_max = max(_data_note_max, note_num);
431 }
432
433 void
434 MidiStreamView::setup_rec_box ()
435 {
436         // cerr << _trackview.name() << " streamview SRB\n";
437
438         if (_trackview.session()->transport_rolling()) {
439
440                 if (!rec_active &&
441                     _trackview.session()->record_status() == Session::Recording &&
442                     _trackview.track()->record_enabled()) {
443
444                         if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
445
446                                 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
447
448                                 MidiRegion::SourceList sources;
449
450                                 rec_data_ready_connections.drop_connections ();
451
452                                 sources.push_back (_trackview.midi_track()->write_source());
453
454                                 // handle multi
455
456                                 framepos_t start = 0;
457                                 if (rec_regions.size() > 0) {
458                                         start = rec_regions.back().first->start()
459                                                         + _trackview.track()->get_captured_frames(rec_regions.size()-1);
460                                 }
461
462                                 if (!rec_regions.empty()) {
463                                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
464                                         mrv->end_write ();
465                                 }
466                                 
467                                 PropertyList plist; 
468                                 
469                                 plist.add (ARDOUR::Properties::start, start);
470                                 plist.add (ARDOUR::Properties::length, 1);
471                                 plist.add (ARDOUR::Properties::name, string());
472                                 plist.add (ARDOUR::Properties::layer, 0);
473
474                                 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
475                                                                       (RegionFactory::create (sources, plist, false)));
476
477                                 assert(region);
478                                 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0), this);
479                                 region->set_position (_trackview.track()->current_capture_start(), this);
480                                 RegionView* rv = add_region_view_internal (region, false);
481                                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
482                                 mrv->begin_write ();
483
484                                 rec_regions.push_back (make_pair (region, rv));
485
486                                 // rec regions are destroyed in setup_rec_box
487
488                                 /* we add the region later */
489
490                                 setup_new_rec_layer_time (region);
491                         }
492
493                         /* start a new rec box */
494
495                         boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
496                         framepos_t const frame_pos = mt->current_capture_start ();
497                         gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
498                         gdouble const xend = xstart;
499                         uint32_t fill_color;
500
501                         assert(_trackview.midi_track()->mode() == Normal);
502
503                         fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
504
505                         ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
506                         rec_rect->property_x1() = xstart;
507                         rec_rect->property_y1() = 1.0;
508                         rec_rect->property_x2() = xend;
509                         rec_rect->property_y2() = (double) _trackview.current_height() - 1;
510                         rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
511                         rec_rect->property_fill_color_rgba() = fill_color;
512                         rec_rect->lower_to_bottom();
513
514                         RecBoxInfo recbox;
515                         recbox.rectangle = rec_rect;
516                         recbox.start = _trackview.session()->transport_frame();
517                         recbox.length = 0;
518
519                         rec_rects.push_back (recbox);
520
521                         screen_update_connection.disconnect();
522                         screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
523                                         sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
524                         rec_updating = true;
525                         rec_active = true;
526
527                 } else if (rec_active &&
528                            (_trackview.session()->record_status() != Session::Recording ||
529                             !_trackview.track()->record_enabled())) {
530                         screen_update_connection.disconnect();
531                         rec_active = false;
532                         rec_updating = false;
533                 }
534
535         } else {
536
537                 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
538
539                 if (!rec_rects.empty() || !rec_regions.empty()) {
540
541                         /* disconnect rapid update */
542                         screen_update_connection.disconnect();
543                         rec_data_ready_connections.drop_connections ();
544
545                         rec_updating = false;
546                         rec_active = false;
547
548                         /* remove temp regions */
549
550                         for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
551                                 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
552
553                                 tmp = iter;
554                                 ++tmp;
555
556                                 (*iter).first->drop_references ();
557
558                                 iter = tmp;
559                         }
560
561                         rec_regions.clear();
562
563                         // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
564
565                         /* transport stopped, clear boxes */
566                         for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
567                                 RecBoxInfo &rect = (*iter);
568                                 delete rect.rectangle;
569                         }
570
571                         rec_rects.clear();
572
573                 }
574         }
575 }
576
577 void
578 MidiStreamView::color_handler ()
579 {
580         draw_note_lines ();
581
582         if (_trackview.is_midi_track()) {
583                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
584         } else {
585                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
586         }
587 }
588
589 void
590 MidiStreamView::note_range_adjustment_changed()
591 {
592         double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
593         int lowest = (int) floor(note_range_adjustment.get_value());
594         int highest;
595
596         if (sum == _range_sum_cache) {
597                 //cerr << "cached" << endl;
598                 highest = (int) floor(sum);
599         } else {
600                 //cerr << "recalc" << endl;
601                 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
602                 _range_sum_cache = sum;
603         }
604
605         if (lowest == _lowest_note && highest == _highest_note) {
606                 return;
607         }
608
609         //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
610         //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;
611
612         _lowest_note = lowest;
613         _highest_note = highest;
614         apply_note_range(lowest, highest, true);
615 }
616
617 void
618 MidiStreamView::update_rec_box ()
619 {
620         StreamView::update_rec_box ();
621
622         if (rec_regions.empty()) {
623                 return;
624         }
625
626         /* Update the region being recorded to reflect where we currently are */
627         boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
628         region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), this);
629
630         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
631         mrv->extend_active_notes ();
632 }
633