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;
56 using namespace Editing;
58 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
60 , note_range_adjustment(0.0f, 0.0f, 0.0f)
62 , _range_sum_cache(-1.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::Layout (_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
77 _note_lines = new ArdourCanvas::LineSet (_canvas_group, ArdourCanvas::LineSet::Horizontal);
79 _note_lines->Event.connect(
80 sigc::bind(sigc::mem_fun(_trackview.editor(),
81 &PublicEditor::canvas_stream_view_event),
82 _note_lines, &_trackview));
84 _note_lines->lower_to_bottom();
88 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
90 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
91 note_range_adjustment.set_value(_lowest_note);
93 note_range_adjustment.signal_value_changed().connect(
94 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
97 MidiStreamView::~MidiStreamView ()
102 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
104 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
110 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
111 _samples_per_pixel, region_color);
113 region_view->init (false);
119 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
121 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
127 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
128 if ((*i)->region() == r) {
130 /* great. we already have a MidiRegionView for this Region. use it again. */
132 (*i)->set_valid (true);
134 display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
140 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
141 if (region_view == 0) {
145 region_views.push_front (region_view);
147 if (_trackview.editor().internal_editing()) {
148 region_view->hide_rect ();
150 region_view->show_rect ();
153 /* display events and find note range */
154 display_region (region_view, wait_for_data);
156 /* fit note range if we are importing */
157 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
158 set_note_range (ContentsRange);
161 /* catch regionview going away */
162 boost::weak_ptr<Region> wr (region); // make this explicit
163 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
165 RegionViewAdded (region_view);
171 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
177 region_view->enable_display (true);
179 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
182 source->load_model();
185 _range_dirty = update_data_note_range(
186 source->model()->lowest_note(),
187 source->model()->highest_note());
189 // Display region contents
190 region_view->set_height (child_height());
191 region_view->display_model(source->model());
196 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
198 StreamView::display_track (tr);
206 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
208 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
210 mr->midi_source(0)->load_model();
211 _range_dirty = update_data_note_range(
212 mr->model()->lowest_note(),
213 mr->model()->highest_note());
218 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
221 if (min < _data_note_min) {
222 _data_note_min = min;
225 if (max > _data_note_max) {
226 _data_note_max = max;
233 MidiStreamView::redisplay_track ()
235 if (!_trackview.is_midi_track()) {
239 list<RegionView*>::iterator i;
241 // Load models if necessary, and find note range of all our contents
242 _range_dirty = false;
243 _data_note_min = 127;
245 _trackview.track()->playlist()->foreach_region(
246 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
248 // No notes, use default range
254 // Flag region views as invalid and disable drawing
255 for (i = region_views.begin(); i != region_views.end(); ++i) {
256 (*i)->set_valid(false);
257 (*i)->enable_display(false);
260 // Add and display region views, and flag them as valid
261 _trackview.track()->playlist()->foreach_region(
262 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
264 // Stack regions by layer, and remove invalid regions
267 // Update note range (not regions which are correct) and draw note lines
268 apply_note_range(_lowest_note, _highest_note, false);
273 MidiStreamView::update_contents_height ()
275 StreamView::update_contents_height();
277 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
279 apply_note_range (lowest_note(), highest_note(), true);
283 MidiStreamView::draw_note_lines()
285 if (!_note_lines || _updates_suspended) {
293 _note_lines->clear();
295 if (child_height() < 140 || note_height() < 3) {
296 /* track is too small for note lines, or there are too many */
300 /* do this is order of highest ... lowest since that matches the
301 * coordinate system in which y=0 is at the top
304 for (int i = highest_note(); i >= lowest_note(); --i) {
308 /* this is the line actually corresponding to the division
312 _note_lines->add (y, 1.0, ARDOUR_UI::config()->get_canvasvar_PianoRollBlackOutline());
314 /* now add a thicker line/bar which covers the entire vertical
315 * height of this note.
324 color = ARDOUR_UI::config()->get_canvasvar_PianoRollBlack();
327 color = ARDOUR_UI::config()->get_canvasvar_PianoRollWhite();
331 double h = y - prev_y;
332 double mid = y + (h/2.0);
335 _note_lines->add (mid, h, color);
343 MidiStreamView::set_note_range(VisibleNoteRange r)
345 if (r == FullRange) {
349 _lowest_note = _data_note_min;
350 _highest_note = _data_note_max;
353 apply_note_range(_lowest_note, _highest_note, true);
357 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
359 _highest_note = highest;
360 _lowest_note = lowest;
362 int const max_note_height = 20; // This should probably be based on text size...
363 int const range = _highest_note - _lowest_note;
364 int const pixels_per_note = floor (child_height () / range);
366 /* do not grow note height beyond 10 pixels */
367 if (pixels_per_note > max_note_height) {
369 int const available_note_range = floor (child_height() / max_note_height);
370 int additional_notes = available_note_range - range;
372 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
373 for (int i = 0; i < additional_notes; i++){
375 if (i % 2 && _highest_note < 127){
381 else if (_lowest_note > 0){
390 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
391 note_range_adjustment.set_value(_lowest_note);
395 if (to_region_views) {
396 apply_note_range_to_regions ();
403 MidiStreamView::apply_note_range_to_regions ()
405 if (!_updates_suspended) {
406 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
407 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
413 MidiStreamView::update_note_range(uint8_t note_num)
415 _data_note_min = min(_data_note_min, note_num);
416 _data_note_max = max(_data_note_max, note_num);
420 MidiStreamView::setup_rec_box ()
422 // cerr << _trackview.name() << " streamview SRB\n";
424 if (_trackview.session()->transport_rolling()) {
427 _trackview.session()->record_status() == Session::Recording &&
428 _trackview.track()->record_enabled()) {
430 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
432 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
434 MidiRegion::SourceList sources;
436 rec_data_ready_connections.drop_connections ();
438 sources.push_back (_trackview.midi_track()->write_source());
442 framepos_t start = 0;
443 if (rec_regions.size() > 0) {
444 start = rec_regions.back().first->start()
445 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
448 if (!rec_regions.empty()) {
449 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
455 plist.add (ARDOUR::Properties::start, start);
456 plist.add (ARDOUR::Properties::length, 1);
457 /* Just above we're setting this nascent region's length to 1. I think this
458 is so that the RegionView gets created with a non-zero width, as apparently
459 creating a RegionView with a zero width causes it never to be displayed
460 (there is a warning in TimeAxisViewItem::init about this). However, we
461 must also set length_beats to something non-zero, otherwise the frame length
462 of 1 causes length_beats to be set to some small quantity << 1. Then
463 when the position is set up below, this length_beats is used to recompute
464 length using BeatsFramesConverter::to, which is slightly innacurate for small
465 beats values because it converts floating point beats to bars, beats and
466 integer ticks. The upshot of which being that length gets set back to 0,
467 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
469 plist.add (ARDOUR::Properties::length_beats, 1);
470 plist.add (ARDOUR::Properties::name, string());
471 plist.add (ARDOUR::Properties::layer, 0);
473 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
474 (RegionFactory::create (sources, plist, false)));
476 region->set_start (_trackview.track()->current_capture_start()
477 - _trackview.track()->get_capture_start_frame (0));
478 region->set_position (_trackview.track()->current_capture_start());
479 RegionView* rv = add_region_view_internal (region, false);
480 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
484 /* rec region will be destroyed in setup_rec_box */
485 rec_regions.push_back (make_pair (region, rv));
487 /* we add the region later */
488 setup_new_rec_layer_time (region);
490 error << _("failed to create MIDI region") << endmsg;
494 /* start a new rec box */
496 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
497 framepos_t const frame_pos = mt->current_capture_start ();
498 gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
499 gdouble const xend = xstart;
502 fill_color = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
504 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
505 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
506 rec_rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
507 rec_rect->set_fill_color (fill_color);
508 rec_rect->lower_to_bottom();
511 recbox.rectangle = rec_rect;
512 recbox.start = _trackview.session()->transport_frame();
515 rec_rects.push_back (recbox);
517 screen_update_connection.disconnect();
518 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
519 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
523 } else if (rec_active &&
524 (_trackview.session()->record_status() != Session::Recording ||
525 !_trackview.track()->record_enabled())) {
526 screen_update_connection.disconnect();
528 rec_updating = false;
533 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
535 if (!rec_rects.empty() || !rec_regions.empty()) {
537 /* disconnect rapid update */
538 screen_update_connection.disconnect();
539 rec_data_ready_connections.drop_connections ();
541 rec_updating = false;
544 /* remove temp regions */
546 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
547 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
552 (*iter).first->drop_references ();
559 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
561 /* transport stopped, clear boxes */
562 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
563 RecBoxInfo &rect = (*iter);
564 delete rect.rectangle;
574 MidiStreamView::color_handler ()
578 if (_trackview.is_midi_track()) {
579 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiTrackBase());
581 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiBusBase());
586 MidiStreamView::note_range_adjustment_changed()
588 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
589 int lowest = (int) floor(note_range_adjustment.get_value());
592 if (sum == _range_sum_cache) {
593 //cerr << "cached" << endl;
594 highest = (int) floor(sum);
596 //cerr << "recalc" << endl;
597 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
598 _range_sum_cache = sum;
601 if (lowest == _lowest_note && highest == _highest_note) {
605 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
606 //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;
608 _lowest_note = lowest;
609 _highest_note = highest;
610 apply_note_range(lowest, highest, true);
614 MidiStreamView::update_rec_box ()
616 StreamView::update_rec_box ();
618 if (rec_regions.empty()) {
622 /* Update the region being recorded to reflect where we currently are */
623 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
624 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
626 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
627 mrv->extend_active_notes ();
631 MidiStreamView::y_to_note (double y) const
633 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
638 } else if (n > 127) {
645 /** Suspend updates to the regions' note ranges and our
646 * note lines until resume_updates() is called.
649 MidiStreamView::suspend_updates ()
651 _updates_suspended = true;
654 /** Resume updates to region note ranges and note lines,
655 * and update them now.
658 MidiStreamView::resume_updates ()
660 _updates_suspended = false;
663 apply_note_range_to_regions ();
665 _canvas_group->redraw ();
669 MidiStreamView::leave_internal_edit_mode ()
671 StreamView::leave_internal_edit_mode ();
672 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
673 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
675 mrv->clear_selection ();