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)
105 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
111 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
112 _samples_per_pixel, region_color);
114 region_view->init (false);
120 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
122 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
128 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
129 if ((*i)->region() == r) {
131 /* great. we already have a MidiRegionView for this Region. use it again. */
133 (*i)->set_valid (true);
135 display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
141 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
142 if (region_view == 0) {
146 region_views.push_front (region_view);
148 if (_trackview.editor().internal_editing()) {
149 region_view->hide_rect ();
151 region_view->show_rect ();
154 /* display events and find note range */
155 display_region (region_view, wait_for_data);
157 /* fit note range if we are importing */
158 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
159 set_note_range (ContentsRange);
162 /* catch regionview going away */
163 boost::weak_ptr<Region> wr (region); // make this explicit
164 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
166 RegionViewAdded (region_view);
172 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
178 region_view->enable_display (true);
179 region_view->set_height (child_height());
181 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
183 error << _("attempt to display MIDI region with no source") << endmsg;
188 source->load_model();
191 if (!source->model()) {
192 error << _("attempt to display MIDI region with no model") << endmsg;
196 _range_dirty = update_data_note_range(
197 source->model()->lowest_note(),
198 source->model()->highest_note());
200 // Display region contents
201 region_view->display_model(source->model());
206 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
208 StreamView::display_track (tr);
216 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
218 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
220 mr->midi_source(0)->load_model();
221 _range_dirty = update_data_note_range(
222 mr->model()->lowest_note(),
223 mr->model()->highest_note());
228 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
231 if (min < _data_note_min) {
232 _data_note_min = min;
235 if (max > _data_note_max) {
236 _data_note_max = max;
243 MidiStreamView::redisplay_track ()
245 if (!_trackview.is_midi_track()) {
249 list<RegionView*>::iterator i;
251 // Load models if necessary, and find note range of all our contents
252 _range_dirty = false;
253 _data_note_min = 127;
255 _trackview.track()->playlist()->foreach_region(
256 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
258 // No notes, use default range
264 // Flag region views as invalid and disable drawing
265 for (i = region_views.begin(); i != region_views.end(); ++i) {
266 (*i)->set_valid(false);
267 (*i)->enable_display(false);
270 // Add and display region views, and flag them as valid
271 _trackview.track()->playlist()->foreach_region(
272 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
274 // Stack regions by layer, and remove invalid regions
277 // Update note range (not regions which are correct) and draw note lines
278 apply_note_range(_lowest_note, _highest_note, false);
283 MidiStreamView::update_contents_height ()
285 StreamView::update_contents_height();
287 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
289 apply_note_range (lowest_note(), highest_note(), true);
293 MidiStreamView::draw_note_lines()
295 if (!_note_lines || _updates_suspended) {
303 _note_lines->clear();
305 if (child_height() < 140 || note_height() < 3) {
306 /* track is too small for note lines, or there are too many */
310 /* do this is order of highest ... lowest since that matches the
311 * coordinate system in which y=0 is at the top
314 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
316 y = floor(note_to_y (i)) + .5;
318 /* this is the line actually corresponding to the division
322 if (i <= highest_note()) {
323 _note_lines->add (y, 1.0, ARDOUR_UI::config()->get_PianoRollBlackOutline());
326 /* now add a thicker line/bar which covers the entire vertical
327 * height of this note.
336 color = ARDOUR_UI::config()->get_PianoRollBlack();
339 color = ARDOUR_UI::config()->get_PianoRollWhite();
343 double h = y - prev_y;
344 double mid = y + (h/2.0);
346 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
347 _note_lines->add (mid, h, color);
355 MidiStreamView::set_note_range(VisibleNoteRange r)
357 if (r == FullRange) {
361 _lowest_note = _data_note_min;
362 _highest_note = _data_note_max;
365 apply_note_range(_lowest_note, _highest_note, true);
369 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
371 _highest_note = highest;
372 _lowest_note = lowest;
374 int const max_note_height = 20; // This should probably be based on text size...
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 > max_note_height) {
381 int const available_note_range = floor (child_height() / max_note_height);
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){
393 else if (_lowest_note > 0){
402 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
403 note_range_adjustment.set_value(_lowest_note);
407 if (to_region_views) {
408 apply_note_range_to_regions ();
415 MidiStreamView::apply_note_range_to_regions ()
417 if (!_updates_suspended) {
418 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
419 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
425 MidiStreamView::update_note_range(uint8_t note_num)
427 _data_note_min = min(_data_note_min, note_num);
428 _data_note_max = max(_data_note_max, note_num);
432 MidiStreamView::setup_rec_box ()
434 // cerr << _trackview.name() << " streamview SRB\n";
436 if (_trackview.session()->transport_rolling()) {
439 _trackview.session()->record_status() == Session::Recording &&
440 _trackview.track()->record_enabled()) {
442 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
444 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
446 MidiRegion::SourceList sources;
448 rec_data_ready_connections.drop_connections ();
450 sources.push_back (_trackview.midi_track()->write_source());
454 framepos_t start = 0;
455 if (rec_regions.size() > 0) {
456 start = rec_regions.back().first->start()
457 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
460 if (!rec_regions.empty()) {
461 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
467 plist.add (ARDOUR::Properties::start, start);
468 plist.add (ARDOUR::Properties::length, 1);
469 /* Just above we're setting this nascent region's length to 1. I think this
470 is so that the RegionView gets created with a non-zero width, as apparently
471 creating a RegionView with a zero width causes it never to be displayed
472 (there is a warning in TimeAxisViewItem::init about this). However, we
473 must also set length_beats to something non-zero, otherwise the frame length
474 of 1 causes length_beats to be set to some small quantity << 1. Then
475 when the position is set up below, this length_beats is used to recompute
476 length using BeatsFramesConverter::to, which is slightly innacurate for small
477 beats values because it converts floating point beats to bars, beats and
478 integer ticks. The upshot of which being that length gets set back to 0,
479 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
481 plist.add (ARDOUR::Properties::length_beats, 1);
482 plist.add (ARDOUR::Properties::name, string());
483 plist.add (ARDOUR::Properties::layer, 0);
485 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
486 (RegionFactory::create (sources, plist, false)));
488 region->set_start (_trackview.track()->current_capture_start()
489 - _trackview.track()->get_capture_start_frame (0));
490 region->set_position (_trackview.track()->current_capture_start());
491 RegionView* rv = add_region_view_internal (region, false);
492 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
496 /* rec region will be destroyed in setup_rec_box */
497 rec_regions.push_back (make_pair (region, rv));
499 /* we add the region later */
500 setup_new_rec_layer_time (region);
502 error << _("failed to create MIDI region") << endmsg;
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().sample_to_pixel (frame_pos);
511 gdouble const xend = xstart;
514 fill_color = ARDOUR_UI::config()->get_RecordingRect();
516 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
517 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
518 rec_rect->set_outline_color (ARDOUR_UI::config()->get_RecordingRect());
519 rec_rect->set_fill_color (fill_color);
520 rec_rect->lower_to_bottom();
523 recbox.rectangle = rec_rect;
524 recbox.start = _trackview.session()->transport_frame();
527 rec_rects.push_back (recbox);
529 screen_update_connection.disconnect();
530 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
531 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
535 } else if (rec_active &&
536 (_trackview.session()->record_status() != Session::Recording ||
537 !_trackview.track()->record_enabled())) {
538 screen_update_connection.disconnect();
540 rec_updating = false;
545 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
547 if (!rec_rects.empty() || !rec_regions.empty()) {
549 /* disconnect rapid update */
550 screen_update_connection.disconnect();
551 rec_data_ready_connections.drop_connections ();
553 rec_updating = false;
556 /* remove temp regions */
558 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
559 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
564 (*iter).first->drop_references ();
571 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
573 /* transport stopped, clear boxes */
574 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
575 RecBoxInfo &rect = (*iter);
576 delete rect.rectangle;
586 MidiStreamView::color_handler ()
590 if (_trackview.is_midi_track()) {
591 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_MidiTrackBase());
593 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_MidiBusBase());
598 MidiStreamView::note_range_adjustment_changed()
600 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
601 int lowest = (int) floor(note_range_adjustment.get_value());
604 if (sum == _range_sum_cache) {
605 //cerr << "cached" << endl;
606 highest = (int) floor(sum);
608 //cerr << "recalc" << endl;
609 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
610 _range_sum_cache = sum;
613 if (lowest == _lowest_note && highest == _highest_note) {
617 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
618 //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;
620 _lowest_note = lowest;
621 _highest_note = highest;
622 apply_note_range(lowest, highest, true);
626 MidiStreamView::update_rec_box ()
628 StreamView::update_rec_box ();
630 if (rec_regions.empty()) {
634 /* Update the region being recorded to reflect where we currently are */
635 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
636 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
638 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
639 mrv->extend_active_notes ();
643 MidiStreamView::y_to_note (double y) const
645 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
650 } else if (n > 127) {
657 /** Suspend updates to the regions' note ranges and our
658 * note lines until resume_updates() is called.
661 MidiStreamView::suspend_updates ()
663 _updates_suspended = true;
666 /** Resume updates to region note ranges and note lines,
667 * and update them now.
670 MidiStreamView::resume_updates ()
672 _updates_suspended = false;
675 apply_note_range_to_regions ();
677 _canvas_group->redraw ();
681 MidiStreamView::leave_internal_edit_mode ()
683 StreamView::leave_internal_edit_mode ();
684 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
685 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
687 mrv->clear_selection ();