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)
252 StreamView::set_layer_display (d);
256 MidiStreamView::redisplay_track ()
258 if (!_trackview.is_midi_track()) {
262 list<RegionView*>::iterator i;
264 // Load models if necessary, and find note range of all our contents
265 _range_dirty = false;
266 _data_note_min = 127;
268 _trackview.track()->playlist()->foreach_region(
269 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
271 // No notes, use default range
277 // Flag region views as invalid and disable drawing
278 for (i = region_views.begin(); i != region_views.end(); ++i) {
279 (*i)->set_valid(false);
280 (*i)->enable_display(false);
283 // Add and display region views, and flag them as valid
284 _trackview.track()->playlist()->foreach_region(
285 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
287 // Stack regions by layer, and remove invalid regions
290 // Update note range (not regions which are correct) and draw note lines
291 apply_note_range(_lowest_note, _highest_note, false);
296 MidiStreamView::update_contents_height ()
298 StreamView::update_contents_height();
300 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
302 apply_note_range (lowest_note(), highest_note(), true);
306 MidiStreamView::draw_note_lines()
308 if (!_note_lines || _updates_suspended) {
316 _note_lines->clear();
318 if (child_height() < 140 || note_height() < 3) {
319 /* track is too small for note lines, or there are too many */
323 /* do this is order of highest ... lowest since that matches the
324 * coordinate system in which y=0 is at the top
327 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
329 y = floor(note_to_y (i)) + .5;
331 /* this is the line actually corresponding to the division
335 if (i <= highest_note()) {
336 _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
339 /* now add a thicker line/bar which covers the entire vertical
340 * height of this note.
349 color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
352 color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
356 double h = y - prev_y;
357 double mid = y + (h/2.0);
359 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
360 _note_lines->add (mid, h, color);
368 MidiStreamView::set_note_range(VisibleNoteRange r)
370 if (r == FullRange) {
374 _lowest_note = _data_note_min;
375 _highest_note = _data_note_max;
378 apply_note_range(_lowest_note, _highest_note, true);
382 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
384 _highest_note = highest;
385 _lowest_note = lowest;
387 int const max_note_height = 20; // This should probably be based on text size...
388 int const range = _highest_note - _lowest_note;
389 int const pixels_per_note = floor (child_height () / range);
391 /* do not grow note height beyond 10 pixels */
392 if (pixels_per_note > max_note_height) {
394 int const available_note_range = floor (child_height() / max_note_height);
395 int additional_notes = available_note_range - range;
397 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
398 for (int i = 0; i < additional_notes; i++){
400 if (i % 2 && _highest_note < 127){
406 else if (_lowest_note > 0){
415 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
416 note_range_adjustment.set_value(_lowest_note);
420 if (to_region_views) {
421 apply_note_range_to_regions ();
428 MidiStreamView::apply_note_range_to_regions ()
430 if (!_updates_suspended) {
431 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
432 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
438 MidiStreamView::update_note_range(uint8_t note_num)
440 _data_note_min = min(_data_note_min, note_num);
441 _data_note_max = max(_data_note_max, note_num);
445 MidiStreamView::setup_rec_box ()
447 // cerr << _trackview.name() << " streamview SRB\n";
449 if (_trackview.session()->transport_rolling()) {
452 _trackview.session()->record_status() == Session::Recording &&
453 _trackview.track()->rec_enable_control()->get_value()) {
455 if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
457 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
459 MidiRegion::SourceList sources;
461 rec_data_ready_connections.drop_connections ();
463 sources.push_back (_trackview.midi_track()->write_source());
467 framepos_t start = 0;
468 if (rec_regions.size() > 0) {
469 start = rec_regions.back().first->start()
470 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
473 if (!rec_regions.empty()) {
474 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
480 plist.add (ARDOUR::Properties::start, start);
481 plist.add (ARDOUR::Properties::length, 1);
482 /* Just above we're setting this nascent region's length to 1. I think this
483 is so that the RegionView gets created with a non-zero width, as apparently
484 creating a RegionView with a zero width causes it never to be displayed
485 (there is a warning in TimeAxisViewItem::init about this). However, we
486 must also set length_beats to something non-zero, otherwise the frame length
487 of 1 causes length_beats to be set to some small quantity << 1. Then
488 when the position is set up below, this length_beats is used to recompute
489 length using BeatsFramesConverter::to, which is slightly innacurate for small
490 beats values because it converts floating point beats to bars, beats and
491 integer ticks. The upshot of which being that length gets set back to 0,
492 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
494 plist.add (ARDOUR::Properties::length_beats, 1);
495 plist.add (ARDOUR::Properties::name, string());
496 plist.add (ARDOUR::Properties::layer, 0);
498 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
499 (RegionFactory::create (sources, plist, false)));
501 region->set_start (_trackview.track()->current_capture_start()
502 - _trackview.track()->get_capture_start_frame (0));
503 region->set_position (_trackview.session()->transport_frame());
505 RegionView* rv = add_region_view_internal (region, false, true);
506 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
509 /* rec region will be destroyed in setup_rec_box */
510 rec_regions.push_back (make_pair (region, rv));
512 /* we add the region later */
513 setup_new_rec_layer_time (region);
515 error << _("failed to create MIDI region") << endmsg;
519 /* start a new rec box */
521 create_rec_box(_trackview.midi_track()->current_capture_start(), 0);
523 } else if (rec_active &&
524 (_trackview.session()->record_status() != Session::Recording ||
525 !_trackview.track()->rec_enable_control()->get_value())) {
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 ();
540 rec_updating = false;
543 /* remove temp regions */
545 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
546 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
551 (*iter).first->drop_references ();
558 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
560 /* transport stopped, clear boxes */
561 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
562 RecBoxInfo &rect = (*iter);
563 delete rect.rectangle;
573 MidiStreamView::color_handler ()
577 if (_trackview.is_midi_track()) {
578 canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
580 canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
585 MidiStreamView::note_range_adjustment_changed()
587 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
588 int lowest = (int) floor(note_range_adjustment.get_value());
591 if (sum == _range_sum_cache) {
592 //cerr << "cached" << endl;
593 highest = (int) floor(sum);
595 //cerr << "recalc" << endl;
596 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
597 _range_sum_cache = sum;
600 if (lowest == _lowest_note && highest == _highest_note) {
604 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
605 //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;
607 _lowest_note = lowest;
608 _highest_note = highest;
609 apply_note_range(lowest, highest, true);
613 MidiStreamView::update_rec_box ()
615 StreamView::update_rec_box ();
617 if (rec_regions.empty()) {
621 /* Update the region being recorded to reflect where we currently are */
622 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
623 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start(), 0);
625 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
626 mrv->extend_active_notes ();
630 MidiStreamView::y_to_note (double y) const
632 int const n = ((contents_height() - y) / contents_height() * (double)contents_note_range())
637 } else if (n > 127) {
641 /* min due to rounding and/or off-by-one errors */
642 return min ((uint8_t) n, highest_note());
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 ();
668 struct RegionPositionSorter {
669 bool operator() (RegionView* a, RegionView* b) {
670 return a->region()->position() < b->region()->position();
675 MidiStreamView::paste (ARDOUR::framepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t sub_num)
677 /* Paste into the first region which starts on or before pos. Only called when
678 using an internal editing tool. */
680 if (region_views.empty()) {
684 region_views.sort (RegionView::PositionOrder());
686 list<RegionView*>::const_iterator prev = region_views.begin ();
688 for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
689 if ((*i)->region()->position() > pos) {
695 boost::shared_ptr<Region> r = (*prev)->region ();
697 /* If *prev doesn't cover pos, it's no good */
698 if (r->position() > pos || ((r->position() + r->length()) < pos)) {
702 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
703 return mrv ? mrv->paste(pos, selection, ctx, sub_num) : false;