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) {
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 /* do this is order of highest ... lowest since that matches the
299 * coordinate system in which y=0 is at the top
302 for (int i = highest_note(); i >= lowest_note(); --i) {
306 /* this is the line actually corresponding to the division
310 _note_lines->add (y, 1.0, ARDOUR_UI::config()->get_canvasvar_PianoRollBlackOutline());
312 /* now add a thicker line/bar which covers the entire vertical
313 * height of this note.
322 color = ARDOUR_UI::config()->get_canvasvar_PianoRollBlack();
325 color = ARDOUR_UI::config()->get_canvasvar_PianoRollWhite();
329 double h = y - prev_y;
330 double mid = y + (h/2.0);
333 _note_lines->add (mid, h, color);
341 MidiStreamView::set_note_range(VisibleNoteRange r)
343 if (r == FullRange) {
347 _lowest_note = _data_note_min;
348 _highest_note = _data_note_max;
351 apply_note_range(_lowest_note, _highest_note, true);
355 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
357 _highest_note = highest;
358 _lowest_note = lowest;
360 int const max_note_height = 20; // This should probably be based on text size...
361 int const range = _highest_note - _lowest_note;
362 int const pixels_per_note = floor (child_height () / range);
364 /* do not grow note height beyond 10 pixels */
365 if (pixels_per_note > max_note_height) {
367 int const available_note_range = floor (child_height() / max_note_height);
368 int additional_notes = available_note_range - range;
370 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
371 for (int i = 0; i < additional_notes; i++){
373 if (i % 2 && _highest_note < 127){
379 else if (_lowest_note > 0){
388 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
389 note_range_adjustment.set_value(_lowest_note);
393 if (to_region_views) {
394 apply_note_range_to_regions ();
401 MidiStreamView::apply_note_range_to_regions ()
403 if (!_updates_suspended) {
404 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
405 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
411 MidiStreamView::update_note_range(uint8_t note_num)
413 _data_note_min = min(_data_note_min, note_num);
414 _data_note_max = max(_data_note_max, note_num);
418 MidiStreamView::setup_rec_box ()
420 // cerr << _trackview.name() << " streamview SRB\n";
422 if (_trackview.session()->transport_rolling()) {
425 _trackview.session()->record_status() == Session::Recording &&
426 _trackview.track()->record_enabled()) {
428 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
430 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
432 MidiRegion::SourceList sources;
434 rec_data_ready_connections.drop_connections ();
436 sources.push_back (_trackview.midi_track()->write_source());
440 framepos_t start = 0;
441 if (rec_regions.size() > 0) {
442 start = rec_regions.back().first->start()
443 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
446 if (!rec_regions.empty()) {
447 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
453 plist.add (ARDOUR::Properties::start, start);
454 plist.add (ARDOUR::Properties::length, 1);
455 /* Just above we're setting this nascent region's length to 1. I think this
456 is so that the RegionView gets created with a non-zero width, as apparently
457 creating a RegionView with a zero width causes it never to be displayed
458 (there is a warning in TimeAxisViewItem::init about this). However, we
459 must also set length_beats to something non-zero, otherwise the frame length
460 of 1 causes length_beats to be set to some small quantity << 1. Then
461 when the position is set up below, this length_beats is used to recompute
462 length using BeatsFramesConverter::to, which is slightly innacurate for small
463 beats values because it converts floating point beats to bars, beats and
464 integer ticks. The upshot of which being that length gets set back to 0,
465 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
467 plist.add (ARDOUR::Properties::length_beats, 1);
468 plist.add (ARDOUR::Properties::name, string());
469 plist.add (ARDOUR::Properties::layer, 0);
471 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
472 (RegionFactory::create (sources, plist, false)));
474 region->set_start (_trackview.track()->current_capture_start()
475 - _trackview.track()->get_capture_start_frame (0));
476 region->set_position (_trackview.track()->current_capture_start());
477 RegionView* rv = add_region_view_internal (region, false);
478 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
482 /* rec region will be destroyed in setup_rec_box */
483 rec_regions.push_back (make_pair (region, rv));
485 /* we add the region later */
486 setup_new_rec_layer_time (region);
488 error << _("failed to create MIDI region") << endmsg;
492 /* start a new rec box */
494 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
495 framepos_t const frame_pos = mt->current_capture_start ();
496 gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
497 gdouble const xend = xstart;
500 fill_color = ARDOUR_UI::config()->get_canvasvar_RecordingRect();
502 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
503 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
504 rec_rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RecordingRect());
505 rec_rect->set_fill_color (fill_color);
506 rec_rect->lower_to_bottom();
509 recbox.rectangle = rec_rect;
510 recbox.start = _trackview.session()->transport_frame();
513 rec_rects.push_back (recbox);
515 screen_update_connection.disconnect();
516 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
517 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
521 } else if (rec_active &&
522 (_trackview.session()->record_status() != Session::Recording ||
523 !_trackview.track()->record_enabled())) {
524 screen_update_connection.disconnect();
526 rec_updating = false;
531 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
533 if (!rec_rects.empty() || !rec_regions.empty()) {
535 /* disconnect rapid update */
536 screen_update_connection.disconnect();
537 rec_data_ready_connections.drop_connections ();
539 rec_updating = false;
542 /* remove temp regions */
544 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
545 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
550 (*iter).first->drop_references ();
557 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
559 /* transport stopped, clear boxes */
560 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
561 RecBoxInfo &rect = (*iter);
562 delete rect.rectangle;
572 MidiStreamView::color_handler ()
576 if (_trackview.is_midi_track()) {
577 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiTrackBase());
579 canvas_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_MidiBusBase());
584 MidiStreamView::note_range_adjustment_changed()
586 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
587 int lowest = (int) floor(note_range_adjustment.get_value());
590 if (sum == _range_sum_cache) {
591 //cerr << "cached" << endl;
592 highest = (int) floor(sum);
594 //cerr << "recalc" << endl;
595 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
596 _range_sum_cache = sum;
599 if (lowest == _lowest_note && highest == _highest_note) {
603 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
604 //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;
606 _lowest_note = lowest;
607 _highest_note = highest;
608 apply_note_range(lowest, highest, true);
612 MidiStreamView::update_rec_box ()
614 StreamView::update_rec_box ();
616 if (rec_regions.empty()) {
620 /* Update the region being recorded to reflect where we currently are */
621 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
622 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
624 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
625 mrv->extend_active_notes ();
629 MidiStreamView::y_to_note (double y) const
631 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
636 } else if (n > 127) {
643 /** Suspend updates to the regions' note ranges and our
644 * note lines until resume_updates() is called.
647 MidiStreamView::suspend_updates ()
649 _updates_suspended = true;
652 /** Resume updates to region note ranges and note lines,
653 * and update them now.
656 MidiStreamView::resume_updates ()
658 _updates_suspended = false;
661 apply_note_range_to_regions ();
663 _canvas_group->redraw ();
667 MidiStreamView::leave_internal_edit_mode ()
669 StreamView::leave_internal_edit_mode ();
670 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
671 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
673 mrv->clear_selection ();