its about that time
[ardour2.git] / gtk2_ardour / midi_streamview.cc
blobb79a6dc6d0a61940f533bb5b80b8df69f959592f
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)
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();
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);
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;
82 _note_lines->signal_event().connect(sigc::bind(
83 sigc::mem_fun(_trackview.editor(), &PublicEditor::canvas_stream_view_event),
84 _note_lines, &_trackview));
86 _note_lines->lower_to_bottom();
88 color_handler ();
90 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
92 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
93 note_range_adjustment.set_value(_lowest_note);
95 note_range_adjustment.signal_value_changed().connect(
96 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
99 MidiStreamView::~MidiStreamView ()
103 static void
104 veto_note_range(uint8_t& min, uint8_t& max)
106 /* Legal notes, thanks */
107 clamp_to_0_127(min);
108 clamp_to_0_127(max);
110 /* Always display at least one octave in [0, 127] */
111 if (max == 127) {
112 if (min > (127 - 11)) {
113 min = 127 - 11;
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;
124 assert(max - min >= 11);
125 assert(max <= 127);
126 assert(min <= 127);
129 RegionView*
130 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
132 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
134 if (region == 0) {
135 return 0;
138 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
139 _samples_per_unit, region_color);
141 region_view->init (region_color, false);
143 return region_view;
146 RegionView*
147 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
149 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
151 if (region == 0) {
152 return 0;
155 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
156 if ((*i)->region() == r) {
158 /* great. we already have a MidiRegionView for this Region. use it again. */
160 (*i)->set_valid (true);
162 display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
164 return 0;
168 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
169 if (region_view == 0) {
170 return 0;
173 region_views.push_front (region_view);
175 if (_trackview.editor().internal_editing()) {
176 region_view->hide_rect ();
177 } else {
178 region_view->show_rect ();
181 /* display events and find note range */
182 display_region (region_view, wfd);
184 /* catch regionview going away */
185 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
187 RegionViewAdded (region_view);
189 return region_view;
192 void
193 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
195 if (!region_view) {
196 return;
199 region_view->enable_display(true);
201 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
203 if (load_model) {
204 source->load_model();
207 _range_dirty = update_data_note_range(
208 source->model()->lowest_note(),
209 source->model()->highest_note());
211 // Display region contents
212 region_view->set_height (child_height());
213 region_view->display_model(source->model());
216 void
217 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
219 StreamView::display_track (tr);
221 draw_note_lines();
223 NoteRangeChanged();
226 void
227 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
229 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
230 if (mr) {
231 mr->midi_source(0)->load_model();
232 _range_dirty = update_data_note_range(
233 mr->model()->lowest_note(),
234 mr->model()->highest_note());
238 bool
239 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
241 bool dirty = false;
242 if (min < _data_note_min) {
243 _data_note_min = min;
244 dirty = true;
246 if (max > _data_note_max) {
247 _data_note_max = max;
248 dirty = true;
250 return dirty;
253 void
254 MidiStreamView::redisplay_track ()
256 if (!_trackview.is_midi_track()) {
257 return;
260 list<RegionView*>::iterator i;
262 // Load models if necessary, and find note range of all our contents
263 _range_dirty = false;
264 _data_note_min = 127;
265 _data_note_max = 0;
266 _trackview.track()->playlist()->foreach_region(
267 sigc::mem_fun (*this, &StreamView::update_contents_metrics)
270 // No notes, use default range
271 if (!_range_dirty) {
272 _data_note_min = 60;
273 _data_note_max = 71;
276 // Extend visible range to show newly recorded data, if necessary
277 _lowest_note = std::min(_lowest_note, _data_note_min);
278 _highest_note = std::max(_highest_note, _data_note_max);
280 veto_note_range(_lowest_note, _highest_note);
282 // Flag region views as invalid and disable drawing
283 for (i = region_views.begin(); i != region_views.end(); ++i) {
284 (*i)->set_valid(false);
285 (*i)->enable_display(false);
288 // Add and display region views, and flag them as valid
289 _trackview.track()->playlist()->foreach_region(
290 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view))
293 // Stack regions by layer, and remove invalid regions
294 layer_regions();
296 // Update note range (not regions which are correct) and draw note lines
297 apply_note_range(_lowest_note, _highest_note, false);
301 void
302 MidiStreamView::update_contents_height ()
304 StreamView::update_contents_height();
305 _note_lines->property_y2() = child_height ();
307 apply_note_range (lowest_note(), highest_note(), true);
310 void
311 MidiStreamView::draw_note_lines()
313 if (!_note_lines) {
314 return;
317 double y;
318 double prev_y = contents_height();
319 uint32_t color;
321 _note_lines->clear();
323 if (child_height() < 140){
324 return;
327 for (int i = lowest_note(); i <= highest_note(); ++i) {
328 y = floor(note_to_y(i));
330 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
332 switch (i % 12) {
333 case 1:
334 case 3:
335 case 6:
336 case 8:
337 case 10:
338 color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
339 break;
340 default:
341 color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
342 break;
345 if (i == highest_note()) {
346 _note_lines->add_line(y, prev_y - y, color);
347 } else {
348 _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
351 prev_y = y;
355 void
356 MidiStreamView::set_note_range(VisibleNoteRange r)
358 if (r == FullRange) {
359 _lowest_note = 0;
360 _highest_note = 127;
361 } else {
362 _lowest_note = _data_note_min;
363 _highest_note = _data_note_max;
366 apply_note_range(_lowest_note, _highest_note, true);
369 void
370 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
372 _highest_note = highest;
373 _lowest_note = lowest;
375 int const range = _highest_note - _lowest_note;
376 int const pixels_per_note = floor (child_height () / range);
378 /* do not grow note height beyond 10 pixels */
379 if (pixels_per_note > 10) {
381 int const available_note_range = floor (child_height() / 10);
382 int additional_notes = available_note_range - range;
384 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
385 for (int i = 0; i < additional_notes; i++){
387 if (i % 2 && _highest_note < 127){
388 _highest_note++;
390 else if (i % 2) {
391 _lowest_note--;
393 else if (_lowest_note > 0){
394 _lowest_note--;
396 else {
397 _highest_note++;
402 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
403 note_range_adjustment.set_value(_lowest_note);
405 draw_note_lines();
407 if (to_region_views) {
408 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
409 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
413 NoteRangeChanged();
416 void
417 MidiStreamView::update_note_range(uint8_t note_num)
419 assert(note_num <= 127);
420 _data_note_min = min(_data_note_min, note_num);
421 _data_note_max = max(_data_note_max, note_num);
424 void
425 MidiStreamView::setup_rec_box ()
427 // cerr << _trackview.name() << " streamview SRB\n";
429 if (_trackview.session()->transport_rolling()) {
431 if (!rec_active &&
432 _trackview.session()->record_status() == Session::Recording &&
433 _trackview.track()->record_enabled()) {
435 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
437 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
439 MidiRegion::SourceList sources;
441 rec_data_ready_connections.drop_connections ();
443 sources.push_back (_trackview.midi_track()->write_source());
445 // handle multi
447 framepos_t start = 0;
448 if (rec_regions.size() > 0) {
449 start = rec_regions.back().first->start()
450 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
453 if (!rec_regions.empty()) {
454 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
455 mrv->end_write ();
458 PropertyList plist;
460 plist.add (ARDOUR::Properties::start, start);
461 plist.add (ARDOUR::Properties::length, 1);
462 /* Just above we're setting this nascent region's length to 1. I think this
463 is so that the RegionView gets created with a non-zero width, as apparently
464 creating a RegionView with a zero width causes it never to be displayed
465 (there is a warning in TimeAxisViewItem::init about this). However, we
466 must also set length_beats to something non-zero, otherwise the frame length
467 of 1 causes length_beats to be set to some small quantity << 1. Then
468 when the position is set up below, this length_beats is used to recompute
469 length using BeatsFramesConverter::to, which is slightly innacurate for small
470 beats values because it converts floating point beats to bars, beats and
471 integer ticks. The upshot of which being that length gets set back to 0,
472 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
474 plist.add (ARDOUR::Properties::length_beats, 1);
475 plist.add (ARDOUR::Properties::name, string());
476 plist.add (ARDOUR::Properties::layer, 0);
478 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
479 (RegionFactory::create (sources, plist, false)));
481 assert(region);
482 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0), this);
483 region->set_position (_trackview.track()->current_capture_start(), this);
484 RegionView* rv = add_region_view_internal (region, false);
485 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
486 mrv->begin_write ();
488 rec_regions.push_back (make_pair (region, rv));
490 // rec regions are destroyed in setup_rec_box
492 /* we add the region later */
494 setup_new_rec_layer_time (region);
497 /* start a new rec box */
499 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
500 framepos_t const frame_pos = mt->current_capture_start ();
501 gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
502 gdouble const xend = xstart;
503 uint32_t fill_color;
505 assert(_trackview.midi_track()->mode() == Normal);
507 fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
509 ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
510 rec_rect->property_x1() = xstart;
511 rec_rect->property_y1() = 1.0;
512 rec_rect->property_x2() = xend;
513 rec_rect->property_y2() = (double) _trackview.current_height() - 1;
514 rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
515 rec_rect->property_fill_color_rgba() = fill_color;
516 rec_rect->lower_to_bottom();
518 RecBoxInfo recbox;
519 recbox.rectangle = rec_rect;
520 recbox.start = _trackview.session()->transport_frame();
521 recbox.length = 0;
523 rec_rects.push_back (recbox);
525 screen_update_connection.disconnect();
526 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
527 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
528 rec_updating = true;
529 rec_active = true;
531 } else if (rec_active &&
532 (_trackview.session()->record_status() != Session::Recording ||
533 !_trackview.track()->record_enabled())) {
534 screen_update_connection.disconnect();
535 rec_active = false;
536 rec_updating = false;
539 } else {
541 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
543 if (!rec_rects.empty() || !rec_regions.empty()) {
545 /* disconnect rapid update */
546 screen_update_connection.disconnect();
547 rec_data_ready_connections.drop_connections ();
549 rec_updating = false;
550 rec_active = false;
552 /* remove temp regions */
554 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
555 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
557 tmp = iter;
558 ++tmp;
560 (*iter).first->drop_references ();
562 iter = tmp;
565 rec_regions.clear();
567 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
569 /* transport stopped, clear boxes */
570 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
571 RecBoxInfo &rect = (*iter);
572 delete rect.rectangle;
575 rec_rects.clear();
581 void
582 MidiStreamView::color_handler ()
584 draw_note_lines ();
586 if (_trackview.is_midi_track()) {
587 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
588 } else {
589 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
593 void
594 MidiStreamView::note_range_adjustment_changed()
596 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
597 int lowest = (int) floor(note_range_adjustment.get_value());
598 int highest;
600 if (sum == _range_sum_cache) {
601 //cerr << "cached" << endl;
602 highest = (int) floor(sum);
603 } else {
604 //cerr << "recalc" << endl;
605 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
606 _range_sum_cache = sum;
609 if (lowest == _lowest_note && highest == _highest_note) {
610 return;
613 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
614 //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;
616 _lowest_note = lowest;
617 _highest_note = highest;
618 apply_note_range(lowest, highest, true);
621 void
622 MidiStreamView::update_rec_box ()
624 StreamView::update_rec_box ();
626 if (rec_regions.empty()) {
627 return;
630 /* Update the region being recorded to reflect where we currently are */
631 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
632 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), this);
634 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
635 mrv->extend_active_notes ();
638 uint8_t
639 MidiStreamView::y_to_note (double y) const
641 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
642 + lowest_note();
644 if (n < 0) {
645 return 0;
646 } else if (n > 127) {
647 return 127;
650 return n;