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 "ardour_ui.h"
38 #include "global_signals.h"
39 #include "gui_thread.h"
40 #include "midi_region_view.h"
41 #include "midi_streamview.h"
42 #include "midi_time_axis.h"
43 #include "midi_util.h"
44 #include "public_editor.h"
45 #include "region_selection.h"
46 #include "region_view.h"
47 #include "rgb_macros.h"
48 #include "selection.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 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::redisplay_track ()
249 if (!_trackview.is_midi_track()) {
253 list<RegionView*>::iterator i;
255 // Load models if necessary, and find note range of all our contents
256 _range_dirty = false;
257 _data_note_min = 127;
259 _trackview.track()->playlist()->foreach_region(
260 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
262 // No notes, use default range
268 // Flag region views as invalid and disable drawing
269 for (i = region_views.begin(); i != region_views.end(); ++i) {
270 (*i)->set_valid(false);
271 (*i)->enable_display(false);
274 // Add and display region views, and flag them as valid
275 _trackview.track()->playlist()->foreach_region(
276 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
278 // Stack regions by layer, and remove invalid regions
281 // Update note range (not regions which are correct) and draw note lines
282 apply_note_range(_lowest_note, _highest_note, false);
287 MidiStreamView::update_contents_height ()
289 StreamView::update_contents_height();
291 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
293 apply_note_range (lowest_note(), highest_note(), true);
297 MidiStreamView::draw_note_lines()
299 if (!_note_lines || _updates_suspended) {
307 _note_lines->clear();
309 if (child_height() < 140 || note_height() < 3) {
310 /* track is too small for note lines, or there are too many */
314 /* do this is order of highest ... lowest since that matches the
315 * coordinate system in which y=0 is at the top
318 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
320 y = floor(note_to_y (i)) + .5;
322 /* this is the line actually corresponding to the division
326 if (i <= highest_note()) {
327 _note_lines->add (y, 1.0, ARDOUR_UI::config()->color ("piano roll black outline"));
330 /* now add a thicker line/bar which covers the entire vertical
331 * height of this note.
340 color = ARDOUR_UI::config()->color_mod ("piano roll black", "piano roll black");
343 color = ARDOUR_UI::config()->color_mod ("piano roll white", "piano roll white");
347 double h = y - prev_y;
348 double mid = y + (h/2.0);
350 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
351 _note_lines->add (mid, h, color);
359 MidiStreamView::set_note_range(VisibleNoteRange r)
361 if (r == FullRange) {
365 _lowest_note = _data_note_min;
366 _highest_note = _data_note_max;
369 apply_note_range(_lowest_note, _highest_note, true);
373 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
375 _highest_note = highest;
376 _lowest_note = lowest;
378 int const max_note_height = 20; // This should probably be based on text size...
379 int const range = _highest_note - _lowest_note;
380 int const pixels_per_note = floor (child_height () / range);
382 /* do not grow note height beyond 10 pixels */
383 if (pixels_per_note > max_note_height) {
385 int const available_note_range = floor (child_height() / max_note_height);
386 int additional_notes = available_note_range - range;
388 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
389 for (int i = 0; i < additional_notes; i++){
391 if (i % 2 && _highest_note < 127){
397 else if (_lowest_note > 0){
406 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
407 note_range_adjustment.set_value(_lowest_note);
411 if (to_region_views) {
412 apply_note_range_to_regions ();
419 MidiStreamView::apply_note_range_to_regions ()
421 if (!_updates_suspended) {
422 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
423 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
429 MidiStreamView::update_note_range(uint8_t note_num)
431 _data_note_min = min(_data_note_min, note_num);
432 _data_note_max = max(_data_note_max, note_num);
436 MidiStreamView::setup_rec_box ()
438 // cerr << _trackview.name() << " streamview SRB\n";
440 if (_trackview.session()->transport_rolling()) {
443 _trackview.session()->record_status() == Session::Recording &&
444 _trackview.track()->record_enabled()) {
446 if (ARDOUR_UI::config()->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
448 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
450 MidiRegion::SourceList sources;
452 rec_data_ready_connections.drop_connections ();
454 sources.push_back (_trackview.midi_track()->write_source());
458 framepos_t start = 0;
459 if (rec_regions.size() > 0) {
460 start = rec_regions.back().first->start()
461 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
464 if (!rec_regions.empty()) {
465 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
471 plist.add (ARDOUR::Properties::start, start);
472 plist.add (ARDOUR::Properties::length, 1);
473 /* Just above we're setting this nascent region's length to 1. I think this
474 is so that the RegionView gets created with a non-zero width, as apparently
475 creating a RegionView with a zero width causes it never to be displayed
476 (there is a warning in TimeAxisViewItem::init about this). However, we
477 must also set length_beats to something non-zero, otherwise the frame length
478 of 1 causes length_beats to be set to some small quantity << 1. Then
479 when the position is set up below, this length_beats is used to recompute
480 length using BeatsFramesConverter::to, which is slightly innacurate for small
481 beats values because it converts floating point beats to bars, beats and
482 integer ticks. The upshot of which being that length gets set back to 0,
483 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
485 plist.add (ARDOUR::Properties::length_beats, 1);
486 plist.add (ARDOUR::Properties::name, string());
487 plist.add (ARDOUR::Properties::layer, 0);
489 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
490 (RegionFactory::create (sources, plist, false)));
492 region->set_start (_trackview.track()->current_capture_start()
493 - _trackview.track()->get_capture_start_frame (0));
494 region->set_position (_trackview.session()->transport_frame());
496 RegionView* rv = add_region_view_internal (region, false, true);
497 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
500 /* rec region will be destroyed in setup_rec_box */
501 rec_regions.push_back (make_pair (region, rv));
503 /* we add the region later */
504 setup_new_rec_layer_time (region);
506 error << _("failed to create MIDI region") << endmsg;
510 /* start a new rec box */
512 create_rec_box(_trackview.midi_track()->current_capture_start(), 0);
514 } else if (rec_active &&
515 (_trackview.session()->record_status() != Session::Recording ||
516 !_trackview.track()->record_enabled())) {
517 screen_update_connection.disconnect();
519 rec_updating = false;
524 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
526 if (!rec_rects.empty() || !rec_regions.empty()) {
528 /* disconnect rapid update */
529 screen_update_connection.disconnect();
530 rec_data_ready_connections.drop_connections ();
531 rec_updating = false;
534 /* remove temp regions */
536 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
537 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
542 (*iter).first->drop_references ();
549 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
551 /* transport stopped, clear boxes */
552 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
553 RecBoxInfo &rect = (*iter);
554 delete rect.rectangle;
564 MidiStreamView::color_handler ()
568 if (_trackview.is_midi_track()) {
569 canvas_rect->set_fill_color (ARDOUR_UI::config()->color_mod ("midi track base", "midi track base"));
571 canvas_rect->set_fill_color (ARDOUR_UI::config()->color ("midi bus base"));
576 MidiStreamView::note_range_adjustment_changed()
578 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
579 int lowest = (int) floor(note_range_adjustment.get_value());
582 if (sum == _range_sum_cache) {
583 //cerr << "cached" << endl;
584 highest = (int) floor(sum);
586 //cerr << "recalc" << endl;
587 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
588 _range_sum_cache = sum;
591 if (lowest == _lowest_note && highest == _highest_note) {
595 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
596 //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;
598 _lowest_note = lowest;
599 _highest_note = highest;
600 apply_note_range(lowest, highest, true);
604 MidiStreamView::update_rec_box ()
606 StreamView::update_rec_box ();
608 if (rec_regions.empty()) {
612 /* Update the region being recorded to reflect where we currently are */
613 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
614 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
616 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
617 mrv->extend_active_notes ();
621 MidiStreamView::y_to_note (double y) const
623 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
628 } else if (n > 127) {
635 /** Suspend updates to the regions' note ranges and our
636 * note lines until resume_updates() is called.
639 MidiStreamView::suspend_updates ()
641 _updates_suspended = true;
644 /** Resume updates to region note ranges and note lines,
645 * and update them now.
648 MidiStreamView::resume_updates ()
650 _updates_suspended = false;
653 apply_note_range_to_regions ();
655 _canvas_group->redraw ();
658 struct RegionPositionSorter {
659 bool operator() (RegionView* a, RegionView* b) {
660 return a->region()->position() < b->region()->position();
665 MidiStreamView::paste (ARDOUR::framepos_t pos, const Selection& selection, PasteContext& ctx)
667 /* Paste into the first region which starts on or before pos. Only called when
668 using an internal editing tool. */
670 if (region_views.empty()) {
674 region_views.sort (RegionView::PositionOrder());
676 list<RegionView*>::const_iterator prev = region_views.begin ();
678 for (list<RegionView*>::const_iterator i = region_views.begin(); i != region_views.end(); ++i) {
679 if ((*i)->region()->position() > pos) {
685 boost::shared_ptr<Region> r = (*prev)->region ();
687 /* If *prev doesn't cover pos, it's no good */
688 if (r->position() > pos || ((r->position() + r->length()) < pos)) {
692 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*prev);
693 return mrv ? mrv->paste(pos, selection, ctx) : false;