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::redisplay_track ()
248 if (!_trackview.is_midi_track()) {
252 list<RegionView*>::iterator i;
254 // Load models if necessary, and find note range of all our contents
255 _range_dirty = false;
256 _data_note_min = 127;
258 _trackview.track()->playlist()->foreach_region(
259 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
261 // No notes, use default range
267 // Flag region views as invalid and disable drawing
268 for (i = region_views.begin(); i != region_views.end(); ++i) {
269 (*i)->set_valid(false);
270 (*i)->enable_display(false);
273 // Add and display region views, and flag them as valid
274 _trackview.track()->playlist()->foreach_region(
275 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
277 // Stack regions by layer, and remove invalid regions
280 // Update note range (not regions which are correct) and draw note lines
281 apply_note_range(_lowest_note, _highest_note, false);
286 MidiStreamView::update_contents_height ()
288 StreamView::update_contents_height();
290 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
292 apply_note_range (lowest_note(), highest_note(), true);
296 MidiStreamView::draw_note_lines()
298 if (!_note_lines || _updates_suspended) {
306 _note_lines->clear();
308 if (child_height() < 140 || note_height() < 3) {
309 /* track is too small for note lines, or there are too many */
313 /* do this is order of highest ... lowest since that matches the
314 * coordinate system in which y=0 is at the top
317 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
319 y = floor(note_to_y (i)) + .5;
321 /* this is the line actually corresponding to the division
325 if (i <= highest_note()) {
326 _note_lines->add (y, 1.0, UIConfiguration::instance().color ("piano roll black outline"));
329 /* now add a thicker line/bar which covers the entire vertical
330 * height of this note.
339 color = UIConfiguration::instance().color_mod ("piano roll black", "piano roll black");
342 color = UIConfiguration::instance().color_mod ("piano roll white", "piano roll white");
346 double h = y - prev_y;
347 double mid = y + (h/2.0);
349 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
350 _note_lines->add (mid, h, color);
358 MidiStreamView::set_note_range(VisibleNoteRange r)
360 if (r == FullRange) {
364 _lowest_note = _data_note_min;
365 _highest_note = _data_note_max;
368 apply_note_range(_lowest_note, _highest_note, true);
372 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
374 _highest_note = highest;
375 _lowest_note = lowest;
377 int const max_note_height = 20; // This should probably be based on text size...
378 int const range = _highest_note - _lowest_note;
379 int const pixels_per_note = floor (child_height () / range);
381 /* do not grow note height beyond 10 pixels */
382 if (pixels_per_note > max_note_height) {
384 int const available_note_range = floor (child_height() / max_note_height);
385 int additional_notes = available_note_range - range;
387 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
388 for (int i = 0; i < additional_notes; i++){
390 if (i % 2 && _highest_note < 127){
396 else if (_lowest_note > 0){
405 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
406 note_range_adjustment.set_value(_lowest_note);
410 if (to_region_views) {
411 apply_note_range_to_regions ();
418 MidiStreamView::apply_note_range_to_regions ()
420 if (!_updates_suspended) {
421 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
422 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
428 MidiStreamView::update_note_range(uint8_t note_num)
430 _data_note_min = min(_data_note_min, note_num);
431 _data_note_max = max(_data_note_max, note_num);
435 MidiStreamView::setup_rec_box ()
437 // cerr << _trackview.name() << " streamview SRB\n";
439 if (_trackview.session()->transport_rolling()) {
442 _trackview.session()->record_status() == Session::Recording &&
443 _trackview.track()->record_enabled()) {
445 if (UIConfiguration::instance().get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
447 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
449 MidiRegion::SourceList sources;
451 rec_data_ready_connections.drop_connections ();
453 sources.push_back (_trackview.midi_track()->write_source());
457 framepos_t start = 0;
458 if (rec_regions.size() > 0) {
459 start = rec_regions.back().first->start()
460 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
463 if (!rec_regions.empty()) {
464 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
470 plist.add (ARDOUR::Properties::start, start);
471 plist.add (ARDOUR::Properties::length, 1);
472 /* Just above we're setting this nascent region's length to 1. I think this
473 is so that the RegionView gets created with a non-zero width, as apparently
474 creating a RegionView with a zero width causes it never to be displayed
475 (there is a warning in TimeAxisViewItem::init about this). However, we
476 must also set length_beats to something non-zero, otherwise the frame length
477 of 1 causes length_beats to be set to some small quantity << 1. Then
478 when the position is set up below, this length_beats is used to recompute
479 length using BeatsFramesConverter::to, which is slightly innacurate for small
480 beats values because it converts floating point beats to bars, beats and
481 integer ticks. The upshot of which being that length gets set back to 0,
482 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
484 plist.add (ARDOUR::Properties::length_beats, 1);
485 plist.add (ARDOUR::Properties::name, string());
486 plist.add (ARDOUR::Properties::layer, 0);
488 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
489 (RegionFactory::create (sources, plist, false)));
491 region->set_start (_trackview.track()->current_capture_start()
492 - _trackview.track()->get_capture_start_frame (0));
493 region->set_position (_trackview.session()->transport_frame());
495 RegionView* rv = add_region_view_internal (region, false, true);
496 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
499 /* rec region will be destroyed in setup_rec_box */
500 rec_regions.push_back (make_pair (region, rv));
502 /* we add the region later */
503 setup_new_rec_layer_time (region);
505 error << _("failed to create MIDI region") << endmsg;
509 /* start a new rec box */
511 create_rec_box(_trackview.midi_track()->current_capture_start(), 0);
513 } else if (rec_active &&
514 (_trackview.session()->record_status() != Session::Recording ||
515 !_trackview.track()->record_enabled())) {
516 screen_update_connection.disconnect();
518 rec_updating = false;
523 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
525 if (!rec_rects.empty() || !rec_regions.empty()) {
527 /* disconnect rapid update */
528 screen_update_connection.disconnect();
529 rec_data_ready_connections.drop_connections ();
530 rec_updating = false;
533 /* remove temp regions */
535 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
536 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
541 (*iter).first->drop_references ();
548 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
550 /* transport stopped, clear boxes */
551 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
552 RecBoxInfo &rect = (*iter);
553 delete rect.rectangle;
563 MidiStreamView::color_handler ()
567 if (_trackview.is_midi_track()) {
568 canvas_rect->set_fill_color (UIConfiguration::instance().color_mod ("midi track base", "midi track base"));
570 canvas_rect->set_fill_color (UIConfiguration::instance().color ("midi bus base"));
575 MidiStreamView::note_range_adjustment_changed()
577 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
578 int lowest = (int) floor(note_range_adjustment.get_value());
581 if (sum == _range_sum_cache) {
582 //cerr << "cached" << endl;
583 highest = (int) floor(sum);
585 //cerr << "recalc" << endl;
586 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
587 _range_sum_cache = sum;
590 if (lowest == _lowest_note && highest == _highest_note) {
594 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
595 //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;
597 _lowest_note = lowest;
598 _highest_note = highest;
599 apply_note_range(lowest, highest, true);
603 MidiStreamView::update_rec_box ()
605 StreamView::update_rec_box ();
607 if (rec_regions.empty()) {
611 /* Update the region being recorded to reflect where we currently are */
612 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
613 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
615 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
616 mrv->extend_active_notes ();
620 MidiStreamView::y_to_note (double y) const
622 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
627 } else if (n > 127) {
634 /** Suspend updates to the regions' note ranges and our
635 * note lines until resume_updates() is called.
638 MidiStreamView::suspend_updates ()
640 _updates_suspended = true;
643 /** Resume updates to region note ranges and note lines,
644 * and update them now.
647 MidiStreamView::resume_updates ()
649 _updates_suspended = false;
652 apply_note_range_to_regions ();
654 _canvas_group->redraw ();
657 struct RegionPositionSorter {
658 bool operator() (RegionView* a, RegionView* b) {
659 return a->region()->position() < b->region()->position();
664 MidiStreamView::paste (ARDOUR::framepos_t pos, const Selection& selection, PasteContext& ctx)
666 /* Paste into the first region which starts on or before pos. Only called when
667 using an internal editing tool. */
669 if (region_views.empty()) {
673 region_views.sort (RegionView::PositionOrder());
675 list<RegionView*>::const_iterator prev = region_views.begin ();
677 for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
678 if ((*i)->region()->position() > pos) {
684 boost::shared_ptr<Region> r = (*prev)->region ();
686 /* If *prev doesn't cover pos, it's no good */
687 if (r->position() > pos || ((r->position() + r->length()) < pos)) {
691 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
692 return mrv ? mrv->paste(pos, selection, ctx) : false;