Remove MIDI track default channel and its menu, and choose the channel for new notes...
[ardour2.git] / gtk2_ardour / midi_streamview.cc
blobfd3d5383aa55f25445f583fd60bac4c9e10e4fd2
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, ArdourCanvas::LineSet::Horizontal);
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;
83 _note_lines->signal_event().connect(sigc::bind(
84 sigc::mem_fun(_trackview.editor(), &PublicEditor::canvas_stream_view_event),
85 _note_lines, &_trackview));
87 _note_lines->lower_to_bottom();
89 color_handler ();
91 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
93 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
94 note_range_adjustment.set_value(_lowest_note);
96 note_range_adjustment.signal_value_changed().connect(
97 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
100 MidiStreamView::~MidiStreamView ()
104 static void
105 veto_note_range(uint8_t& min, uint8_t& max)
107 /* Legal notes, thanks */
108 clamp_to_0_127(min);
109 clamp_to_0_127(max);
111 /* Always display at least one octave in [0, 127] */
112 if (max == 127) {
113 if (min > (127 - 11)) {
114 min = 127 - 11;
116 } else if (max < min + 11) {
117 uint8_t d = 11 - (max - min);
118 if (max + d/2 > 127) {
119 min -= d;
120 } else {
121 min -= d / 2;
122 max += d / 2;
125 assert(max - min >= 11);
126 assert(max <= 127);
127 assert(min <= 127);
130 RegionView*
131 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
133 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
135 if (region == 0) {
136 return 0;
139 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
140 _samples_per_unit, region_color);
142 region_view->init (region_color, false);
144 return region_view;
147 RegionView*
148 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
150 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
152 if (region == 0) {
153 return 0;
156 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
157 if ((*i)->region() == r) {
159 /* great. we already have a MidiRegionView for this Region. use it again. */
161 (*i)->set_valid (true);
163 display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
165 return 0;
169 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
170 if (region_view == 0) {
171 return 0;
174 region_views.push_front (region_view);
176 if (_trackview.editor().internal_editing()) {
177 region_view->hide_rect ();
178 } else {
179 region_view->show_rect ();
182 /* display events and find note range */
183 display_region (region_view, wfd);
185 /* catch regionview going away */
186 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
188 RegionViewAdded (region_view);
190 return region_view;
193 void
194 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
196 if (!region_view) {
197 return;
200 region_view->enable_display(true);
202 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
204 if (load_model) {
205 source->load_model();
208 _range_dirty = update_data_note_range(
209 source->model()->lowest_note(),
210 source->model()->highest_note());
212 // Display region contents
213 region_view->set_height (child_height());
214 region_view->display_model(source->model());
217 void
218 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
220 StreamView::display_track (tr);
222 draw_note_lines();
224 NoteRangeChanged();
227 void
228 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
230 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
231 if (mr) {
232 mr->midi_source(0)->load_model();
233 _range_dirty = update_data_note_range(
234 mr->model()->lowest_note(),
235 mr->model()->highest_note());
239 bool
240 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
242 bool dirty = false;
243 if (min < _data_note_min) {
244 _data_note_min = min;
245 dirty = true;
247 if (max > _data_note_max) {
248 _data_note_max = max;
249 dirty = true;
251 return dirty;
254 void
255 MidiStreamView::redisplay_track ()
257 if (!_trackview.is_midi_track()) {
258 return;
261 list<RegionView*>::iterator i;
263 // Load models if necessary, and find note range of all our contents
264 _range_dirty = false;
265 _data_note_min = 127;
266 _data_note_max = 0;
267 _trackview.track()->playlist()->foreach_region(
268 sigc::mem_fun (*this, &StreamView::update_contents_metrics)
271 // No notes, use default range
272 if (!_range_dirty) {
273 _data_note_min = 60;
274 _data_note_max = 71;
277 // Extend visible range to show newly recorded data, if necessary
278 _lowest_note = std::min(_lowest_note, _data_note_min);
279 _highest_note = std::max(_highest_note, _data_note_max);
281 veto_note_range(_lowest_note, _highest_note);
283 // Flag region views as invalid and disable drawing
284 for (i = region_views.begin(); i != region_views.end(); ++i) {
285 (*i)->set_valid(false);
286 (*i)->enable_display(false);
289 // Add and display region views, and flag them as valid
290 _trackview.track()->playlist()->foreach_region(
291 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 range = _highest_note - _lowest_note;
377 int const pixels_per_note = floor (child_height () / range);
379 /* do not grow note height beyond 10 pixels */
380 if (pixels_per_note > 10) {
382 int const available_note_range = floor (child_height() / 10);
383 int additional_notes = available_note_range - range;
385 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
386 for (int i = 0; i < additional_notes; i++){
388 if (i % 2 && _highest_note < 127){
389 _highest_note++;
391 else if (i % 2) {
392 _lowest_note--;
394 else if (_lowest_note > 0){
395 _lowest_note--;
397 else {
398 _highest_note++;
403 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
404 note_range_adjustment.set_value(_lowest_note);
406 draw_note_lines();
408 if (to_region_views) {
409 apply_note_range_to_regions ();
412 NoteRangeChanged();
415 void
416 MidiStreamView::apply_note_range_to_regions ()
418 if (!_updates_suspended) {
419 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
420 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
425 void
426 MidiStreamView::update_note_range(uint8_t note_num)
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);
433 void
434 MidiStreamView::setup_rec_box ()
436 // cerr << _trackview.name() << " streamview SRB\n";
438 if (_trackview.session()->transport_rolling()) {
440 if (!rec_active &&
441 _trackview.session()->record_status() == Session::Recording &&
442 _trackview.track()->record_enabled()) {
444 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
446 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
448 MidiRegion::SourceList sources;
450 rec_data_ready_connections.drop_connections ();
452 sources.push_back (_trackview.midi_track()->write_source());
454 // handle multi
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);
462 if (!rec_regions.empty()) {
463 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
464 mrv->end_write ();
467 PropertyList plist;
469 plist.add (ARDOUR::Properties::start, start);
470 plist.add (ARDOUR::Properties::length, 1);
471 /* Just above we're setting this nascent region's length to 1. I think this
472 is so that the RegionView gets created with a non-zero width, as apparently
473 creating a RegionView with a zero width causes it never to be displayed
474 (there is a warning in TimeAxisViewItem::init about this). However, we
475 must also set length_beats to something non-zero, otherwise the frame length
476 of 1 causes length_beats to be set to some small quantity << 1. Then
477 when the position is set up below, this length_beats is used to recompute
478 length using BeatsFramesConverter::to, which is slightly innacurate for small
479 beats values because it converts floating point beats to bars, beats and
480 integer ticks. The upshot of which being that length gets set back to 0,
481 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
483 plist.add (ARDOUR::Properties::length_beats, 1);
484 plist.add (ARDOUR::Properties::name, string());
485 plist.add (ARDOUR::Properties::layer, 0);
487 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
488 (RegionFactory::create (sources, plist, false)));
490 assert(region);
491 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0), this);
492 region->set_position (_trackview.track()->current_capture_start(), this);
493 RegionView* rv = add_region_view_internal (region, false);
494 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
495 mrv->begin_write ();
497 rec_regions.push_back (make_pair (region, rv));
499 // rec regions are destroyed in setup_rec_box
501 /* we add the region later */
503 setup_new_rec_layer_time (region);
506 /* start a new rec box */
508 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
509 framepos_t const frame_pos = mt->current_capture_start ();
510 gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
511 gdouble const xend = xstart;
512 uint32_t fill_color;
514 assert(_trackview.midi_track()->mode() == Normal);
516 fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
518 ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
519 rec_rect->property_x1() = xstart;
520 rec_rect->property_y1() = 1.0;
521 rec_rect->property_x2() = xend;
522 rec_rect->property_y2() = (double) _trackview.current_height() - 1;
523 rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
524 rec_rect->property_fill_color_rgba() = fill_color;
525 rec_rect->lower_to_bottom();
527 RecBoxInfo recbox;
528 recbox.rectangle = rec_rect;
529 recbox.start = _trackview.session()->transport_frame();
530 recbox.length = 0;
532 rec_rects.push_back (recbox);
534 screen_update_connection.disconnect();
535 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
536 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
537 rec_updating = true;
538 rec_active = true;
540 } else if (rec_active &&
541 (_trackview.session()->record_status() != Session::Recording ||
542 !_trackview.track()->record_enabled())) {
543 screen_update_connection.disconnect();
544 rec_active = false;
545 rec_updating = false;
548 } else {
550 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
552 if (!rec_rects.empty() || !rec_regions.empty()) {
554 /* disconnect rapid update */
555 screen_update_connection.disconnect();
556 rec_data_ready_connections.drop_connections ();
558 rec_updating = false;
559 rec_active = false;
561 /* remove temp regions */
563 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
564 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
566 tmp = iter;
567 ++tmp;
569 (*iter).first->drop_references ();
571 iter = tmp;
574 rec_regions.clear();
576 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
578 /* transport stopped, clear boxes */
579 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
580 RecBoxInfo &rect = (*iter);
581 delete rect.rectangle;
584 rec_rects.clear();
590 void
591 MidiStreamView::color_handler ()
593 draw_note_lines ();
595 if (_trackview.is_midi_track()) {
596 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
597 } else {
598 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
602 void
603 MidiStreamView::note_range_adjustment_changed()
605 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
606 int lowest = (int) floor(note_range_adjustment.get_value());
607 int highest;
609 if (sum == _range_sum_cache) {
610 //cerr << "cached" << endl;
611 highest = (int) floor(sum);
612 } else {
613 //cerr << "recalc" << endl;
614 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
615 _range_sum_cache = sum;
618 if (lowest == _lowest_note && highest == _highest_note) {
619 return;
622 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
623 //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;
625 _lowest_note = lowest;
626 _highest_note = highest;
627 apply_note_range(lowest, highest, true);
630 void
631 MidiStreamView::update_rec_box ()
633 StreamView::update_rec_box ();
635 if (rec_regions.empty()) {
636 return;
639 /* Update the region being recorded to reflect where we currently are */
640 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
641 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), this);
643 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
644 mrv->extend_active_notes ();
647 uint8_t
648 MidiStreamView::y_to_note (double y) const
650 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
651 + lowest_note();
653 if (n < 0) {
654 return 0;
655 } else if (n > 127) {
656 return 127;
659 return n;
662 /** Suspend updates to the regions' note ranges and our
663 * note lines until resume_updates() is called.
665 void
666 MidiStreamView::suspend_updates ()
668 _updates_suspended = true;
671 /** Resume updates to region note ranges and note lines,
672 * and update them now.
674 void
675 MidiStreamView::resume_updates ()
677 _updates_suspended = false;
679 draw_note_lines ();
680 apply_note_range_to_regions ();