2 * Copyright (C) 2006-2014 David Robillard <d@drobilla.net>
3 * Copyright (C) 2007 Doug McLain <doug@nostar.net>
4 * Copyright (C) 2008-2017 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
6 * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
7 * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
8 * Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 #include <gtkmm2ext/gtk_ui.h>
32 #include "canvas/line_set.h"
33 #include "canvas/rectangle.h"
35 #include "ardour/midi_region.h"
36 #include "ardour/midi_source.h"
37 #include "ardour/midi_track.h"
38 #include "ardour/operations.h"
39 #include "ardour/region_factory.h"
40 #include "ardour/session.h"
41 #include "ardour/smf_source.h"
42 #include "ardour/evoral_types_convert.h"
44 #include "gui_thread.h"
45 #include "midi_region_view.h"
46 #include "midi_streamview.h"
47 #include "midi_time_axis.h"
48 #include "midi_util.h"
49 #include "public_editor.h"
50 #include "region_selection.h"
51 #include "region_view.h"
52 #include "rgb_macros.h"
53 #include "selection.h"
54 #include "ui_config.h"
60 using namespace ARDOUR;
61 using namespace ARDOUR_UI_UTILS;
63 using namespace Editing;
65 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
67 , note_range_adjustment(0.0f, 0.0f, 0.0f)
69 , _range_sum_cache(-1.0)
75 , _updates_suspended (false)
77 /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
78 midi_underlay_group = new ArdourCanvas::Container (_canvas_group);
79 midi_underlay_group->lower_to_bottom();
81 /* put the note lines in the timeaxisview's group, so it
82 can be put below ghost regions from MIDI underlays
84 _note_lines = new ArdourCanvas::LineSet (_canvas_group, ArdourCanvas::LineSet::Horizontal);
86 _note_lines->Event.connect(
87 sigc::bind(sigc::mem_fun(_trackview.editor(),
88 &PublicEditor::canvas_stream_view_event),
89 _note_lines, &_trackview));
91 _note_lines->lower_to_bottom();
95 UIConfiguration::instance().ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
97 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
98 note_range_adjustment.set_value(_lowest_note);
100 note_range_adjustment.signal_value_changed().connect(
101 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
104 MidiStreamView::~MidiStreamView ()
109 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool recording)
111 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
117 RegionView* region_view = NULL;
119 region_view = new MidiRegionView (
120 _canvas_group, _trackview, region,
121 _samples_per_pixel, region_color, recording,
122 TimeAxisViewItem::Visibility(TimeAxisViewItem::ShowFrame));
124 region_view = new MidiRegionView (_canvas_group, _trackview, region,
125 _samples_per_pixel, region_color);
128 region_view->init (false);
134 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
136 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
142 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
143 if ((*i)->region() == r) {
145 /* great. we already have a MidiRegionView for this Region. use it again. */
147 (*i)->set_valid (true);
149 display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
155 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
156 if (region_view == 0) {
160 region_views.push_front (region_view);
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 Glib::Threads::Mutex::Lock lm(source->mutex());
197 source->load_model(lm);
200 if (!source->model()) {
201 error << _("attempt to display MIDI region with no model") << endmsg;
205 _range_dirty = update_data_note_range(
206 source->model()->lowest_note(),
207 source->model()->highest_note());
209 // Display region contents
210 region_view->display_model(source->model());
215 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
217 StreamView::display_track (tr);
221 NoteRangeChanged(); /* EMIT SIGNAL*/
225 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
227 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
229 Glib::Threads::Mutex::Lock lm(mr->midi_source(0)->mutex());
230 mr->midi_source(0)->load_model(lm);
231 _range_dirty = update_data_note_range(
232 mr->model()->lowest_note(),
233 mr->model()->highest_note());
238 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
241 if (min < _data_note_min) {
242 _data_note_min = min;
245 if (max > _data_note_max) {
246 _data_note_max = max;
253 MidiStreamView::set_layer_display (LayerDisplay d)
256 //revert this change for now. Although stacked view is weirdly implemented wrt the "scroomer", it is still necessary to manage layered midi regions.
257 // if (d != Overlaid) {
261 StreamView::set_layer_display (d);
265 MidiStreamView::redisplay_track ()
267 if (!_trackview.is_midi_track()) {
271 list<RegionView*>::iterator i;
273 // Load models if necessary, and find note range of all our contents
274 _range_dirty = false;
275 _data_note_min = 127;
277 _trackview.track()->playlist()->foreach_region(
278 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
280 // No notes, use default range
286 // Flag region views as invalid and disable drawing
287 for (i = region_views.begin(); i != region_views.end(); ++i) {
288 (*i)->set_valid(false);
289 (*i)->enable_display(false);
292 // Add and display region views, and flag them as valid
293 _trackview.track()->playlist()->foreach_region(
294 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
296 // Stack regions by layer, and remove invalid regions
299 // Update note range (not regions which are correct) and draw note lines
300 apply_note_range(_lowest_note, _highest_note, false);
305 MidiStreamView::update_contents_height ()
307 StreamView::update_contents_height();
309 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
311 apply_note_range (lowest_note(), highest_note(), true);
315 MidiStreamView::draw_note_lines()
317 if (!_note_lines || _updates_suspended) {
325 _note_lines->clear();
327 if (child_height() < 140 || note_height() < 3) {
328 /* track is too small for note lines, or there are too many */
332 /* do this is order of highest ... lowest since that matches the
333 * coordinate system in which y=0 is at the top
336 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
338 y = floor(note_to_y (i)) + .5;
340 /* this is the line actually corresponding to the division
344 if (i <= highest_note()) {
345 _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
348 /* now add a thicker line/bar which covers the entire vertical
349 * height of this note.
358 color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
361 color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
365 double h = y - prev_y;
366 double mid = y + (h/2.0);
368 if (mid >= 0 && h > 1.0) {
369 _note_lines->add (mid, h, color);
377 MidiStreamView::set_note_range(VisibleNoteRange r)
379 if (r == FullRange) {
383 _lowest_note = _data_note_min;
384 _highest_note = _data_note_max;
387 apply_note_range(_lowest_note, _highest_note, true);
391 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
393 _highest_note = highest;
394 _lowest_note = lowest;
396 int const max_note_height = 20; // This should probably be based on text size...
397 int const range = _highest_note - _lowest_note;
398 int const pixels_per_note = floor (child_height () / range);
400 /* do not grow note height beyond 10 pixels */
401 if (pixels_per_note > max_note_height) {
403 int const available_note_range = floor (child_height() / max_note_height);
404 int additional_notes = available_note_range - range;
406 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
407 for (int i = 0; i < additional_notes; i++){
409 if (i % 2 && _highest_note < 127){
415 else if (_lowest_note > 0){
424 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
425 note_range_adjustment.set_value(_lowest_note);
429 if (to_region_views) {
430 apply_note_range_to_regions ();
433 NoteRangeChanged(); /* EMIT SIGNAL*/
437 MidiStreamView::apply_note_range_to_regions ()
439 if (!_updates_suspended) {
440 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
441 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
447 MidiStreamView::update_note_range(uint8_t note_num)
449 _data_note_min = min(_data_note_min, note_num);
450 _data_note_max = max(_data_note_max, note_num);
454 MidiStreamView::setup_rec_box ()
456 // cerr << _trackview.name() << " streamview SRB\n";
458 if (!_trackview.session()->transport_stopped()) {
461 _trackview.session()->record_status() == Session::Recording &&
462 _trackview.track()->rec_enable_control()->get_value()) {
464 if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
466 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
468 MidiRegion::SourceList sources;
470 rec_data_ready_connections.drop_connections ();
472 sources.push_back (_trackview.midi_track()->write_source());
476 samplepos_t start = 0;
477 if (rec_regions.size() > 0) {
478 start = rec_regions.back().first->start()
479 + _trackview.track()->get_captured_samples (rec_regions.size() - 1);
482 if (!rec_regions.empty()) {
483 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
489 plist.add (ARDOUR::Properties::start, start);
490 plist.add (ARDOUR::Properties::length, 1);
491 /* Just above we're setting this nascent region's length to 1. I think this
492 is so that the RegionView gets created with a non-zero width, as apparently
493 creating a RegionView with a zero width causes it never to be displayed
494 (there is a warning in TimeAxisViewItem::init about this). However, we
495 must also set length_beats to something non-zero, otherwise the sample length
496 of 1 causes length_beats to be set to some small quantity << 1. Then
497 when the position is set up below, this length_beats is used to recompute
498 length using BeatsSamplesConverter::to, which is slightly innacurate for small
499 beats values because it converts floating point beats to bars, beats and
500 integer ticks. The upshot of which being that length gets set back to 0,
501 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
503 plist.add (ARDOUR::Properties::length_beats, 1);
504 plist.add (ARDOUR::Properties::name, string());
505 plist.add (ARDOUR::Properties::layer, 0);
507 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
508 (RegionFactory::create (sources, plist, false)));
510 region->set_start (_trackview.track()->current_capture_start()
511 - _trackview.track()->get_capture_start_sample (0));
512 region->set_position (_trackview.session()->transport_sample());
514 RegionView* rv = add_region_view_internal (region, false, true);
515 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
518 /* rec region will be destroyed in setup_rec_box */
519 rec_regions.push_back (make_pair (region, rv));
521 /* we add the region later */
522 setup_new_rec_layer_time (region);
524 error << _("failed to create MIDI region") << endmsg;
528 /* start a new rec box */
530 create_rec_box (_trackview.midi_track()->current_capture_start(), 0);
532 } else if (rec_active &&
533 (_trackview.session()->record_status() != Session::Recording ||
534 !_trackview.track()->rec_enable_control()->get_value())) {
535 screen_update_connection.disconnect();
537 rec_updating = false;
542 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
544 if (!rec_rects.empty() || !rec_regions.empty()) {
546 /* disconnect rapid update */
547 screen_update_connection.disconnect();
548 rec_data_ready_connections.drop_connections ();
549 rec_updating = 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;
560 (*iter).first->drop_references ();
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;
582 MidiStreamView::color_handler ()
586 if (_trackview.is_midi_track()) {
587 canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
589 canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
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());
600 if (sum == _range_sum_cache) {
601 //cerr << "cached" << endl;
602 highest = (int) floor(sum);
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) {
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);
622 MidiStreamView::update_rec_box ()
624 StreamView::update_rec_box ();
626 if (rec_regions.empty()) {
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(), 0);
634 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
635 mrv->extend_active_notes ();
639 MidiStreamView::y_to_note (double y) const
641 int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
646 } else if (n > 127) {
650 /* min due to rounding and/or off-by-one errors */
651 return min ((uint8_t) n, highest_note());
654 /** Suspend updates to the regions' note ranges and our
655 * note lines until resume_updates() is called.
658 MidiStreamView::suspend_updates ()
660 _updates_suspended = true;
663 /** Resume updates to region note ranges and note lines,
664 * and update them now.
667 MidiStreamView::resume_updates ()
669 _updates_suspended = false;
672 apply_note_range_to_regions ();
674 _canvas_group->redraw ();
677 struct RegionPositionSorter {
678 bool operator() (RegionView* a, RegionView* b) {
679 return a->region()->position() < b->region()->position();
684 MidiStreamView::paste (ARDOUR::samplepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t sub_num)
686 /* Paste into the first region which starts on or before pos. Only called when
687 using an internal editing tool. */
689 if (region_views.empty()) {
693 region_views.sort (RegionView::PositionOrder());
695 list<RegionView*>::const_iterator prev = region_views.begin ();
697 for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
698 if ((*i)->region()->position() > pos) {
704 boost::shared_ptr<Region> r = (*prev)->region ();
706 /* If *prev doesn't cover pos, it's no good */
707 if (r->position() > pos || ((r->position() + r->length()) < pos)) {
711 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
712 return mrv ? mrv->paste(pos, selection, ctx, sub_num) : false;