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"
36 #include "ardour/evoral_types_convert.h"
38 #include "gui_thread.h"
39 #include "midi_region_view.h"
40 #include "midi_streamview.h"
41 #include "midi_time_axis.h"
42 #include "midi_util.h"
43 #include "public_editor.h"
44 #include "region_selection.h"
45 #include "region_view.h"
46 #include "rgb_macros.h"
47 #include "selection.h"
48 #include "ui_config.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 UIConfiguration::instance().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 /* display events and find note range */
157 display_region (region_view, wait_for_data);
159 /* fit note range if we are importing */
160 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
161 set_note_range (ContentsRange);
164 /* catch regionview going away */
165 boost::weak_ptr<Region> wr (region); // make this explicit
166 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
168 RegionViewAdded (region_view);
174 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
180 region_view->enable_display (true);
181 region_view->set_height (child_height());
183 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
185 error << _("attempt to display MIDI region with no source") << endmsg;
190 Glib::Threads::Mutex::Lock lm(source->mutex());
191 source->load_model(lm);
194 if (!source->model()) {
195 error << _("attempt to display MIDI region with no model") << endmsg;
199 _range_dirty = update_data_note_range(
200 source->model()->lowest_note(),
201 source->model()->highest_note());
203 // Display region contents
204 region_view->display_model(source->model());
209 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
211 StreamView::display_track (tr);
219 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
221 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
223 Glib::Threads::Mutex::Lock lm(mr->midi_source(0)->mutex());
224 mr->midi_source(0)->load_model(lm);
225 _range_dirty = update_data_note_range(
226 mr->model()->lowest_note(),
227 mr->model()->highest_note());
232 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
235 if (min < _data_note_min) {
236 _data_note_min = min;
239 if (max > _data_note_max) {
240 _data_note_max = max;
247 MidiStreamView::set_layer_display (LayerDisplay d)
250 //revert this change for now. Although stacked view is weirdly implemented wrt the "scroomer", it is still necessary to manage layered midi regions.
251 // if (d != Overlaid) {
255 StreamView::set_layer_display (d);
259 MidiStreamView::redisplay_track ()
261 if (!_trackview.is_midi_track()) {
265 list<RegionView*>::iterator i;
267 // Load models if necessary, and find note range of all our contents
268 _range_dirty = false;
269 _data_note_min = 127;
271 _trackview.track()->playlist()->foreach_region(
272 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
274 // No notes, use default range
280 // Flag region views as invalid and disable drawing
281 for (i = region_views.begin(); i != region_views.end(); ++i) {
282 (*i)->set_valid(false);
283 (*i)->enable_display(false);
286 // Add and display region views, and flag them as valid
287 _trackview.track()->playlist()->foreach_region(
288 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
290 // Stack regions by layer, and remove invalid regions
293 // Update note range (not regions which are correct) and draw note lines
294 apply_note_range(_lowest_note, _highest_note, false);
299 MidiStreamView::update_contents_height ()
301 StreamView::update_contents_height();
303 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
305 apply_note_range (lowest_note(), highest_note(), true);
309 MidiStreamView::draw_note_lines()
311 if (!_note_lines || _updates_suspended) {
319 _note_lines->clear();
321 if (child_height() < 140 || note_height() < 3) {
322 /* track is too small for note lines, or there are too many */
326 /* do this is order of highest ... lowest since that matches the
327 * coordinate system in which y=0 is at the top
330 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
332 y = floor(note_to_y (i)) + .5;
334 /* this is the line actually corresponding to the division
338 if (i <= highest_note()) {
339 _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
342 /* now add a thicker line/bar which covers the entire vertical
343 * height of this note.
352 color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
355 color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
359 double h = y - prev_y;
360 double mid = y + (h/2.0);
362 if (mid >= 0 && h > 1.0) {
363 _note_lines->add (mid, h, color);
371 MidiStreamView::set_note_range(VisibleNoteRange r)
373 if (r == FullRange) {
377 _lowest_note = _data_note_min;
378 _highest_note = _data_note_max;
381 apply_note_range(_lowest_note, _highest_note, true);
385 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
387 _highest_note = highest;
388 _lowest_note = lowest;
390 int const max_note_height = 20; // This should probably be based on text size...
391 int const range = _highest_note - _lowest_note;
392 int const pixels_per_note = floor (child_height () / range);
394 /* do not grow note height beyond 10 pixels */
395 if (pixels_per_note > max_note_height) {
397 int const available_note_range = floor (child_height() / max_note_height);
398 int additional_notes = available_note_range - range;
400 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
401 for (int i = 0; i < additional_notes; i++){
403 if (i % 2 && _highest_note < 127){
409 else if (_lowest_note > 0){
418 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
419 note_range_adjustment.set_value(_lowest_note);
423 if (to_region_views) {
424 apply_note_range_to_regions ();
431 MidiStreamView::apply_note_range_to_regions ()
433 if (!_updates_suspended) {
434 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
435 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
441 MidiStreamView::update_note_range(uint8_t note_num)
443 _data_note_min = min(_data_note_min, note_num);
444 _data_note_max = max(_data_note_max, note_num);
448 MidiStreamView::setup_rec_box ()
450 // cerr << _trackview.name() << " streamview SRB\n";
452 if (!_trackview.session()->transport_stopped()) {
455 _trackview.session()->record_status() == Session::Recording &&
456 _trackview.track()->rec_enable_control()->get_value()) {
458 if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
460 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
462 MidiRegion::SourceList sources;
464 rec_data_ready_connections.drop_connections ();
466 sources.push_back (_trackview.midi_track()->write_source());
470 framepos_t start = 0;
471 if (rec_regions.size() > 0) {
472 start = rec_regions.back().first->start()
473 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
476 if (!rec_regions.empty()) {
477 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
483 plist.add (ARDOUR::Properties::start, start);
484 plist.add (ARDOUR::Properties::length, 1);
485 /* Just above we're setting this nascent region's length to 1. I think this
486 is so that the RegionView gets created with a non-zero width, as apparently
487 creating a RegionView with a zero width causes it never to be displayed
488 (there is a warning in TimeAxisViewItem::init about this). However, we
489 must also set length_beats to something non-zero, otherwise the frame length
490 of 1 causes length_beats to be set to some small quantity << 1. Then
491 when the position is set up below, this length_beats is used to recompute
492 length using BeatsFramesConverter::to, which is slightly innacurate for small
493 beats values because it converts floating point beats to bars, beats and
494 integer ticks. The upshot of which being that length gets set back to 0,
495 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
497 plist.add (ARDOUR::Properties::length_beats, 1);
498 plist.add (ARDOUR::Properties::name, string());
499 plist.add (ARDOUR::Properties::layer, 0);
501 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
502 (RegionFactory::create (sources, plist, false)));
504 region->set_start (_trackview.track()->current_capture_start()
505 - _trackview.track()->get_capture_start_frame (0));
506 region->set_position (_trackview.session()->transport_frame());
508 RegionView* rv = add_region_view_internal (region, false, true);
509 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
512 /* rec region will be destroyed in setup_rec_box */
513 rec_regions.push_back (make_pair (region, rv));
515 /* we add the region later */
516 setup_new_rec_layer_time (region);
518 error << _("failed to create MIDI region") << endmsg;
522 /* start a new rec box */
524 create_rec_box(_trackview.midi_track()->current_capture_start(), 0);
526 } else if (rec_active &&
527 (_trackview.session()->record_status() != Session::Recording ||
528 !_trackview.track()->rec_enable_control()->get_value())) {
529 screen_update_connection.disconnect();
531 rec_updating = false;
536 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
538 if (!rec_rects.empty() || !rec_regions.empty()) {
540 /* disconnect rapid update */
541 screen_update_connection.disconnect();
542 rec_data_ready_connections.drop_connections ();
543 rec_updating = false;
546 /* remove temp regions */
548 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
549 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
554 (*iter).first->drop_references ();
561 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
563 /* transport stopped, clear boxes */
564 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
565 RecBoxInfo &rect = (*iter);
566 delete rect.rectangle;
576 MidiStreamView::color_handler ()
580 if (_trackview.is_midi_track()) {
581 canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
583 canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
588 MidiStreamView::note_range_adjustment_changed()
590 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
591 int lowest = (int) floor(note_range_adjustment.get_value());
594 if (sum == _range_sum_cache) {
595 //cerr << "cached" << endl;
596 highest = (int) floor(sum);
598 //cerr << "recalc" << endl;
599 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
600 _range_sum_cache = sum;
603 if (lowest == _lowest_note && highest == _highest_note) {
607 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
608 //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;
610 _lowest_note = lowest;
611 _highest_note = highest;
612 apply_note_range(lowest, highest, true);
616 MidiStreamView::update_rec_box ()
618 StreamView::update_rec_box ();
620 if (rec_regions.empty()) {
624 /* Update the region being recorded to reflect where we currently are */
625 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
626 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), 0);
628 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
629 mrv->extend_active_notes ();
633 MidiStreamView::y_to_note (double y) const
635 int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
640 } else if (n > 127) {
644 /* min due to rounding and/or off-by-one errors */
645 return min ((uint8_t) n, highest_note());
648 /** Suspend updates to the regions' note ranges and our
649 * note lines until resume_updates() is called.
652 MidiStreamView::suspend_updates ()
654 _updates_suspended = true;
657 /** Resume updates to region note ranges and note lines,
658 * and update them now.
661 MidiStreamView::resume_updates ()
663 _updates_suspended = false;
666 apply_note_range_to_regions ();
668 _canvas_group->redraw ();
671 struct RegionPositionSorter {
672 bool operator() (RegionView* a, RegionView* b) {
673 return a->region()->position() < b->region()->position();
678 MidiStreamView::paste (ARDOUR::framepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t sub_num)
680 /* Paste into the first region which starts on or before pos. Only called when
681 using an internal editing tool. */
683 if (region_views.empty()) {
687 region_views.sort (RegionView::PositionOrder());
689 list<RegionView*>::const_iterator prev = region_views.begin ();
691 for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
692 if ((*i)->region()->position() > pos) {
698 boost::shared_ptr<Region> r = (*prev)->region ();
700 /* If *prev doesn't cover pos, it's no good */
701 if (r->position() > pos || ((r->position() + r->length()) < pos)) {
705 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
706 return mrv ? mrv->paste(pos, selection, ctx, sub_num) : false;