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;
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::Group (_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);
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 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)
104 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
110 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
111 _samples_per_pixel, region_color);
113 region_view->init (region_color, false);
119 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
121 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
127 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
128 if ((*i)->region() == r) {
130 /* great. we already have a MidiRegionView for this Region. use it again. */
132 (*i)->set_valid (true);
134 display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
140 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
141 if (region_view == 0) {
145 region_views.push_front (region_view);
147 if (_trackview.editor().internal_editing()) {
148 region_view->hide_rect ();
150 region_view->show_rect ();
153 /* display events and find note range */
154 display_region (region_view, wfd);
156 /* fit note range if we are importing */
157 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
158 set_note_range (ContentsRange);
161 /* catch regionview going away */
162 boost::weak_ptr<Region> wr (region); // make this explicit
163 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
165 RegionViewAdded (region_view);
171 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
177 region_view->enable_display(true);
179 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
182 source->load_model();
185 _range_dirty = update_data_note_range(
186 source->model()->lowest_note(),
187 source->model()->highest_note());
189 // Display region contents
190 region_view->set_height (child_height());
191 region_view->display_model(source->model());
195 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
197 StreamView::display_track (tr);
205 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
207 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
209 mr->midi_source(0)->load_model();
210 _range_dirty = update_data_note_range(
211 mr->model()->lowest_note(),
212 mr->model()->highest_note());
217 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
220 if (min < _data_note_min) {
221 _data_note_min = min;
224 if (max > _data_note_max) {
225 _data_note_max = max;
232 MidiStreamView::redisplay_track ()
234 if (!_trackview.is_midi_track()) {
238 list<RegionView*>::iterator i;
240 // Load models if necessary, and find note range of all our contents
241 _range_dirty = false;
242 _data_note_min = 127;
244 _trackview.track()->playlist()->foreach_region(
245 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
247 // No notes, use default range
253 // Flag region views as invalid and disable drawing
254 for (i = region_views.begin(); i != region_views.end(); ++i) {
255 (*i)->set_valid(false);
256 (*i)->enable_display(false);
259 // Add and display region views, and flag them as valid
260 _trackview.track()->playlist()->foreach_region(
261 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
263 // Stack regions by layer, and remove invalid regions
266 // Update note range (not regions which are correct) and draw note lines
267 apply_note_range(_lowest_note, _highest_note, false);
272 MidiStreamView::update_contents_height ()
274 StreamView::update_contents_height();
275 _note_lines->set_height (child_height ());
277 apply_note_range (lowest_note(), highest_note(), true);
281 MidiStreamView::draw_note_lines()
283 if (!_note_lines || _updates_suspended) {
288 double prev_y = contents_height();
291 _note_lines->clear();
293 if (child_height() < 140 || note_height() < 3) {
294 /* track is too small for note lines, or there are too many */
298 for (int i = lowest_note(); i <= highest_note(); ++i) {
299 y = floor(note_to_y(i));
301 _note_lines->add (prev_y, 1.0, ARDOUR_UI::config()->get_canvasvar_PianoRollBlackOutline());
309 color = ARDOUR_UI::config()->get_canvasvar_PianoRollBlack();
312 color = ARDOUR_UI::config()->get_canvasvar_PianoRollWhite();
316 if (i == highest_note()) {
317 _note_lines->add (y, prev_y - y, color);
319 _note_lines->add (y + 1.0, prev_y - y - 1.0, color);
327 MidiStreamView::set_note_range(VisibleNoteRange r)
329 if (r == FullRange) {
333 _lowest_note = _data_note_min;
334 _highest_note = _data_note_max;
337 apply_note_range(_lowest_note, _highest_note, true);
341 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
343 _highest_note = highest;
344 _lowest_note = lowest;
346 int const max_note_height = 20; // This should probably be based on text size...
347 int const range = _highest_note - _lowest_note;
348 int const pixels_per_note = floor (child_height () / range);
350 /* do not grow note height beyond 10 pixels */
351 if (pixels_per_note > max_note_height) {
353 int const available_note_range = floor (child_height() / max_note_height);
354 int additional_notes = available_note_range - range;
356 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
357 for (int i = 0; i < additional_notes; i++){
359 if (i % 2 && _highest_note < 127){
365 else if (_lowest_note > 0){
374 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
375 note_range_adjustment.set_value(_lowest_note);
379 if (to_region_views) {
380 apply_note_range_to_regions ();
387 MidiStreamView::apply_note_range_to_regions ()
389 if (!_updates_suspended) {
390 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
391 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
397 MidiStreamView::update_note_range(uint8_t note_num)
399 _data_note_min = min(_data_note_min, note_num);
400 _data_note_max = max(_data_note_max, note_num);
404 MidiStreamView::setup_rec_box ()
406 // cerr << _trackview.name() << " streamview SRB\n";
408 if (_trackview.session()->transport_rolling()) {
411 _trackview.session()->record_status() == Session::Recording &&
412 _trackview.track()->record_enabled()) {
414 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
416 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
418 MidiRegion::SourceList sources;
420 rec_data_ready_connections.drop_connections ();
422 sources.push_back (_trackview.midi_track()->write_source());
426 framepos_t start = 0;
427 if (rec_regions.size() > 0) {
428 start = rec_regions.back().first->start()
429 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
432 if (!rec_regions.empty()) {
433 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
439 plist.add (ARDOUR::Properties::start, start);
440 plist.add (ARDOUR::Properties::length, 1);
441 /* Just above we're setting this nascent region's length to 1. I think this
442 is so that the RegionView gets created with a non-zero width, as apparently
443 creating a RegionView with a zero width causes it never to be displayed
444 (there is a warning in TimeAxisViewItem::init about this). However, we
445 must also set length_beats to something non-zero, otherwise the frame length
446 of 1 causes length_beats to be set to some small quantity << 1. Then
447 when the position is set up below, this length_beats is used to recompute
448 length using BeatsFramesConverter::to, which is slightly innacurate for small
449 beats values because it converts floating point beats to bars, beats and
450 integer ticks. The upshot of which being that length gets set back to 0,
451 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
453 plist.add (ARDOUR::Properties::length_beats, 1);
454 plist.add (ARDOUR::Properties::name, string());
455 plist.add (ARDOUR::Properties::layer, 0);
457 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
458 (RegionFactory::create (sources, plist, false)));
460 region->set_start (_trackview.track()->current_capture_start()
461 - _trackview.track()->get_capture_start_frame (0));
462 region->set_position (_trackview.track()->current_capture_start());
463 RegionView* rv = add_region_view_internal (region, false);
464 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
468 /* rec region will be destroyed in setup_rec_box */
469 rec_regions.push_back (make_pair (region, rv));
471 /* we add the region later */
472 setup_new_rec_layer_time (region);
474 error << _("failed to create MIDI region") << endmsg;
478 /* start a new rec box */
480 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
481 framepos_t const frame_pos = mt->current_capture_start ();
482 gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
483 gdouble const xend = xstart;
486 fill_color = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
488 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
489 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
490 rec_rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
491 rec_rect->set_fill_color (fill_color);
492 rec_rect->lower_to_bottom();
495 recbox.rectangle = rec_rect;
496 recbox.start = _trackview.session()->transport_frame();
499 rec_rects.push_back (recbox);
501 screen_update_connection.disconnect();
502 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
503 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
507 } else if (rec_active &&
508 (_trackview.session()->record_status() != Session::Recording ||
509 !_trackview.track()->record_enabled())) {
510 screen_update_connection.disconnect();
512 rec_updating = false;
517 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
519 if (!rec_rects.empty() || !rec_regions.empty()) {
521 /* disconnect rapid update */
522 screen_update_connection.disconnect();
523 rec_data_ready_connections.drop_connections ();
525 rec_updating = false;
528 /* remove temp regions */
530 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
531 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
536 (*iter).first->drop_references ();
543 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
545 /* transport stopped, clear boxes */
546 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
547 RecBoxInfo &rect = (*iter);
548 delete rect.rectangle;
558 MidiStreamView::color_handler ()
562 if (_trackview.is_midi_track()) {
563 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiTrackBase());
565 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiBusBase());
570 MidiStreamView::note_range_adjustment_changed()
572 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
573 int lowest = (int) floor(note_range_adjustment.get_value());
576 if (sum == _range_sum_cache) {
577 //cerr << "cached" << endl;
578 highest = (int) floor(sum);
580 //cerr << "recalc" << endl;
581 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
582 _range_sum_cache = sum;
585 if (lowest == _lowest_note && highest == _highest_note) {
589 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
590 //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;
592 _lowest_note = lowest;
593 _highest_note = highest;
594 apply_note_range(lowest, highest, true);
598 MidiStreamView::update_rec_box ()
600 StreamView::update_rec_box ();
602 if (rec_regions.empty()) {
606 /* Update the region being recorded to reflect where we currently are */
607 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
608 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
610 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
611 mrv->extend_active_notes ();
615 MidiStreamView::y_to_note (double y) const
617 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
622 } else if (n > 127) {
629 /** Suspend updates to the regions' note ranges and our
630 * note lines until resume_updates() is called.
633 MidiStreamView::suspend_updates ()
635 _updates_suspended = true;
638 /** Resume updates to region note ranges and note lines,
639 * and update them now.
642 MidiStreamView::resume_updates ()
644 _updates_suspended = false;
647 apply_note_range_to_regions ();
651 MidiStreamView::leave_internal_edit_mode ()
653 StreamView::leave_internal_edit_mode ();
654 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
655 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
657 mrv->clear_selection ();