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 "ardour/midi_region.h"
27 #include "ardour/midi_source.h"
28 #include "ardour/midi_track.h"
29 #include "ardour/operations.h"
30 #include "ardour/region_factory.h"
31 #include "ardour/session.h"
32 #include "ardour/smf_source.h"
34 #include "ardour_ui.h"
35 #include "canvas-simplerect.h"
36 #include "global_signals.h"
37 #include "gui_thread.h"
39 #include "midi_region_view.h"
40 #include "midi_streamview.h"
41 #include "midi_time_axis.h"
42 #include "midi_util.h"
43 #include "public_editor.h"
44 #include "region_selection.h"
45 #include "region_view.h"
46 #include "rgb_macros.h"
47 #include "selection.h"
48 #include "simplerect.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*/
76 _note_lines = new ArdourCanvas::LineSet(*_canvas_group,
77 ArdourCanvas::LineSet::Horizontal);
79 _note_lines->property_x1() = 0;
80 _note_lines->property_y1() = 0;
81 _note_lines->property_x2() = DBL_MAX;
82 _note_lines->property_y2() = 0;
84 _note_lines->signal_event().connect(
85 sigc::bind(sigc::mem_fun(_trackview.editor(),
86 &PublicEditor::canvas_stream_view_event),
87 _note_lines, &_trackview));
89 _note_lines->lower_to_bottom();
93 ColorsChanged.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler));
95 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
96 note_range_adjustment.set_value(_lowest_note);
98 note_range_adjustment.signal_value_changed().connect(
99 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed));
102 MidiStreamView::~MidiStreamView ()
107 MidiStreamView::create_region_view (boost::shared_ptr<Region> r, bool /*wfd*/, bool)
109 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
115 RegionView* region_view = new MidiRegionView (_canvas_group, _trackview, region,
116 _samples_per_unit, region_color);
118 region_view->init (region_color, false);
124 MidiStreamView::add_region_view_internal (boost::shared_ptr<Region> r, bool wfd, bool recording)
126 boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion> (r);
132 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
133 if ((*i)->region() == r) {
135 /* great. we already have a MidiRegionView for this Region. use it again. */
137 (*i)->set_valid (true);
139 display_region(dynamic_cast<MidiRegionView*>(*i), wfd);
145 MidiRegionView* region_view = dynamic_cast<MidiRegionView*> (create_region_view (r, wfd, recording));
146 if (region_view == 0) {
150 region_views.push_front (region_view);
152 if (_trackview.editor().internal_editing()) {
153 region_view->hide_rect ();
155 region_view->show_rect ();
158 /* display events and find note range */
159 display_region (region_view, wfd);
161 /* fit note range if we are importing */
162 if (_trackview.session()->operation_in_progress (Operations::insert_file)) {
163 set_note_range (ContentsRange);
166 /* catch regionview going away */
167 boost::weak_ptr<Region> wr (region); // make this explicit
168 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, wr), gui_context());
170 RegionViewAdded (region_view);
176 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
182 region_view->enable_display(true);
184 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
187 source->load_model();
190 _range_dirty = update_data_note_range(
191 source->model()->lowest_note(),
192 source->model()->highest_note());
194 // Display region contents
195 region_view->set_height (child_height());
196 region_view->display_model(source->model());
200 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
202 StreamView::display_track (tr);
210 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
212 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
214 mr->midi_source(0)->load_model();
215 _range_dirty = update_data_note_range(
216 mr->model()->lowest_note(),
217 mr->model()->highest_note());
222 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
225 if (min < _data_note_min) {
226 _data_note_min = min;
229 if (max > _data_note_max) {
230 _data_note_max = max;
237 MidiStreamView::redisplay_track ()
239 if (!_trackview.is_midi_track()) {
243 list<RegionView*>::iterator i;
245 // Load models if necessary, and find note range of all our contents
246 _range_dirty = false;
247 _data_note_min = 127;
249 _trackview.track()->playlist()->foreach_region(
250 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
252 // No notes, use default range
258 // Flag region views as invalid and disable drawing
259 for (i = region_views.begin(); i != region_views.end(); ++i) {
260 (*i)->set_valid(false);
261 (*i)->enable_display(false);
264 // Add and display region views, and flag them as valid
265 _trackview.track()->playlist()->foreach_region(
266 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
268 // Stack regions by layer, and remove invalid regions
271 // Update note range (not regions which are correct) and draw note lines
272 apply_note_range(_lowest_note, _highest_note, false);
277 MidiStreamView::update_contents_height ()
279 StreamView::update_contents_height();
280 _note_lines->property_y2() = child_height ();
282 apply_note_range (lowest_note(), highest_note(), true);
286 MidiStreamView::draw_note_lines()
288 if (!_note_lines || _updates_suspended) {
293 double prev_y = contents_height();
296 _note_lines->clear();
298 if (child_height() < 140 || note_height() < 3) {
299 /* track is too small for note lines, or there are too many */
303 for (int i = lowest_note(); i <= highest_note(); ++i) {
304 y = floor(note_to_y(i));
306 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
314 color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
317 color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
321 if (i == highest_note()) {
322 _note_lines->add_line(y, prev_y - y, color);
324 _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
332 MidiStreamView::set_note_range(VisibleNoteRange r)
334 if (r == FullRange) {
338 _lowest_note = _data_note_min;
339 _highest_note = _data_note_max;
342 apply_note_range(_lowest_note, _highest_note, true);
346 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
348 _highest_note = highest;
349 _lowest_note = lowest;
351 int const max_note_height = 20; // This should probably be based on text size...
352 int const range = _highest_note - _lowest_note;
353 int const pixels_per_note = floor (child_height () / range);
355 /* do not grow note height beyond 10 pixels */
356 if (pixels_per_note > max_note_height) {
358 int const available_note_range = floor (child_height() / max_note_height);
359 int additional_notes = available_note_range - range;
361 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
362 for (int i = 0; i < additional_notes; i++){
364 if (i % 2 && _highest_note < 127){
370 else if (_lowest_note > 0){
379 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
380 note_range_adjustment.set_value(_lowest_note);
384 if (to_region_views) {
385 apply_note_range_to_regions ();
392 MidiStreamView::apply_note_range_to_regions ()
394 if (!_updates_suspended) {
395 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
396 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
402 MidiStreamView::update_note_range(uint8_t note_num)
404 _data_note_min = min(_data_note_min, note_num);
405 _data_note_max = max(_data_note_max, note_num);
409 MidiStreamView::setup_rec_box ()
411 // cerr << _trackview.name() << " streamview SRB\n";
413 if (_trackview.session()->transport_rolling()) {
416 _trackview.session()->record_status() == Session::Recording &&
417 _trackview.track()->record_enabled()) {
419 if (Config->get_show_waveforms_while_recording() && rec_regions.size() == rec_rects.size()) {
421 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
423 MidiRegion::SourceList sources;
425 rec_data_ready_connections.drop_connections ();
427 sources.push_back (_trackview.midi_track()->write_source());
431 framepos_t start = 0;
432 if (rec_regions.size() > 0) {
433 start = rec_regions.back().first->start()
434 + _trackview.track()->get_captured_frames(rec_regions.size()-1);
437 if (!rec_regions.empty()) {
438 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
444 plist.add (ARDOUR::Properties::start, start);
445 plist.add (ARDOUR::Properties::length, 1);
446 /* Just above we're setting this nascent region's length to 1. I think this
447 is so that the RegionView gets created with a non-zero width, as apparently
448 creating a RegionView with a zero width causes it never to be displayed
449 (there is a warning in TimeAxisViewItem::init about this). However, we
450 must also set length_beats to something non-zero, otherwise the frame length
451 of 1 causes length_beats to be set to some small quantity << 1. Then
452 when the position is set up below, this length_beats is used to recompute
453 length using BeatsFramesConverter::to, which is slightly innacurate for small
454 beats values because it converts floating point beats to bars, beats and
455 integer ticks. The upshot of which being that length gets set back to 0,
456 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
458 plist.add (ARDOUR::Properties::length_beats, 1);
459 plist.add (ARDOUR::Properties::name, string());
460 plist.add (ARDOUR::Properties::layer, 0);
462 boost::shared_ptr<MidiRegion> region (boost::dynamic_pointer_cast<MidiRegion>
463 (RegionFactory::create (sources, plist, false)));
465 region->set_start (_trackview.track()->current_capture_start()
466 - _trackview.track()->get_capture_start_frame (0));
467 region->set_position (_trackview.track()->current_capture_start());
468 RegionView* rv = add_region_view_internal (region, false);
469 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rv);
473 /* rec region will be destroyed in setup_rec_box */
474 rec_regions.push_back (make_pair (region, rv));
476 /* we add the region later */
477 setup_new_rec_layer_time (region);
479 error << _("failed to create MIDI region") << endmsg;
483 /* start a new rec box */
485 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
486 framepos_t const frame_pos = mt->current_capture_start ();
487 gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
488 gdouble const xend = xstart;
491 fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
493 ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
494 rec_rect->property_x1() = xstart;
495 rec_rect->property_y1() = 1.0;
496 rec_rect->property_x2() = xend;
497 rec_rect->property_y2() = (double) _trackview.current_height() - 1;
498 rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
499 rec_rect->property_fill_color_rgba() = fill_color;
500 rec_rect->lower_to_bottom();
503 recbox.rectangle = rec_rect;
504 recbox.start = _trackview.session()->transport_frame();
507 rec_rects.push_back (recbox);
509 screen_update_connection.disconnect();
510 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
511 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
515 } else if (rec_active &&
516 (_trackview.session()->record_status() != Session::Recording ||
517 !_trackview.track()->record_enabled())) {
518 screen_update_connection.disconnect();
520 rec_updating = false;
525 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
527 if (!rec_rects.empty() || !rec_regions.empty()) {
529 /* disconnect rapid update */
530 screen_update_connection.disconnect();
531 rec_data_ready_connections.drop_connections ();
533 rec_updating = false;
536 /* remove temp regions */
538 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
539 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
544 (*iter).first->drop_references ();
551 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
553 /* transport stopped, clear boxes */
554 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
555 RecBoxInfo &rect = (*iter);
556 delete rect.rectangle;
566 MidiStreamView::color_handler ()
570 if (_trackview.is_midi_track()) {
571 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
573 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
578 MidiStreamView::note_range_adjustment_changed()
580 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
581 int lowest = (int) floor(note_range_adjustment.get_value());
584 if (sum == _range_sum_cache) {
585 //cerr << "cached" << endl;
586 highest = (int) floor(sum);
588 //cerr << "recalc" << endl;
589 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
590 _range_sum_cache = sum;
593 if (lowest == _lowest_note && highest == _highest_note) {
597 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
598 //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;
600 _lowest_note = lowest;
601 _highest_note = highest;
602 apply_note_range(lowest, highest, true);
606 MidiStreamView::update_rec_box ()
608 StreamView::update_rec_box ();
610 if (rec_regions.empty()) {
614 /* Update the region being recorded to reflect where we currently are */
615 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
616 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
618 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
619 mrv->extend_active_notes ();
623 MidiStreamView::y_to_note (double y) const
625 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
630 } else if (n > 127) {
637 /** Suspend updates to the regions' note ranges and our
638 * note lines until resume_updates() is called.
641 MidiStreamView::suspend_updates ()
643 _updates_suspended = true;
646 /** Resume updates to region note ranges and note lines,
647 * and update them now.
650 MidiStreamView::resume_updates ()
652 _updates_suspended = false;
655 apply_note_range_to_regions ();
659 MidiStreamView::leave_internal_edit_mode ()
661 StreamView::leave_internal_edit_mode ();
662 for (RegionViewList::iterator i = region_views.begin(); i != region_views.end(); ++i) {
663 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*i);
665 mrv->clear_selection ();