deframe audio regions as well as MIDI when entering 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::display_track (boost::shared_ptr<Track> tr)
211 {
212         StreamView::display_track (tr);
213
214         draw_note_lines();
215         
216         NoteRangeChanged();
217 }
218
219 void
220 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
221 {
222         boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
223         if (mr) {
224                 mr->midi_source(0)->load_model();
225                 _range_dirty = update_data_note_range(
226                                 mr->model()->lowest_note(),
227                                 mr->model()->highest_note());
228         }
229 }
230
231 bool
232 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
233 {
234         bool dirty = false;
235         if (min < _data_note_min) {
236                 _data_note_min = min;
237                 dirty = true;
238         }
239         if (max > _data_note_max) {
240                 _data_note_max = max;
241                 dirty = true;
242         }
243         return dirty;
244 }
245
246 void
247 MidiStreamView::redisplay_track ()
248 {
249         if (!_trackview.is_midi_track()) {
250                 return;
251         }
252
253         list<RegionView*>::iterator i;
254
255         // Load models if necessary, and find note range of all our contents
256         _range_dirty = false;
257         _data_note_min = 127;
258         _data_note_max = 0;
259         _trackview.track()->playlist()->foreach_region(
260                 sigc::mem_fun (*this, &StreamView::update_contents_metrics)
261                 );
262
263         // No notes, use default range
264         if (!_range_dirty) {
265                 _data_note_min = 60;
266                 _data_note_max = 71;
267         }
268
269         // Extend visible range to show newly recorded data, if necessary
270         _lowest_note  = std::min(_lowest_note, _data_note_min);
271         _highest_note = std::max(_highest_note, _data_note_max);
272
273         veto_note_range(_lowest_note, _highest_note);
274
275         // Flag region views as invalid and disable drawing
276         for (i = region_views.begin(); i != region_views.end(); ++i) {
277                 (*i)->set_valid(false);
278                 (*i)->enable_display(false);
279         }
280
281         // Add and display region views, and flag them as valid
282         _trackview.track()->playlist()->foreach_region(
283                 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view))
284                 );
285
286         // Stack regions by layer, and remove invalid regions
287         layer_regions();
288
289         // Update note range (not regions which are correct) and draw note lines
290         apply_note_range(_lowest_note, _highest_note, false);
291 }
292
293
294 void
295 MidiStreamView::update_contents_height ()
296 {
297         StreamView::update_contents_height();
298         _note_lines->property_y2() = child_height ();
299
300         apply_note_range (lowest_note(), highest_note(), true);
301 }
302
303 void
304 MidiStreamView::draw_note_lines()
305 {
306         if (!_note_lines) {
307                 return;
308         }
309
310         double y;
311         double prev_y = contents_height();
312         uint32_t color;
313
314         _note_lines->clear();
315         
316         if (child_height() < 140){
317                 return;
318         }
319
320         for (int i = lowest_note(); i <= highest_note(); ++i) {
321                 y = floor(note_to_y(i));
322
323                 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
324
325                 switch (i % 12) {
326                 case 1:
327                 case 3:
328                 case 6:
329                 case 8:
330                 case 10:
331                         color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
332                         break;
333                 default:
334                         color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
335                         break;
336                 }
337
338                 if (i == highest_note()) {
339                         _note_lines->add_line(y, prev_y - y, color);
340                 } else {
341                         _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
342                 }
343
344                 prev_y = y;
345         }
346 }
347
348 void
349 MidiStreamView::set_note_range(VisibleNoteRange r)
350 {
351         if (r == FullRange) {
352                 _lowest_note = 0;
353                 _highest_note = 127;
354         } else {
355                 _lowest_note = _data_note_min;
356                 _highest_note = _data_note_max;
357         }
358
359         apply_note_range(_lowest_note, _highest_note, true);
360 }
361
362 void
363 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
364 {
365         _highest_note = highest;
366         _lowest_note = lowest;
367         
368         int const range = _highest_note - _lowest_note;  
369         int const pixels_per_note = floor (child_height () / range);
370         
371         /* do not grow note height beyond 10 pixels */
372         if (pixels_per_note > 10) {
373                 
374                 int const available_note_range = floor (child_height() / 10);
375                 int additional_notes = available_note_range - range;
376                 
377                 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
378                 for (int i = 0; i < additional_notes; i++){
379                         
380                         if (i % 2 && _highest_note < 127){
381                                 _highest_note++;
382                         }
383                         else if (i % 2) {
384                                 _lowest_note--;
385                         }
386                         else if (_lowest_note > 0){
387                                 _lowest_note--;
388                         }
389                         else {
390                                 _highest_note++;
391                         }
392                 }
393         }
394         
395         note_range_adjustment.set_page_size(_highest_note - _lowest_note);
396         note_range_adjustment.set_value(_lowest_note);
397         
398         draw_note_lines();
399
400         if (to_region_views) {
401                 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
402                         ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
403                 }
404         }
405
406         NoteRangeChanged();
407 }
408
409 void
410 MidiStreamView::update_note_range(uint8_t note_num)
411 {
412         assert(note_num <= 127);
413         _data_note_min = min(_data_note_min, note_num);
414         _data_note_max = max(_data_note_max, note_num);
415 }
416
417 void
418 MidiStreamView::setup_rec_box ()
419 {
420         // cerr << _trackview.name() << " streamview SRB\n";
421
422         if (_trackview.session()->transport_rolling()) {
423
424                 if (!rec_active &&
425                     _trackview.session()->record_status() == Session::Recording &&
426                     _trackview.track()->record_enabled()) {
427
428                         if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
429
430                                 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
431
432                                 MidiRegion::SourceList sources;
433
434                                 rec_data_ready_connections.drop_connections ();
435
436                                 sources.push_back (_trackview.midi_track()->write_source());
437
438                                 // handle multi
439
440                                 framepos_t start = 0;
441                                 if (rec_regions.size() > 0) {
442                                         start = rec_regions.back().first->start()
443                                                         + _trackview.track()->get_captured_frames(rec_regions.size()-1);
444                                 }
445
446                                 if (!rec_regions.empty()) {
447                                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
448                                         mrv->end_write ();
449                                 }
450                                 
451                                 PropertyList plist; 
452                                 
453                                 plist.add (ARDOUR::Properties::start, start);
454                                 plist.add (ARDOUR::Properties::length, 1);
455                                 plist.add (ARDOUR::Properties::name, string());
456                                 plist.add (ARDOUR::Properties::layer, 0);
457
458                                 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
459                                                                       (RegionFactory::create (sources, plist, false)));
460
461                                 assert(region);
462                                 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0), this);
463                                 region->set_position (_trackview.track()->current_capture_start(), this);
464                                 RegionView* rv = add_region_view_internal (region, false);
465                                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
466                                 mrv->begin_write ();
467
468                                 rec_regions.push_back (make_pair (region, rv));
469
470                                 // rec regions are destroyed in setup_rec_box
471
472                                 /* we add the region later */
473
474                                 setup_new_rec_layer_time (region);
475                         }
476
477                         /* start a new rec box */
478
479                         boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
480                         framepos_t const frame_pos = mt->current_capture_start ();
481                         gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
482                         gdouble const xend = xstart;
483                         uint32_t fill_color;
484
485                         assert(_trackview.midi_track()->mode() == Normal);
486
487                         fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
488
489                         ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
490                         rec_rect->property_x1() = xstart;
491                         rec_rect->property_y1() = 1.0;
492                         rec_rect->property_x2() = xend;
493                         rec_rect->property_y2() = (double) _trackview.current_height() - 1;
494                         rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
495                         rec_rect->property_fill_color_rgba() = fill_color;
496                         rec_rect->lower_to_bottom();
497
498                         RecBoxInfo recbox;
499                         recbox.rectangle = rec_rect;
500                         recbox.start = _trackview.session()->transport_frame();
501                         recbox.length = 0;
502
503                         rec_rects.push_back (recbox);
504
505                         screen_update_connection.disconnect();
506                         screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
507                                         sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
508                         rec_updating = true;
509                         rec_active = true;
510
511                 } else if (rec_active &&
512                            (_trackview.session()->record_status() != Session::Recording ||
513                             !_trackview.track()->record_enabled())) {
514                         screen_update_connection.disconnect();
515                         rec_active = false;
516                         rec_updating = false;
517                 }
518
519         } else {
520
521                 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
522
523                 if (!rec_rects.empty() || !rec_regions.empty()) {
524
525                         /* disconnect rapid update */
526                         screen_update_connection.disconnect();
527                         rec_data_ready_connections.drop_connections ();
528
529                         rec_updating = false;
530                         rec_active = false;
531
532                         /* remove temp regions */
533
534                         for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
535                                 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
536
537                                 tmp = iter;
538                                 ++tmp;
539
540                                 (*iter).first->drop_references ();
541
542                                 iter = tmp;
543                         }
544
545                         rec_regions.clear();
546
547                         // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
548
549                         /* transport stopped, clear boxes */
550                         for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
551                                 RecBoxInfo &rect = (*iter);
552                                 delete rect.rectangle;
553                         }
554
555                         rec_rects.clear();
556
557                 }
558         }
559 }
560
561 void
562 MidiStreamView::color_handler ()
563 {
564         draw_note_lines ();
565
566         if (_trackview.is_midi_track()) {
567                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
568         } else {
569                 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
570         }
571 }
572
573 void
574 MidiStreamView::note_range_adjustment_changed()
575 {
576         double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
577         int lowest = (int) floor(note_range_adjustment.get_value());
578         int highest;
579
580         if (sum == _range_sum_cache) {
581                 //cerr << "cached" << endl;
582                 highest = (int) floor(sum);
583         } else {
584                 //cerr << "recalc" << endl;
585                 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
586                 _range_sum_cache = sum;
587         }
588
589         if (lowest == _lowest_note && highest == _highest_note) {
590                 return;
591         }
592
593         //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
594         //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;
595
596         _lowest_note = lowest;
597         _highest_note = highest;
598         apply_note_range(lowest, highest, true);
599 }
600
601 void
602 MidiStreamView::update_rec_box ()
603 {
604         StreamView::update_rec_box ();
605
606         if (rec_regions.empty()) {
607                 return;
608         }
609
610         /* Update the region being recorded to reflect where we currently are */
611         boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
612         region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), this);
613
614         MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
615         mrv->extend_active_notes ();
616 }
617