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.
24 #include <gtkmm2ext/gtk_ui.h>
26 #include "canvas/line_set.h"
27 #include "canvas/rectangle.h"
29 #include "ardour/midi_region.h"
30 #include "ardour/midi_source.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/operations.h"
33 #include "ardour/region_factory.h"
34 #include "ardour/session.h"
35 #include "ardour/smf_source.h"
37 #include "ardour_ui.h"
38 #include "global_signals.h"
39 #include "gui_thread.h"
40 #include "midi_region_view.h"
41 #include "midi_streamview.h"
42 #include "midi_time_axis.h"
43 #include "midi_util.h"
44 #include "public_editor.h"
45 #include "region_selection.h"
46 #include "region_view.h"
47 #include "rgb_macros.h"
48 #include "selection.h"
54 using namespace ARDOUR;
55 using namespace ARDOUR_UI_UTILS;
57 using namespace Editing;
59 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
61 , note_range_adjustment(0.0f, 0.0f, 0.0f)
63 , _range_sum_cache(-1.0)
69 , _updates_suspended (false)
71 /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
72 midi_underlay_group = new ArdourCanvas::Container (_canvas_group);
73 midi_underlay_group->lower_to_bottom();
75 /* put the note lines in the timeaxisview's group, so it
76 can be put below ghost regions from MIDI underlays
78 _note_lines = new ArdourCanvas::LineSet (_canvas_group, ArdourCanvas::LineSet::Horizontal);
80 _note_lines->Event.connect(
81 sigc::bind(sigc::mem_fun(_trackview.editor(),
82 &PublicEditor::canvas_stream_view_event),
83 _note_lines, &_trackview));
85 _note_lines->lower_to_bottom();
89 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
91 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
92 note_range_adjustment.set_value(_lowest_note);
94 note_range_adjustment.signal_value_changed().connect(
95 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
98 MidiStreamView::~MidiStreamView ()
103 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool recording)
105 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
111 RegionView* region_view = NULL;
113 region_view = new MidiRegionView (
114 _canvas_group, _trackview, region,
115 _samples_per_pixel, region_color, recording,
116 TimeAxisViewItem::Visibility(TimeAxisViewItem::ShowFrame));
118 region_view = new MidiRegionView (_canvas_group, _trackview, region,
119 _samples_per_pixel, region_color);
122 region_view->init (false);
128 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
130 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
136 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
137 if ((*i)->region() == r) {
139 /* great. we already have a MidiRegionView for this Region. use it again. */
141 (*i)->set_valid (true);
143 display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
149 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
150 if (region_view == 0) {
154 region_views.push_front (region_view);
156 if (_trackview.editor().internal_editing()) {
157 region_view->hide_rect ();
159 region_view->show_rect ();
162 /* display events and find note range */
163 display_region (region_view, wait_for_data);
165 /* fit note range if we are importing */
166 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
167 set_note_range (ContentsRange);
170 /* catch regionview going away */
171 boost::weak_ptr<Region> wr (region); // make this explicit
172 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
174 RegionViewAdded (region_view);
180 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
186 region_view->enable_display (true);
187 region_view->set_height (child_height());
189 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
191 error << _("attempt to display MIDI region with no source") << endmsg;
196 source->load_model();
199 if (!source->model()) {
200 error << _("attempt to display MIDI region with no model") << endmsg;
204 _range_dirty = update_data_note_range(
205 source->model()->lowest_note(),
206 source->model()->highest_note());
208 // Display region contents
209 region_view->display_model(source->model());
214 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
216 StreamView::display_track (tr);
224 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
226 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
228 mr->midi_source(0)->load_model();
229 _range_dirty = update_data_note_range(
230 mr->model()->lowest_note(),
231 mr->model()->highest_note());
236 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
239 if (min < _data_note_min) {
240 _data_note_min = min;
243 if (max > _data_note_max) {
244 _data_note_max = max;
251 MidiStreamView::redisplay_track ()
253 if (!_trackview.is_midi_track()) {
257 list<RegionView*>::iterator i;
259 // Load models if necessary, and find note range of all our contents
260 _range_dirty = false;
261 _data_note_min = 127;
263 _trackview.track()->playlist()->foreach_region(
264 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
266 // No notes, use default range
272 // Flag region views as invalid and disable drawing
273 for (i = region_views.begin(); i != region_views.end(); ++i) {
274 (*i)->set_valid(false);
275 (*i)->enable_display(false);
278 // Add and display region views, and flag them as valid
279 _trackview.track()->playlist()->foreach_region(
280 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
282 // Stack regions by layer, and remove invalid regions
285 // Update note range (not regions which are correct) and draw note lines
286 apply_note_range(_lowest_note, _highest_note, false);
291 MidiStreamView::update_contents_height ()
293 StreamView::update_contents_height();
295 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
297 apply_note_range (lowest_note(), highest_note(), true);
301 MidiStreamView::draw_note_lines()
303 if (!_note_lines || _updates_suspended) {
311 _note_lines->clear();
313 if (child_height() < 140 || note_height() < 3) {
314 /* track is too small for note lines, or there are too many */
318 /* do this is order of highest ... lowest since that matches the
319 * coordinate system in which y=0 is at the top
322 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
324 y = floor(note_to_y (i)) + .5;
326 /* this is the line actually corresponding to the division
330 if (i <= highest_note()) {
331 _note_lines->add (y, 1.0, ARDOUR_UI::config()->color ("piano roll black outline"));
334 /* now add a thicker line/bar which covers the entire vertical
335 * height of this note.
344 color = ARDOUR_UI::config()->color_mod ("piano roll black", "piano roll black");
347 color = ARDOUR_UI::config()->color_mod ("piano roll white", "piano roll white");
351 double h = y - prev_y;
352 double mid = y + (h/2.0);
354 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
355 _note_lines->add (mid, h, color);
363 MidiStreamView::set_note_range(VisibleNoteRange r)
365 if (r == FullRange) {
369 _lowest_note = _data_note_min;
370 _highest_note = _data_note_max;
373 apply_note_range(_lowest_note, _highest_note, true);
377 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
379 _highest_note = highest;
380 _lowest_note = lowest;
382 int const max_note_height = 20; // This should probably be based on text size...
383 int const range = _highest_note - _lowest_note;
384 int const pixels_per_note = floor (child_height () / range);
386 /* do not grow note height beyond 10 pixels */
387 if (pixels_per_note > max_note_height) {
389 int const available_note_range = floor (child_height() / max_note_height);
390 int additional_notes = available_note_range - range;
392 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
393 for (int i = 0; i < additional_notes; i++){
395 if (i % 2 && _highest_note < 127){
401 else if (_lowest_note > 0){
410 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
411 note_range_adjustment.set_value(_lowest_note);
415 if (to_region_views) {
416 apply_note_range_to_regions ();
423 MidiStreamView::apply_note_range_to_regions ()
425 if (!_updates_suspended) {
426 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
427 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
433 MidiStreamView::update_note_range(uint8_t note_num)
435 _data_note_min = min(_data_note_min, note_num);
436 _data_note_max = max(_data_note_max, note_num);
440 MidiStreamView::setup_rec_box ()
442 // cerr << _trackview.name() << " streamview SRB\n";
444 if (_trackview.session()->transport_rolling()) {
447 _trackview.session()->record_status() == Session::Recording &&
448 _trackview.track()->record_enabled()) {
450 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
452 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
454 MidiRegion::SourceList sources;
456 rec_data_ready_connections.drop_connections ();
458 sources.push_back (_trackview.midi_track()->write_source());
462 framepos_t start = 0;
463 if (rec_regions.size() > 0) {
464 start = rec_regions.back().first->start()
465 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
468 if (!rec_regions.empty()) {
469 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
475 plist.add (ARDOUR::Properties::start, start);
476 plist.add (ARDOUR::Properties::length, 1);
477 /* Just above we're setting this nascent region's length to 1. I think this
478 is so that the RegionView gets created with a non-zero width, as apparently
479 creating a RegionView with a zero width causes it never to be displayed
480 (there is a warning in TimeAxisViewItem::init about this). However, we
481 must also set length_beats to something non-zero, otherwise the frame length
482 of 1 causes length_beats to be set to some small quantity << 1. Then
483 when the position is set up below, this length_beats is used to recompute
484 length using BeatsFramesConverter::to, which is slightly innacurate for small
485 beats values because it converts floating point beats to bars, beats and
486 integer ticks. The upshot of which being that length gets set back to 0,
487 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
489 plist.add (ARDOUR::Properties::length_beats, 1);
490 plist.add (ARDOUR::Properties::name, string());
491 plist.add (ARDOUR::Properties::layer, 0);
493 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
494 (RegionFactory::create (sources, plist, false)));
496 region->set_start (_trackview.track()->current_capture_start()
497 - _trackview.track()->get_capture_start_frame (0));
498 region->set_position (_trackview.track()->current_capture_start());
499 RegionView* rv = add_region_view_internal (region, false, true);
500 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
504 /* rec region will be destroyed in setup_rec_box */
505 rec_regions.push_back (make_pair (region, rv));
507 /* we add the region later */
508 setup_new_rec_layer_time (region);
510 error << _("failed to create MIDI region") << endmsg;
514 /* start a new rec box */
516 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
517 framepos_t const frame_pos = mt->current_capture_start ();
518 gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
519 gdouble const xend = xstart;
522 fill_color = ARDOUR_UI::config()->color ("recording rect");
524 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
525 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
526 rec_rect->set_outline_color (ARDOUR_UI::config()->color ("recording rect"));
527 rec_rect->set_fill_color (fill_color);
528 rec_rect->lower_to_bottom();
531 recbox.rectangle = rec_rect;
532 recbox.start = _trackview.session()->transport_frame();
535 rec_rects.push_back (recbox);
537 screen_update_connection.disconnect();
538 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
539 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
543 } else if (rec_active &&
544 (_trackview.session()->record_status() != Session::Recording ||
545 !_trackview.track()->record_enabled())) {
546 screen_update_connection.disconnect();
548 rec_updating = false;
553 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
555 if (!rec_rects.empty() || !rec_regions.empty()) {
557 /* disconnect rapid update */
558 screen_update_connection.disconnect();
559 rec_data_ready_connections.drop_connections ();
561 rec_updating = false;
564 /* remove temp regions */
566 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
567 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
572 (*iter).first->drop_references ();
579 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
581 /* transport stopped, clear boxes */
582 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
583 RecBoxInfo &rect = (*iter);
584 delete rect.rectangle;
594 MidiStreamView::color_handler ()
598 if (_trackview.is_midi_track()) {
599 canvas_rect->set_fill_color (ARDOUR_UI::config()->color_mod ("midi track base", "midi track base"));
601 canvas_rect->set_fill_color (ARDOUR_UI::config()->color ("midi bus base"));
606 MidiStreamView::note_range_adjustment_changed()
608 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
609 int lowest = (int) floor(note_range_adjustment.get_value());
612 if (sum == _range_sum_cache) {
613 //cerr << "cached" << endl;
614 highest = (int) floor(sum);
616 //cerr << "recalc" << endl;
617 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
618 _range_sum_cache = sum;
621 if (lowest == _lowest_note && highest == _highest_note) {
625 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
626 //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;
628 _lowest_note = lowest;
629 _highest_note = highest;
630 apply_note_range(lowest, highest, true);
634 MidiStreamView::update_rec_box ()
636 StreamView::update_rec_box ();
638 if (rec_regions.empty()) {
642 /* Update the region being recorded to reflect where we currently are */
643 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
644 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
646 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
647 mrv->extend_active_notes ();
651 MidiStreamView::y_to_note (double y) const
653 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
658 } else if (n > 127) {
665 /** Suspend updates to the regions' note ranges and our
666 * note lines until resume_updates() is called.
669 MidiStreamView::suspend_updates ()
671 _updates_suspended = true;
674 /** Resume updates to region note ranges and note lines,
675 * and update them now.
678 MidiStreamView::resume_updates ()
680 _updates_suspended = false;
683 apply_note_range_to_regions ();
685 _canvas_group->redraw ();
689 MidiStreamView::leave_internal_edit_mode ()
691 StreamView::leave_internal_edit_mode ();
692 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
693 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
695 mrv->clear_selection ();