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 if (_trackview.editor().internal_editing()) {
157 region_view->hide_rect ();
159 region_view->show_rect ();
162 /* display events and find note range */
163 display_region (region_view, wait_for_data);
165 /* fit note range if we are importing */
166 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
167 set_note_range (ContentsRange);
170 /* catch regionview going away */
171 boost::weak_ptr<Region> wr (region); // make this explicit
172 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
174 RegionViewAdded (region_view);
180 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
186 region_view->enable_display (true);
187 region_view->set_height (child_height());
189 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
191 error << _("attempt to display MIDI region with no source") << endmsg;
196 Glib::Threads::Mutex::Lock lm(source->mutex());
197 source->load_model(lm);
200 if (!source->model()) {
201 error << _("attempt to display MIDI region with no model") << endmsg;
205 _range_dirty = update_data_note_range(
206 source->model()->lowest_note(),
207 source->model()->highest_note());
209 // Display region contents
210 region_view->display_model(source->model());
215 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
217 StreamView::display_track (tr);
225 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
227 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
229 Glib::Threads::Mutex::Lock lm(mr->midi_source(0)->mutex());
230 mr->midi_source(0)->load_model(lm);
231 _range_dirty = update_data_note_range(
232 mr->model()->lowest_note(),
233 mr->model()->highest_note());
238 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
241 if (min < _data_note_min) {
242 _data_note_min = min;
245 if (max > _data_note_max) {
246 _data_note_max = max;
253 MidiStreamView::redisplay_track ()
255 if (!_trackview.is_midi_track()) {
259 list<RegionView*>::iterator i;
261 // Load models if necessary, and find note range of all our contents
262 _range_dirty = false;
263 _data_note_min = 127;
265 _trackview.track()->playlist()->foreach_region(
266 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
268 // No notes, use default range
274 // Flag region views as invalid and disable drawing
275 for (i = region_views.begin(); i != region_views.end(); ++i) {
276 (*i)->set_valid(false);
277 (*i)->enable_display(false);
280 // Add and display region views, and flag them as valid
281 _trackview.track()->playlist()->foreach_region(
282 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
284 // Stack regions by layer, and remove invalid regions
287 // Update note range (not regions which are correct) and draw note lines
288 apply_note_range(_lowest_note, _highest_note, false);
293 MidiStreamView::update_contents_height ()
295 StreamView::update_contents_height();
297 _note_lines->set_extent (ArdourCanvas::COORD_MAX);
299 apply_note_range (lowest_note(), highest_note(), true);
303 MidiStreamView::draw_note_lines()
305 if (!_note_lines || _updates_suspended) {
313 _note_lines->clear();
315 if (child_height() < 140 || note_height() < 3) {
316 /* track is too small for note lines, or there are too many */
320 /* do this is order of highest ... lowest since that matches the
321 * coordinate system in which y=0 is at the top
324 for (int i = highest_note() + 1; i >= lowest_note(); --i) {
326 y = floor(note_to_y (i)) + .5;
328 /* this is the line actually corresponding to the division
332 if (i <= highest_note()) {
333 _note_lines->add (y, 1.0, ARDOUR_UI::config()->color ("piano roll black outline"));
336 /* now add a thicker line/bar which covers the entire vertical
337 * height of this note.
346 color = ARDOUR_UI::config()->color_mod ("piano roll black", "piano roll black");
349 color = ARDOUR_UI::config()->color_mod ("piano roll white", "piano roll white");
353 double h = y - prev_y;
354 double mid = y + (h/2.0);
356 if (height > 1.0) { // XXX ? should that not be h >= 1 ?
357 _note_lines->add (mid, h, color);
365 MidiStreamView::set_note_range(VisibleNoteRange r)
367 if (r == FullRange) {
371 _lowest_note = _data_note_min;
372 _highest_note = _data_note_max;
375 apply_note_range(_lowest_note, _highest_note, true);
379 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
381 _highest_note = highest;
382 _lowest_note = lowest;
384 int const max_note_height = 20; // This should probably be based on text size...
385 int const range = _highest_note - _lowest_note;
386 int const pixels_per_note = floor (child_height () / range);
388 /* do not grow note height beyond 10 pixels */
389 if (pixels_per_note > max_note_height) {
391 int const available_note_range = floor (child_height() / max_note_height);
392 int additional_notes = available_note_range - range;
394 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
395 for (int i = 0; i < additional_notes; i++){
397 if (i % 2 && _highest_note < 127){
403 else if (_lowest_note > 0){
412 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
413 note_range_adjustment.set_value(_lowest_note);
417 if (to_region_views) {
418 apply_note_range_to_regions ();
425 MidiStreamView::apply_note_range_to_regions ()
427 if (!_updates_suspended) {
428 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
429 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
435 MidiStreamView::update_note_range(uint8_t note_num)
437 _data_note_min = min(_data_note_min, note_num);
438 _data_note_max = max(_data_note_max, note_num);
442 MidiStreamView::setup_rec_box ()
444 // cerr << _trackview.name() << " streamview SRB\n";
446 if (_trackview.session()->transport_rolling()) {
449 _trackview.session()->record_status() == Session::Recording &&
450 _trackview.track()->record_enabled()) {
452 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
454 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
456 MidiRegion::SourceList sources;
458 rec_data_ready_connections.drop_connections ();
460 sources.push_back (_trackview.midi_track()->write_source());
464 framepos_t start = 0;
465 if (rec_regions.size() > 0) {
466 start = rec_regions.back().first->start()
467 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
470 if (!rec_regions.empty()) {
471 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
477 plist.add (ARDOUR::Properties::start, start);
478 plist.add (ARDOUR::Properties::length, 1);
479 /* Just above we're setting this nascent region's length to 1. I think this
480 is so that the RegionView gets created with a non-zero width, as apparently
481 creating a RegionView with a zero width causes it never to be displayed
482 (there is a warning in TimeAxisViewItem::init about this). However, we
483 must also set length_beats to something non-zero, otherwise the frame length
484 of 1 causes length_beats to be set to some small quantity << 1. Then
485 when the position is set up below, this length_beats is used to recompute
486 length using BeatsFramesConverter::to, which is slightly innacurate for small
487 beats values because it converts floating point beats to bars, beats and
488 integer ticks. The upshot of which being that length gets set back to 0,
489 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
491 plist.add (ARDOUR::Properties::length_beats, 1);
492 plist.add (ARDOUR::Properties::name, string());
493 plist.add (ARDOUR::Properties::layer, 0);
495 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
496 (RegionFactory::create (sources, plist, false)));
498 region->set_start (_trackview.track()->current_capture_start()
499 - _trackview.track()->get_capture_start_frame (0));
500 region->set_position (_trackview.track()->current_capture_start());
501 RegionView* rv = add_region_view_internal (region, false, true);
502 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
506 /* rec region will be destroyed in setup_rec_box */
507 rec_regions.push_back (make_pair (region, rv));
509 /* we add the region later */
510 setup_new_rec_layer_time (region);
512 error << _("failed to create MIDI region") << endmsg;
516 /* start a new rec box */
518 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
519 framepos_t const frame_pos = mt->current_capture_start ();
520 gdouble const xstart = _trackview.editor().sample_to_pixel (frame_pos);
521 gdouble const xend = xstart;
524 fill_color = ARDOUR_UI::config()->color ("recording rect");
526 ArdourCanvas::Rectangle * rec_rect = new ArdourCanvas::Rectangle (_canvas_group);
527 rec_rect->set (ArdourCanvas::Rect (xstart, 1, xend, _trackview.current_height() - 1));
528 rec_rect->set_outline_color (ARDOUR_UI::config()->color ("recording rect"));
529 rec_rect->set_fill_color (fill_color);
530 rec_rect->lower_to_bottom();
533 recbox.rectangle = rec_rect;
534 recbox.start = _trackview.session()->transport_frame();
537 rec_rects.push_back (recbox);
539 screen_update_connection.disconnect();
540 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
541 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
545 } else if (rec_active &&
546 (_trackview.session()->record_status() != Session::Recording ||
547 !_trackview.track()->record_enabled())) {
548 screen_update_connection.disconnect();
550 rec_updating = false;
555 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
557 if (!rec_rects.empty() || !rec_regions.empty()) {
559 /* disconnect rapid update */
560 screen_update_connection.disconnect();
561 rec_data_ready_connections.drop_connections ();
563 rec_updating = false;
566 /* remove temp regions */
568 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
569 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
574 (*iter).first->drop_references ();
581 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
583 /* transport stopped, clear boxes */
584 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
585 RecBoxInfo &rect = (*iter);
586 delete rect.rectangle;
596 MidiStreamView::color_handler ()
600 if (_trackview.is_midi_track()) {
601 canvas_rect->set_fill_color (ARDOUR_UI::config()->color_mod ("midi track base", "midi track base"));
603 canvas_rect->set_fill_color (ARDOUR_UI::config()->color ("midi bus base"));
608 MidiStreamView::note_range_adjustment_changed()
610 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
611 int lowest = (int) floor(note_range_adjustment.get_value());
614 if (sum == _range_sum_cache) {
615 //cerr << "cached" << endl;
616 highest = (int) floor(sum);
618 //cerr << "recalc" << endl;
619 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
620 _range_sum_cache = sum;
623 if (lowest == _lowest_note && highest == _highest_note) {
627 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
628 //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;
630 _lowest_note = lowest;
631 _highest_note = highest;
632 apply_note_range(lowest, highest, true);
636 MidiStreamView::update_rec_box ()
638 StreamView::update_rec_box ();
640 if (rec_regions.empty()) {
644 /* Update the region being recorded to reflect where we currently are */
645 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
646 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
648 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
649 mrv->extend_active_notes ();
653 MidiStreamView::y_to_note (double y) const
655 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
660 } else if (n > 127) {
667 /** Suspend updates to the regions' note ranges and our
668 * note lines until resume_updates() is called.
671 MidiStreamView::suspend_updates ()
673 _updates_suspended = true;
676 /** Resume updates to region note ranges and note lines,
677 * and update them now.
680 MidiStreamView::resume_updates ()
682 _updates_suspended = false;
685 apply_note_range_to_regions ();
687 _canvas_group->redraw ();
691 MidiStreamView::leave_internal_edit_mode ()
693 StreamView::leave_internal_edit_mode ();
694 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
695 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
697 mrv->clear_selection ();