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)
105 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
111 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
112 _samples_per_pixel, region_color);
114 region_view->init (false);
120 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wait_for_data, bool recording)
122 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
128 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
129 if ((*i)->region() == r) {
131 /* great. we already have a MidiRegionView for this Region. use it again. */
133 (*i)->set_valid (true);
135 display_region(dynamic_cast<MidiRegionView*>(*i), wait_for_data);
141 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wait_for_data, recording));
142 if (region_view == 0) {
146 region_views.push_front (region_view);
148 if (_trackview.editor().internal_editing()) {
149 region_view->hide_rect ();
151 region_view->show_rect ();
154 /* display events and find note range */
155 display_region (region_view, wait_for_data);
157 /* fit note range if we are importing */
158 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
159 set_note_range (ContentsRange);
162 /* catch regionview going away */
163 boost::weak_ptr<Region> wr (region); // make this explicit
164 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
166 RegionViewAdded (region_view);
172 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
178 region_view->enable_display (true);
180 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
183 source->load_model();
186 _range_dirty = update_data_note_range(
187 source->model()->lowest_note(),
188 source->model()->highest_note());
190 // Display region contents
191 region_view->set_height (child_height());
192 region_view->display_model(source->model());
197 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
199 StreamView::display_track (tr);
207 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
209 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
211 mr->midi_source(0)->load_model();
212 _range_dirty = update_data_note_range(
213 mr->model()->lowest_note(),
214 mr->model()->highest_note());
219 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
222 if (min < _data_note_min) {
223 _data_note_min = min;
226 if (max > _data_note_max) {
227 _data_note_max = max;
234 MidiStreamView::redisplay_track ()
236 if (!_trackview.is_midi_track()) {
240 list<RegionView*>::iterator i;
242 // Load models if necessary, and find note range of all our contents
243 _range_dirty = false;
244 _data_note_min = 127;
246 _trackview.track()->playlist()->foreach_region(
247 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
249 // No notes, use default range
255 // Flag region views as invalid and disable drawing
256 for (i = region_views.begin(); i != region_views.end(); ++i) {
257 (*i)->set_valid(false);
258 (*i)->enable_display(false);
261 // Add and display region views, and flag them as valid
262 _trackview.track()->playlist()->foreach_region(
263 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
265 // Stack regions by layer, and remove invalid regions
268 // Update note range (not regions which are correct) and draw note lines
269 apply_note_range(_lowest_note, _highest_note, false);
274 MidiStreamView::update_contents_height ()
276 StreamView::update_contents_height();
278 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
280 apply_note_range (lowest_note(), highest_note(), true);
284 MidiStreamView::draw_note_lines()
286 if (!_note_lines || _updates_suspended) {
294 _note_lines->clear();
296 if (child_height() < 140 || note_height() < 3) {
297 /* track is too small for note lines, or there are too many */
301 /* do this is order of highest ... lowest since that matches the
302 * coordinate system in which y=0 is at the top
305 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
307 y = floor(note_to_y (i)) + .5;
309 /* this is the line actually corresponding to the division
313 if (i <= highest_note()) {
314 _note_lines->add (y, 1.0, ARDOUR_UI::config()->get_canvasvar_PianoRollBlackOutline());
317 /* now add a thicker line/bar which covers the entire vertical
318 * height of this note.
327 color = ARDOUR_UI::config()->get_canvasvar_PianoRollBlack();
330 color = ARDOUR_UI::config()->get_canvasvar_PianoRollWhite();
334 double h = y - prev_y;
335 double mid = y + (h/2.0);
337 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
338 _note_lines->add (mid, h, color);
346 MidiStreamView::set_note_range(VisibleNoteRange r)
348 if (r == FullRange) {
352 _lowest_note = _data_note_min;
353 _highest_note = _data_note_max;
356 apply_note_range(_lowest_note, _highest_note, true);
360 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
362 _highest_note = highest;
363 _lowest_note = lowest;
365 int const max_note_height = 20; // This should probably be based on text size...
366 int const range = _highest_note - _lowest_note;
367 int const pixels_per_note = floor (child_height () / range);
369 /* do not grow note height beyond 10 pixels */
370 if (pixels_per_note > max_note_height) {
372 int const available_note_range = floor (child_height() / max_note_height);
373 int additional_notes = available_note_range - range;
375 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
376 for (int i = 0; i < additional_notes; i++){
378 if (i % 2 && _highest_note < 127){
384 else if (_lowest_note > 0){
393 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
394 note_range_adjustment.set_value(_lowest_note);
398 if (to_region_views) {
399 apply_note_range_to_regions ();
406 MidiStreamView::apply_note_range_to_regions ()
408 if (!_updates_suspended) {
409 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
410 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
416 MidiStreamView::update_note_range(uint8_t note_num)
418 _data_note_min = min(_data_note_min, note_num);
419 _data_note_max = max(_data_note_max, note_num);
423 MidiStreamView::setup_rec_box ()
425 // cerr << _trackview.name() << " streamview SRB\n";
427 if (_trackview.session()->transport_rolling()) {
430 _trackview.session()->record_status() == Session::Recording &&
431 _trackview.track()->record_enabled()) {
433 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
435 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
437 MidiRegion::SourceList sources;
439 rec_data_ready_connections.drop_connections ();
441 sources.push_back (_trackview.midi_track()->write_source());
445 framepos_t start = 0;
446 if (rec_regions.size() > 0) {
447 start = rec_regions.back().first->start()
448 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
451 if (!rec_regions.empty()) {
452 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
458 plist.add (ARDOUR::Properties::start, start);
459 plist.add (ARDOUR::Properties::length, 1);
460 /* Just above we're setting this nascent region's length to 1. I think this
461 is so that the RegionView gets created with a non-zero width, as apparently
462 creating a RegionView with a zero width causes it never to be displayed
463 (there is a warning in TimeAxisViewItem::init about this). However, we
464 must also set length_beats to something non-zero, otherwise the frame length
465 of 1 causes length_beats to be set to some small quantity << 1. Then
466 when the position is set up below, this length_beats is used to recompute
467 length using BeatsFramesConverter::to, which is slightly innacurate for small
468 beats values because it converts floating point beats to bars, beats and
469 integer ticks. The upshot of which being that length gets set back to 0,
470 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
472 plist.add (ARDOUR::Properties::length_beats, 1);
473 plist.add (ARDOUR::Properties::name, string());
474 plist.add (ARDOUR::Properties::layer, 0);
476 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
477 (RegionFactory::create (sources, plist, false)));
479 region->set_start (_trackview.track()->current_capture_start()
480 - _trackview.track()->get_capture_start_frame (0));
481 region->set_position (_trackview.track()->current_capture_start());
482 RegionView* rv = add_region_view_internal (region, false);
483 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
487 /* rec region will be destroyed in setup_rec_box */
488 rec_regions.push_back (make_pair (region, rv));
490 /* we add the region later */
491 setup_new_rec_layer_time (region);
493 error << _("failed to create MIDI region") << endmsg;
497 /* start a new rec box */
499 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
500 framepos_t const frame_pos = mt->current_capture_start ();
501 gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
502 gdouble const xend = xstart;
505 fill_color = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
507 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
508 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
509 rec_rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
510 rec_rect->set_fill_color (fill_color);
511 rec_rect->lower_to_bottom();
514 recbox.rectangle = rec_rect;
515 recbox.start = _trackview.session()->transport_frame();
518 rec_rects.push_back (recbox);
520 screen_update_connection.disconnect();
521 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
522 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
526 } else if (rec_active &&
527 (_trackview.session()->record_status() != Session::Recording ||
528 !_trackview.track()->record_enabled())) {
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 ();
544 rec_updating = false;
547 /* remove temp regions */
549 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
550 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
555 (*iter).first->drop_references ();
562 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
564 /* transport stopped, clear boxes */
565 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
566 RecBoxInfo &rect = (*iter);
567 delete rect.rectangle;
577 MidiStreamView::color_handler ()
581 if (_trackview.is_midi_track()) {
582 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiTrackBase());
584 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiBusBase());
589 MidiStreamView::note_range_adjustment_changed()
591 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
592 int lowest = (int) floor(note_range_adjustment.get_value());
595 if (sum == _range_sum_cache) {
596 //cerr << "cached" << endl;
597 highest = (int) floor(sum);
599 //cerr << "recalc" << endl;
600 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
601 _range_sum_cache = sum;
604 if (lowest == _lowest_note && highest == _highest_note) {
608 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
609 //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;
611 _lowest_note = lowest;
612 _highest_note = highest;
613 apply_note_range(lowest, highest, true);
617 MidiStreamView::update_rec_box ()
619 StreamView::update_rec_box ();
621 if (rec_regions.empty()) {
625 /* Update the region being recorded to reflect where we currently are */
626 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
627 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
629 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
630 mrv->extend_active_notes ();
634 MidiStreamView::y_to_note (double y) const
636 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
641 } else if (n > 127) {
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 ();
672 MidiStreamView::leave_internal_edit_mode ()
674 StreamView::leave_internal_edit_mode ();
675 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
676 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
678 mrv->clear_selection ();