Remove erroneous assert which I added earlier.
[ardour2.git] / gtk2_ardour / midi_streamview.cc
blob79b30502b8da7947c904c3891a1754fba1d51ba6
1 /*
2 Copyright (C) 2001-2007 Paul Davis
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.
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.
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.
19 #include <cmath>
20 #include <cassert>
21 #include <utility>
23 #include <gtkmm.h>
25 #include <gtkmm2ext/gtk_ui.h>
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"
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"
53 using namespace std;
54 using namespace ARDOUR;
55 using namespace PBD;
56 using namespace Editing;
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)
70 /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
71 midi_underlay_group = new ArdourCanvas::Group (*_canvas_group);
72 midi_underlay_group->lower_to_bottom();
74 /* put the note lines in the timeaxisview's group, so it
75 can be put below ghost regions from MIDI underlays*/
76 _note_lines = new ArdourCanvas::LineSet(*_canvas_group,
77 ArdourCanvas::LineSet::Horizontal);
79 _note_lines->property_x1() = 0;
80 _note_lines->property_y1() = 0;
81 _note_lines->property_x2() = DBL_MAX;
82 _note_lines->property_y2() = 0;
84 _note_lines->signal_event().connect(
85 sigc::bind(sigc::mem_fun(_trackview.editor(),
86 &PublicEditor::canvas_stream_view_event),
87 _note_lines, &_trackview));
89 _note_lines->lower_to_bottom();
91 color_handler ();
93 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
95 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
96 note_range_adjustment.set_value(_lowest_note);
98 note_range_adjustment.signal_value_changed().connect(
99 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
102 MidiStreamView::~MidiStreamView ()
106 static void
107 veto_note_range(uint8_t& min, uint8_t& max)
109 /* Legal notes, thanks */
110 clamp_to_0_127(min);
111 clamp_to_0_127(max);
113 /* Always display at least one octave in [0, 127] */
114 if (max == 127) {
115 if (min > (127 - 11)) {
116 min = 127 - 11;
118 } else if (max < min + 11) {
119 uint8_t d = 11 - (max - min);
120 if (max + d/2 > 127) {
121 min -= d;
122 } else {
123 min -= d / 2;
124 max += d / 2;
127 assert(max - min >= 11);
128 assert(max <= 127);
129 assert(min <= 127);
132 RegionView*
133 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
135 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
137 if (region == 0) {
138 return 0;
141 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
142 _samples_per_unit, region_color);
144 region_view->init (region_color, false);
146 return region_view;
149 RegionView*
150 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
152 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
154 if (region == 0) {
155 return 0;
158 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
159 if ((*i)->region() == r) {
161 /* great. we already have a MidiRegionView for this Region. use it again. */
163 (*i)->set_valid (true);
165 display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
167 return 0;
171 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
172 if (region_view == 0) {
173 return 0;
176 region_views.push_front (region_view);
178 if (_trackview.editor().internal_editing()) {
179 region_view->hide_rect ();
180 } else {
181 region_view->show_rect ();
184 /* display events and find note range */
185 display_region (region_view, wfd);
187 /* catch regionview going away */
188 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
190 RegionViewAdded (region_view);
192 return region_view;
195 void
196 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
198 if (!region_view) {
199 return;
202 region_view->enable_display(true);
204 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
206 if (load_model) {
207 source->load_model();
210 _range_dirty = update_data_note_range(
211 source->model()->lowest_note(),
212 source->model()->highest_note());
214 // Display region contents
215 region_view->set_height (child_height());
216 region_view->display_model(source->model());
219 void
220 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
222 StreamView::display_track (tr);
224 draw_note_lines();
226 NoteRangeChanged();
229 void
230 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
232 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
233 if (mr) {
234 mr->midi_source(0)->load_model();
235 _range_dirty = update_data_note_range(
236 mr->model()->lowest_note(),
237 mr->model()->highest_note());
241 bool
242 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
244 bool dirty = false;
245 if (min < _data_note_min) {
246 _data_note_min = min;
247 dirty = true;
249 if (max > _data_note_max) {
250 _data_note_max = max;
251 dirty = true;
253 return dirty;
256 void
257 MidiStreamView::redisplay_track ()
259 if (!_trackview.is_midi_track()) {
260 return;
263 list<RegionView*>::iterator i;
265 // Load models if necessary, and find note range of all our contents
266 _range_dirty = false;
267 _data_note_min = 127;
268 _data_note_max = 0;
269 _trackview.track()->playlist()->foreach_region(
270 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
272 // No notes, use default range
273 if (!_range_dirty) {
274 _data_note_min = 60;
275 _data_note_max = 71;
278 // Extend visible range to show newly recorded data, if necessary
279 _lowest_note = std::min(_lowest_note, _data_note_min);
280 _highest_note = std::max(_highest_note, _data_note_max);
282 veto_note_range(_lowest_note, _highest_note);
284 // Flag region views as invalid and disable drawing
285 for (i = region_views.begin(); i != region_views.end(); ++i) {
286 (*i)->set_valid(false);
287 (*i)->enable_display(false);
290 // Add and display region views, and flag them as valid
291 _trackview.track()->playlist()->foreach_region(
292 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
294 // Stack regions by layer, and remove invalid regions
295 layer_regions();
297 // Update note range (not regions which are correct) and draw note lines
298 apply_note_range(_lowest_note, _highest_note, false);
302 void
303 MidiStreamView::update_contents_height ()
305 StreamView::update_contents_height();
306 _note_lines->property_y2() = child_height ();
308 apply_note_range (lowest_note(), highest_note(), true);
311 void
312 MidiStreamView::draw_note_lines()
314 if (!_note_lines || _updates_suspended) {
315 return;
318 double y;
319 double prev_y = contents_height();
320 uint32_t color;
322 _note_lines->clear();
324 if (child_height() < 140){
325 return;
328 for (int i = lowest_note(); i <= highest_note(); ++i) {
329 y = floor(note_to_y(i));
331 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
333 switch (i % 12) {
334 case 1:
335 case 3:
336 case 6:
337 case 8:
338 case 10:
339 color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
340 break;
341 default:
342 color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
343 break;
346 if (i == highest_note()) {
347 _note_lines->add_line(y, prev_y - y, color);
348 } else {
349 _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
352 prev_y = y;
356 void
357 MidiStreamView::set_note_range(VisibleNoteRange r)
359 if (r == FullRange) {
360 _lowest_note = 0;
361 _highest_note = 127;
362 } else {
363 _lowest_note = _data_note_min;
364 _highest_note = _data_note_max;
367 apply_note_range(_lowest_note, _highest_note, true);
370 void
371 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
373 _highest_note = highest;
374 _lowest_note = lowest;
376 int const max_note_height = 20; // This should probably be based on text size...
377 int const range = _highest_note - _lowest_note;
378 int const pixels_per_note = floor (child_height () / range);
380 /* do not grow note height beyond 10 pixels */
381 if (pixels_per_note > max_note_height) {
383 int const available_note_range = floor (child_height() / max_note_height);
384 int additional_notes = available_note_range - range;
386 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
387 for (int i = 0; i < additional_notes; i++){
389 if (i % 2 && _highest_note < 127){
390 _highest_note++;
392 else if (i % 2) {
393 _lowest_note--;
395 else if (_lowest_note > 0){
396 _lowest_note--;
398 else {
399 _highest_note++;
404 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
405 note_range_adjustment.set_value(_lowest_note);
407 draw_note_lines();
409 if (to_region_views) {
410 apply_note_range_to_regions ();
413 NoteRangeChanged();
416 void
417 MidiStreamView::apply_note_range_to_regions ()
419 if (!_updates_suspended) {
420 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
421 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
426 void
427 MidiStreamView::update_note_range(uint8_t note_num)
429 assert(note_num <= 127);
430 _data_note_min = min(_data_note_min, note_num);
431 _data_note_max = max(_data_note_max, note_num);
434 void
435 MidiStreamView::setup_rec_box ()
437 // cerr << _trackview.name() << " streamview SRB\n";
439 if (_trackview.session()->transport_rolling()) {
441 if (!rec_active &&
442 _trackview.session()->record_status() == Session::Recording &&
443 _trackview.track()->record_enabled()) {
445 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
447 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
449 MidiRegion::SourceList sources;
451 rec_data_ready_connections.drop_connections ();
453 sources.push_back (_trackview.midi_track()->write_source());
455 // handle multi
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);
463 if (!rec_regions.empty()) {
464 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
465 mrv->end_write ();
468 PropertyList plist;
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).
484 plist.add (ARDOUR::Properties::length_beats, 1);
485 plist.add (ARDOUR::Properties::name, string());
486 plist.add (ARDOUR::Properties::layer, 0);
488 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
489 (RegionFactory::create (sources, plist, false)));
491 assert(region);
492 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0));
493 region->set_position (_trackview.track()->current_capture_start());
494 RegionView* rv = add_region_view_internal (region, false);
495 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
496 mrv->begin_write ();
498 rec_regions.push_back (make_pair (region, rv));
500 // rec regions are destroyed in setup_rec_box
502 /* we add the region later */
504 setup_new_rec_layer_time (region);
507 /* start a new rec box */
509 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
510 framepos_t const frame_pos = mt->current_capture_start ();
511 gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
512 gdouble const xend = xstart;
513 uint32_t fill_color;
515 assert(_trackview.midi_track()->mode() == Normal);
517 fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
519 ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
520 rec_rect->property_x1() = xstart;
521 rec_rect->property_y1() = 1.0;
522 rec_rect->property_x2() = xend;
523 rec_rect->property_y2() = (double) _trackview.current_height() - 1;
524 rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
525 rec_rect->property_fill_color_rgba() = fill_color;
526 rec_rect->lower_to_bottom();
528 RecBoxInfo recbox;
529 recbox.rectangle = rec_rect;
530 recbox.start = _trackview.session()->transport_frame();
531 recbox.length = 0;
533 rec_rects.push_back (recbox);
535 screen_update_connection.disconnect();
536 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
537 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
538 rec_updating = true;
539 rec_active = true;
541 } else if (rec_active &&
542 (_trackview.session()->record_status() != Session::Recording ||
543 !_trackview.track()->record_enabled())) {
544 screen_update_connection.disconnect();
545 rec_active = false;
546 rec_updating = false;
549 } else {
551 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
553 if (!rec_rects.empty() || !rec_regions.empty()) {
555 /* disconnect rapid update */
556 screen_update_connection.disconnect();
557 rec_data_ready_connections.drop_connections ();
559 rec_updating = false;
560 rec_active = false;
562 /* remove temp regions */
564 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
565 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
567 tmp = iter;
568 ++tmp;
570 (*iter).first->drop_references ();
572 iter = tmp;
575 rec_regions.clear();
577 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
579 /* transport stopped, clear boxes */
580 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
581 RecBoxInfo &rect = (*iter);
582 delete rect.rectangle;
585 rec_rects.clear();
591 void
592 MidiStreamView::color_handler ()
594 draw_note_lines ();
596 if (_trackview.is_midi_track()) {
597 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
598 } else {
599 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
603 void
604 MidiStreamView::note_range_adjustment_changed()
606 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
607 int lowest = (int) floor(note_range_adjustment.get_value());
608 int highest;
610 if (sum == _range_sum_cache) {
611 //cerr << "cached" << endl;
612 highest = (int) floor(sum);
613 } else {
614 //cerr << "recalc" << endl;
615 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
616 _range_sum_cache = sum;
619 if (lowest == _lowest_note && highest == _highest_note) {
620 return;
623 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
624 //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;
626 _lowest_note = lowest;
627 _highest_note = highest;
628 apply_note_range(lowest, highest, true);
631 void
632 MidiStreamView::update_rec_box ()
634 StreamView::update_rec_box ();
636 if (rec_regions.empty()) {
637 return;
640 /* Update the region being recorded to reflect where we currently are */
641 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
642 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
644 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
645 mrv->extend_active_notes ();
648 uint8_t
649 MidiStreamView::y_to_note (double y) const
651 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
652 + lowest_note();
654 if (n < 0) {
655 return 0;
656 } else if (n > 127) {
657 return 127;
660 return n;
663 /** Suspend updates to the regions' note ranges and our
664 * note lines until resume_updates() is called.
666 void
667 MidiStreamView::suspend_updates ()
669 _updates_suspended = true;
672 /** Resume updates to region note ranges and note lines,
673 * and update them now.
675 void
676 MidiStreamView::resume_updates ()
678 _updates_suspended = false;
680 draw_note_lines ();
681 apply_note_range_to_regions ();