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.
25 #include <gtkmm2ext/gtk_ui.h>
27 #include "ardour/midi_region.h"
28 #include "ardour/midi_source.h"
29 #include "ardour/midi_track.h"
30 #include "ardour/operations.h"
31 #include "ardour/region_factory.h"
32 #include "ardour/session.h"
33 #include "ardour/smf_source.h"
35 #include "ardour_ui.h"
36 #include "canvas-simplerect.h"
37 #include "global_signals.h"
38 #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"
49 #include "simplerect.h"
53 using namespace ARDOUR;
55 using namespace Editing;
57 MidiStreamView::MidiStreamView (MidiTimeAxisView& tv)
59 , note_range_adjustment(0.0f, 0.0f, 0.0f)
61 , _range_sum_cache(-1.0)
67 , _updates_suspended (false)
69 /* use a group dedicated to MIDI underlays. Audio underlays are not in this group. */
70 midi_underlay_group = new ArdourCanvas::Group (*_canvas_group);
71 midi_underlay_group->lower_to_bottom();
73 /* put the note lines in the timeaxisview's group, so it
74 can be put below ghost regions from MIDI underlays*/
75 _note_lines = new ArdourCanvas::LineSet(*_canvas_group,
76 ArdourCanvas::LineSet::Horizontal);
78 _note_lines->property_x1() = 0;
79 _note_lines->property_y1() = 0;
80 _note_lines->property_x2() = DBL_MAX;
81 _note_lines->property_y2() = 0;
83 _note_lines->signal_event().connect(
84 sigc::bind(sigc::mem_fun(_trackview.editor(),
85 &PublicEditor::canvas_stream_view_event),
86 _note_lines, &_trackview));
88 _note_lines->lower_to_bottom();
92 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
94 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
95 note_range_adjustment.set_value(_lowest_note);
97 note_range_adjustment.signal_value_changed().connect(
98 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
101 MidiStreamView::~MidiStreamView ()
106 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
108 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
114 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
115 _samples_per_unit, region_color);
117 region_view->init (region_color, false);
123 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
125 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
131 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
132 if ((*i)->region() == r) {
134 /* great. we already have a MidiRegionView for this Region. use it again. */
136 (*i)->set_valid (true);
138 display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
144 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
145 if (region_view == 0) {
149 region_views.push_front (region_view);
151 if (_trackview.editor().internal_editing()) {
152 region_view->hide_rect ();
154 region_view->show_rect ();
157 /* display events and find note range */
158 display_region (region_view, wfd);
160 /* fit note range if we are importing */
161 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
162 set_note_range (ContentsRange);
165 /* catch regionview going away */
166 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
168 RegionViewAdded (region_view);
174 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
180 region_view->enable_display(true);
182 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
185 source->load_model();
188 _range_dirty = update_data_note_range(
189 source->model()->lowest_note(),
190 source->model()->highest_note());
192 // Display region contents
193 region_view->set_height (child_height());
194 region_view->display_model(source->model());
198 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
200 StreamView::display_track (tr);
208 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
210 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
212 mr->midi_source(0)->load_model();
213 _range_dirty = update_data_note_range(
214 mr->model()->lowest_note(),
215 mr->model()->highest_note());
220 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
223 if (min < _data_note_min) {
224 _data_note_min = min;
227 if (max > _data_note_max) {
228 _data_note_max = max;
235 MidiStreamView::redisplay_track ()
237 if (!_trackview.is_midi_track()) {
241 list<RegionView*>::iterator i;
243 // Load models if necessary, and find note range of all our contents
244 _range_dirty = false;
245 _data_note_min = 127;
247 _trackview.track()->playlist()->foreach_region(
248 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
250 // No notes, use default range
256 // Flag region views as invalid and disable drawing
257 for (i = region_views.begin(); i != region_views.end(); ++i) {
258 (*i)->set_valid(false);
259 (*i)->enable_display(false);
262 // Add and display region views, and flag them as valid
263 _trackview.track()->playlist()->foreach_region(
264 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
266 // Stack regions by layer, and remove invalid regions
269 // Update note range (not regions which are correct) and draw note lines
270 apply_note_range(_lowest_note, _highest_note, false);
275 MidiStreamView::update_contents_height ()
277 StreamView::update_contents_height();
278 _note_lines->property_y2() = child_height ();
280 apply_note_range (lowest_note(), highest_note(), true);
284 MidiStreamView::draw_note_lines()
286 if (!_note_lines || _updates_suspended) {
291 double prev_y = contents_height();
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 for (int i = lowest_note(); i <= highest_note(); ++i) {
302 y = floor(note_to_y(i));
304 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
312 color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
315 color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
319 if (i == highest_note()) {
320 _note_lines->add_line(y, prev_y - y, color);
322 _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
330 MidiStreamView::set_note_range(VisibleNoteRange r)
332 if (r == FullRange) {
336 _lowest_note = _data_note_min;
337 _highest_note = _data_note_max;
340 apply_note_range(_lowest_note, _highest_note, true);
344 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
346 _highest_note = highest;
347 _lowest_note = lowest;
349 int const max_note_height = 20; // This should probably be based on text size...
350 int const range = _highest_note - _lowest_note;
351 int const pixels_per_note = floor (child_height () / range);
353 /* do not grow note height beyond 10 pixels */
354 if (pixels_per_note > max_note_height) {
356 int const available_note_range = floor (child_height() / max_note_height);
357 int additional_notes = available_note_range - range;
359 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
360 for (int i = 0; i < additional_notes; i++){
362 if (i % 2 && _highest_note < 127){
368 else if (_lowest_note > 0){
377 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
378 note_range_adjustment.set_value(_lowest_note);
382 if (to_region_views) {
383 apply_note_range_to_regions ();
390 MidiStreamView::apply_note_range_to_regions ()
392 if (!_updates_suspended) {
393 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
394 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
400 MidiStreamView::update_note_range(uint8_t note_num)
402 assert(note_num <= 127);
403 _data_note_min = min(_data_note_min, note_num);
404 _data_note_max = max(_data_note_max, note_num);
408 MidiStreamView::setup_rec_box ()
410 // cerr << _trackview.name() << " streamview SRB\n";
412 if (_trackview.session()->transport_rolling()) {
415 _trackview.session()->record_status() == Session::Recording &&
416 _trackview.track()->record_enabled()) {
418 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
420 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
422 MidiRegion::SourceList sources;
424 rec_data_ready_connections.drop_connections ();
426 sources.push_back (_trackview.midi_track()->write_source());
430 framepos_t start = 0;
431 if (rec_regions.size() > 0) {
432 start = rec_regions.back().first->start()
433 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
436 if (!rec_regions.empty()) {
437 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
443 plist.add (ARDOUR::Properties::start, start);
444 plist.add (ARDOUR::Properties::length, 1);
445 /* Just above we're setting this nascent region's length to 1. I think this
446 is so that the RegionView gets created with a non-zero width, as apparently
447 creating a RegionView with a zero width causes it never to be displayed
448 (there is a warning in TimeAxisViewItem::init about this). However, we
449 must also set length_beats to something non-zero, otherwise the frame length
450 of 1 causes length_beats to be set to some small quantity << 1. Then
451 when the position is set up below, this length_beats is used to recompute
452 length using BeatsFramesConverter::to, which is slightly innacurate for small
453 beats values because it converts floating point beats to bars, beats and
454 integer ticks. The upshot of which being that length gets set back to 0,
455 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
457 plist.add (ARDOUR::Properties::length_beats, 1);
458 plist.add (ARDOUR::Properties::name, string());
459 plist.add (ARDOUR::Properties::layer, 0);
461 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
462 (RegionFactory::create (sources, plist, false)));
465 region->set_start (_trackview.track()->current_capture_start() - _trackview.track()->get_capture_start_frame (0));
466 region->set_position (_trackview.track()->current_capture_start());
467 RegionView* rv = add_region_view_internal (region, false);
468 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
471 rec_regions.push_back (make_pair (region, rv));
473 // rec regions are destroyed in setup_rec_box
475 /* we add the region later */
477 setup_new_rec_layer_time (region);
480 /* start a new rec box */
482 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
483 framepos_t const frame_pos = mt->current_capture_start ();
484 gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
485 gdouble const xend = xstart;
488 assert(_trackview.midi_track()->mode() == Normal);
490 fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
492 ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
493 rec_rect->property_x1() = xstart;
494 rec_rect->property_y1() = 1.0;
495 rec_rect->property_x2() = xend;
496 rec_rect->property_y2() = (double) _trackview.current_height() - 1;
497 rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
498 rec_rect->property_fill_color_rgba() = fill_color;
499 rec_rect->lower_to_bottom();
502 recbox.rectangle = rec_rect;
503 recbox.start = _trackview.session()->transport_frame();
506 rec_rects.push_back (recbox);
508 screen_update_connection.disconnect();
509 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
510 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
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 ();
532 rec_updating = false;
535 /* remove temp regions */
537 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
538 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
543 (*iter).first->drop_references ();
550 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
552 /* transport stopped, clear boxes */
553 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
554 RecBoxInfo &rect = (*iter);
555 delete rect.rectangle;
565 MidiStreamView::color_handler ()
569 if (_trackview.is_midi_track()) {
570 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
572 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
577 MidiStreamView::note_range_adjustment_changed()
579 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
580 int lowest = (int) floor(note_range_adjustment.get_value());
583 if (sum == _range_sum_cache) {
584 //cerr << "cached" << endl;
585 highest = (int) floor(sum);
587 //cerr << "recalc" << endl;
588 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
589 _range_sum_cache = sum;
592 if (lowest == _lowest_note && highest == _highest_note) {
596 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
597 //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;
599 _lowest_note = lowest;
600 _highest_note = highest;
601 apply_note_range(lowest, highest, true);
605 MidiStreamView::update_rec_box ()
607 StreamView::update_rec_box ();
609 if (rec_regions.empty()) {
613 /* Update the region being recorded to reflect where we currently are */
614 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
615 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
617 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
618 mrv->extend_active_notes ();
622 MidiStreamView::y_to_note (double y) const
624 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
629 } else if (n > 127) {
636 /** Suspend updates to the regions' note ranges and our
637 * note lines until resume_updates() is called.
640 MidiStreamView::suspend_updates ()
642 _updates_suspended = true;
645 /** Resume updates to region note ranges and note lines,
646 * and update them now.
649 MidiStreamView::resume_updates ()
651 _updates_suspended = false;
654 apply_note_range_to_regions ();
658 MidiStreamView::leave_internal_edit_mode ()
660 StreamView::leave_internal_edit_mode ();
661 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
662 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
664 mrv->clear_selection ();