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 "gui_thread.h"
38 #include "midi_region_view.h"
39 #include "midi_streamview.h"
40 #include "midi_time_axis.h"
41 #include "midi_util.h"
42 #include "public_editor.h"
43 #include "region_selection.h"
44 #include "region_view.h"
45 #include "rgb_macros.h"
46 #include "selection.h"
47 #include "ui_config.h"
53 using namespace ARDOUR;
54 using namespace ARDOUR_UI_UTILS;
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::Container (_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 UIConfiguration::instance().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 recording)
104 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
110 RegionView* region_view = NULL;
112 region_view = new MidiRegionView (
113 _canvas_group, _trackview, region,
114 _samples_per_pixel, region_color, recording,
115 TimeAxisViewItem::Visibility(TimeAxisViewItem::ShowFrame));
117 region_view = new MidiRegionView (_canvas_group, _trackview, region,
118 _samples_per_pixel, region_color);
121 region_view->init (false);
127 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
129 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
135 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
136 if ((*i)->region() == r) {
138 /* great. we already have a MidiRegionView for this Region. use it again. */
140 (*i)->set_valid (true);
142 display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
148 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
149 if (region_view == 0) {
153 region_views.push_front (region_view);
155 /* display events and find note range */
156 display_region (region_view, wait_for_data);
158 /* fit note range if we are importing */
159 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
160 set_note_range (ContentsRange);
163 /* catch regionview going away */
164 boost::weak_ptr<Region> wr (region); // make this explicit
165 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
167 RegionViewAdded (region_view);
173 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
179 region_view->enable_display (true);
180 region_view->set_height (child_height());
182 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
184 error << _("attempt to display MIDI region with no source") << endmsg;
189 Glib::Threads::Mutex::Lock lm(source->mutex());
190 source->load_model(lm);
193 if (!source->model()) {
194 error << _("attempt to display MIDI region with no model") << endmsg;
198 _range_dirty = update_data_note_range(
199 source->model()->lowest_note(),
200 source->model()->highest_note());
202 // Display region contents
203 region_view->display_model(source->model());
208 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
210 StreamView::display_track (tr);
218 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
220 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
222 Glib::Threads::Mutex::Lock lm(mr->midi_source(0)->mutex());
223 mr->midi_source(0)->load_model(lm);
224 _range_dirty = update_data_note_range(
225 mr->model()->lowest_note(),
226 mr->model()->highest_note());
231 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
234 if (min < _data_note_min) {
235 _data_note_min = min;
238 if (max > _data_note_max) {
239 _data_note_max = max;
246 MidiStreamView::set_layer_display (LayerDisplay d)
249 //revert this change for now. Although stacked view is weirdly implemented wrt the "scroomer", it is still necessary to manage layered midi regions.
250 // if (d != Overlaid) {
254 StreamView::set_layer_display (d);
258 MidiStreamView::redisplay_track ()
260 if (!_trackview.is_midi_track()) {
264 list<RegionView*>::iterator i;
266 // Load models if necessary, and find note range of all our contents
267 _range_dirty = false;
268 _data_note_min = 127;
270 _trackview.track()->playlist()->foreach_region(
271 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
273 // No notes, use default range
279 // Flag region views as invalid and disable drawing
280 for (i = region_views.begin(); i != region_views.end(); ++i) {
281 (*i)->set_valid(false);
282 (*i)->enable_display(false);
285 // Add and display region views, and flag them as valid
286 _trackview.track()->playlist()->foreach_region(
287 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
289 // Stack regions by layer, and remove invalid regions
292 // Update note range (not regions which are correct) and draw note lines
293 apply_note_range(_lowest_note, _highest_note, false);
298 MidiStreamView::update_contents_height ()
300 StreamView::update_contents_height();
302 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
304 apply_note_range (lowest_note(), highest_note(), true);
308 MidiStreamView::draw_note_lines()
310 if (!_note_lines || _updates_suspended) {
318 _note_lines->clear();
320 if (child_height() < 140 || note_height() < 3) {
321 /* track is too small for note lines, or there are too many */
325 /* do this is order of highest ... lowest since that matches the
326 * coordinate system in which y=0 is at the top
329 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
331 y = floor(note_to_y (i)) + .5;
333 /* this is the line actually corresponding to the division
337 if (i <= highest_note()) {
338 _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
341 /* now add a thicker line/bar which covers the entire vertical
342 * height of this note.
351 color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
354 color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
358 double h = y - prev_y;
359 double mid = y + (h/2.0);
361 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
362 _note_lines->add (mid, h, color);
370 MidiStreamView::set_note_range(VisibleNoteRange r)
372 if (r == FullRange) {
376 _lowest_note = _data_note_min;
377 _highest_note = _data_note_max;
380 apply_note_range(_lowest_note, _highest_note, true);
384 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
386 _highest_note = highest;
387 _lowest_note = lowest;
389 int const max_note_height = 20; // This should probably be based on text size...
390 int const range = _highest_note - _lowest_note;
391 int const pixels_per_note = floor (child_height () / range);
393 /* do not grow note height beyond 10 pixels */
394 if (pixels_per_note > max_note_height) {
396 int const available_note_range = floor (child_height() / max_note_height);
397 int additional_notes = available_note_range - range;
399 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
400 for (int i = 0; i < additional_notes; i++){
402 if (i % 2 && _highest_note < 127){
408 else if (_lowest_note > 0){
417 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
418 note_range_adjustment.set_value(_lowest_note);
422 if (to_region_views) {
423 apply_note_range_to_regions ();
430 MidiStreamView::apply_note_range_to_regions ()
432 if (!_updates_suspended) {
433 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
434 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
440 MidiStreamView::update_note_range(uint8_t note_num)
442 _data_note_min = min(_data_note_min, note_num);
443 _data_note_max = max(_data_note_max, note_num);
447 MidiStreamView::setup_rec_box ()
449 // cerr << _trackview.name() << " streamview SRB\n";
451 if (_trackview.session()->transport_rolling()) {
454 _trackview.session()->record_status() == Session::Recording &&
455 _trackview.track()->rec_enable_control()->get_value()) {
457 if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
459 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
461 MidiRegion::SourceList sources;
463 rec_data_ready_connections.drop_connections ();
465 sources.push_back (_trackview.midi_track()->write_source());
469 framepos_t start = 0;
470 if (rec_regions.size() > 0) {
471 start = rec_regions.back().first->start()
472 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
475 if (!rec_regions.empty()) {
476 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
482 plist.add (ARDOUR::Properties::start, start);
483 plist.add (ARDOUR::Properties::length, 1);
484 /* Just above we're setting this nascent region's length to 1. I think this
485 is so that the RegionView gets created with a non-zero width, as apparently
486 creating a RegionView with a zero width causes it never to be displayed
487 (there is a warning in TimeAxisViewItem::init about this). However, we
488 must also set length_beats to something non-zero, otherwise the frame length
489 of 1 causes length_beats to be set to some small quantity << 1. Then
490 when the position is set up below, this length_beats is used to recompute
491 length using BeatsFramesConverter::to, which is slightly innacurate for small
492 beats values because it converts floating point beats to bars, beats and
493 integer ticks. The upshot of which being that length gets set back to 0,
494 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
496 plist.add (ARDOUR::Properties::length_beats, 1);
497 plist.add (ARDOUR::Properties::name, string());
498 plist.add (ARDOUR::Properties::layer, 0);
500 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
501 (RegionFactory::create (sources, plist, false)));
503 region->set_start (_trackview.track()->current_capture_start()
504 - _trackview.track()->get_capture_start_frame (0));
505 region->set_position (_trackview.session()->transport_frame());
507 RegionView* rv = add_region_view_internal (region, false, true);
508 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
511 /* rec region will be destroyed in setup_rec_box */
512 rec_regions.push_back (make_pair (region, rv));
514 /* we add the region later */
515 setup_new_rec_layer_time (region);
517 error << _("failed to create MIDI region") << endmsg;
521 /* start a new rec box */
523 create_rec_box(_trackview.midi_track()->current_capture_start(), 0);
525 } else if (rec_active &&
526 (_trackview.session()->record_status() != Session::Recording ||
527 !_trackview.track()->rec_enable_control()->get_value())) {
528 screen_update_connection.disconnect();
530 rec_updating = false;
535 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
537 if (!rec_rects.empty() || !rec_regions.empty()) {
539 /* disconnect rapid update */
540 screen_update_connection.disconnect();
541 rec_data_ready_connections.drop_connections ();
542 rec_updating = false;
545 /* remove temp regions */
547 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
548 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
553 (*iter).first->drop_references ();
560 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
562 /* transport stopped, clear boxes */
563 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
564 RecBoxInfo &rect = (*iter);
565 delete rect.rectangle;
575 MidiStreamView::color_handler ()
579 if (_trackview.is_midi_track()) {
580 canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
582 canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
587 MidiStreamView::note_range_adjustment_changed()
589 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
590 int lowest = (int) floor(note_range_adjustment.get_value());
593 if (sum == _range_sum_cache) {
594 //cerr << "cached" << endl;
595 highest = (int) floor(sum);
597 //cerr << "recalc" << endl;
598 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
599 _range_sum_cache = sum;
602 if (lowest == _lowest_note && highest == _highest_note) {
606 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
607 //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;
609 _lowest_note = lowest;
610 _highest_note = highest;
611 apply_note_range(lowest, highest, true);
615 MidiStreamView::update_rec_box ()
617 StreamView::update_rec_box ();
619 if (rec_regions.empty()) {
623 /* Update the region being recorded to reflect where we currently are */
624 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
625 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), 0);
627 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
628 mrv->extend_active_notes ();
632 MidiStreamView::y_to_note (double y) const
634 int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
639 } else if (n > 127) {
643 /* min due to rounding and/or off-by-one errors */
644 return min ((uint8_t) n, highest_note());
647 /** Suspend updates to the regions' note ranges and our
648 * note lines until resume_updates() is called.
651 MidiStreamView::suspend_updates ()
653 _updates_suspended = true;
656 /** Resume updates to region note ranges and note lines,
657 * and update them now.
660 MidiStreamView::resume_updates ()
662 _updates_suspended = false;
665 apply_note_range_to_regions ();
667 _canvas_group->redraw ();
670 struct RegionPositionSorter {
671 bool operator() (RegionView* a, RegionView* b) {
672 return a->region()->position() < b->region()->position();
677 MidiStreamView::paste (ARDOUR::framepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t sub_num)
679 /* Paste into the first region which starts on or before pos. Only called when
680 using an internal editing tool. */
682 if (region_views.empty()) {
686 region_views.sort (RegionView::PositionOrder());
688 list<RegionView*>::const_iterator prev = region_views.begin ();
690 for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
691 if ((*i)->region()->position() > pos) {
697 boost::shared_ptr<Region> r = (*prev)->region ();
699 /* If *prev doesn't cover pos, it's no good */
700 if (r->position() > pos || ((r->position() + r->length()) < pos)) {
704 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
705 return mrv ? mrv->paste(pos, selection, ctx, sub_num) : false;