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_diskstream.h"
28 #include "ardour/midi_playlist.h"
29 #include "ardour/midi_region.h"
30 #include "ardour/midi_source.h"
31 #include "ardour/midi_track.h"
32 #include "ardour/region_factory.h"
33 #include "ardour/smf_source.h"
34 #include "ardour/session.h"
36 #include "ardour_ui.h"
37 #include "canvas-simplerect.h"
38 #include "global_signals.h"
39 #include "gui_thread.h"
41 #include "midi_region_view.h"
42 #include "midi_streamview.h"
43 #include "midi_time_axis.h"
44 #include "midi_util.h"
45 #include "public_editor.h"
46 #include "region_selection.h"
47 #include "region_view.h"
48 #include "rgb_macros.h"
49 #include "selection.h"
50 #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 /* catch regionview going away */
162 region->DropReferences.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view, this, region), gui_context());
164 RegionViewAdded (region_view);
170 MidiStreamView::display_region(MidiRegionView* region_view, bool load_model)
176 region_view->enable_display(true);
178 boost::shared_ptr<MidiSource> source(region_view->midi_region()->midi_source(0));
181 source->load_model();
184 _range_dirty = update_data_note_range(
185 source->model()->lowest_note(),
186 source->model()->highest_note());
188 // Display region contents
189 region_view->set_height (child_height());
190 region_view->display_model(source->model());
194 MidiStreamView::display_track (boost::shared_ptr<Track> tr)
196 StreamView::display_track (tr);
204 MidiStreamView::update_contents_metrics(boost::shared_ptr<Region> r)
206 boost::shared_ptr<MidiRegion> mr = boost::dynamic_pointer_cast<MidiRegion>(r);
208 mr->midi_source(0)->load_model();
209 _range_dirty = update_data_note_range(
210 mr->model()->lowest_note(),
211 mr->model()->highest_note());
216 MidiStreamView::update_data_note_range(uint8_t min, uint8_t max)
219 if (min < _data_note_min) {
220 _data_note_min = min;
223 if (max > _data_note_max) {
224 _data_note_max = max;
231 MidiStreamView::redisplay_track ()
233 if (!_trackview.is_midi_track()) {
237 list<RegionView*>::iterator i;
239 // Load models if necessary, and find note range of all our contents
240 _range_dirty = false;
241 _data_note_min = 127;
243 _trackview.track()->playlist()->foreach_region(
244 sigc::mem_fun (*this, &StreamView::update_contents_metrics));
246 // No notes, use default range
252 // Flag region views as invalid and disable drawing
253 for (i = region_views.begin(); i != region_views.end(); ++i) {
254 (*i)->set_valid(false);
255 (*i)->enable_display(false);
258 // Add and display region views, and flag them as valid
259 _trackview.track()->playlist()->foreach_region(
260 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view)));
262 // Stack regions by layer, and remove invalid regions
265 // Update note range (not regions which are correct) and draw note lines
266 apply_note_range(_lowest_note, _highest_note, false);
271 MidiStreamView::update_contents_height ()
273 StreamView::update_contents_height();
274 _note_lines->property_y2() = child_height ();
276 apply_note_range (lowest_note(), highest_note(), true);
280 MidiStreamView::draw_note_lines()
282 if (!_note_lines || _updates_suspended) {
287 double prev_y = contents_height();
290 _note_lines->clear();
292 if (child_height() < 140 || note_height() < 3) {
293 /* track is too small for note lines, or there are too many */
297 for (int i = lowest_note(); i <= highest_note(); ++i) {
298 y = floor(note_to_y(i));
300 _note_lines->add_line(prev_y, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline.get());
308 color = ARDOUR_UI::config()->canvasvar_PianoRollBlack.get();
311 color = ARDOUR_UI::config()->canvasvar_PianoRollWhite.get();
315 if (i == highest_note()) {
316 _note_lines->add_line(y, prev_y - y, color);
318 _note_lines->add_line(y + 1.0, prev_y - y - 1.0, color);
326 MidiStreamView::set_note_range(VisibleNoteRange r)
328 if (r == FullRange) {
332 _lowest_note = _data_note_min;
333 _highest_note = _data_note_max;
336 apply_note_range(_lowest_note, _highest_note, true);
340 MidiStreamView::apply_note_range(uint8_t lowest, uint8_t highest, bool to_region_views)
342 _highest_note = highest;
343 _lowest_note = lowest;
345 int const max_note_height = 20; // This should probably be based on text size...
346 int const range = _highest_note - _lowest_note;
347 int const pixels_per_note = floor (child_height () / range);
349 /* do not grow note height beyond 10 pixels */
350 if (pixels_per_note > max_note_height) {
352 int const available_note_range = floor (child_height() / max_note_height);
353 int additional_notes = available_note_range - range;
355 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
356 for (int i = 0; i < additional_notes; i++){
358 if (i % 2 && _highest_note < 127){
364 else if (_lowest_note > 0){
373 note_range_adjustment.set_page_size(_highest_note - _lowest_note);
374 note_range_adjustment.set_value(_lowest_note);
378 if (to_region_views) {
379 apply_note_range_to_regions ();
386 MidiStreamView::apply_note_range_to_regions ()
388 if (!_updates_suspended) {
389 for (list<RegionView*>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
390 ((MidiRegionView*)(*i))->apply_note_range(_lowest_note, _highest_note);
396 MidiStreamView::update_note_range(uint8_t note_num)
398 assert(note_num <= 127);
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)));
461 region->set_start (_trackview.track()->current_capture_start() - _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);
467 rec_regions.push_back (make_pair (region, rv));
469 // rec regions are destroyed in setup_rec_box
471 /* we add the region later */
473 setup_new_rec_layer_time (region);
476 /* start a new rec box */
478 boost::shared_ptr<MidiTrack> mt = _trackview.midi_track(); /* we know what it is already */
479 framepos_t const frame_pos = mt->current_capture_start ();
480 gdouble const xstart = _trackview.editor().frame_to_pixel (frame_pos);
481 gdouble const xend = xstart;
484 assert(_trackview.midi_track()->mode() == Normal);
486 fill_color = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
488 ArdourCanvas::SimpleRect * rec_rect = new Gnome::Canvas::SimpleRect (*_canvas_group);
489 rec_rect->property_x1() = xstart;
490 rec_rect->property_y1() = 1.0;
491 rec_rect->property_x2() = xend;
492 rec_rect->property_y2() = (double) _trackview.current_height() - 1;
493 rec_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect.get();
494 rec_rect->property_fill_color_rgba() = fill_color;
495 rec_rect->lower_to_bottom();
498 recbox.rectangle = rec_rect;
499 recbox.start = _trackview.session()->transport_frame();
502 rec_rects.push_back (recbox);
504 screen_update_connection.disconnect();
505 screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
506 sigc::mem_fun (*this, &MidiStreamView::update_rec_box));
510 } else if (rec_active &&
511 (_trackview.session()->record_status() != Session::Recording ||
512 !_trackview.track()->record_enabled())) {
513 screen_update_connection.disconnect();
515 rec_updating = false;
520 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
522 if (!rec_rects.empty() || !rec_regions.empty()) {
524 /* disconnect rapid update */
525 screen_update_connection.disconnect();
526 rec_data_ready_connections.drop_connections ();
528 rec_updating = false;
531 /* remove temp regions */
533 for (list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator iter = rec_regions.begin(); iter != rec_regions.end();) {
534 list<pair<boost::shared_ptr<Region>,RegionView*> >::iterator tmp;
539 (*iter).first->drop_references ();
546 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
548 /* transport stopped, clear boxes */
549 for (vector<RecBoxInfo>::iterator iter=rec_rects.begin(); iter != rec_rects.end(); ++iter) {
550 RecBoxInfo &rect = (*iter);
551 delete rect.rectangle;
561 MidiStreamView::color_handler ()
565 if (_trackview.is_midi_track()) {
566 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase.get();
568 canvas_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase.get();;
573 MidiStreamView::note_range_adjustment_changed()
575 double sum = note_range_adjustment.get_value() + note_range_adjustment.get_page_size();
576 int lowest = (int) floor(note_range_adjustment.get_value());
579 if (sum == _range_sum_cache) {
580 //cerr << "cached" << endl;
581 highest = (int) floor(sum);
583 //cerr << "recalc" << endl;
584 highest = lowest + (int) floor(note_range_adjustment.get_page_size());
585 _range_sum_cache = sum;
588 if (lowest == _lowest_note && highest == _highest_note) {
592 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
593 //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;
595 _lowest_note = lowest;
596 _highest_note = highest;
597 apply_note_range(lowest, highest, true);
601 MidiStreamView::update_rec_box ()
603 StreamView::update_rec_box ();
605 if (rec_regions.empty()) {
609 /* Update the region being recorded to reflect where we currently are */
610 boost::shared_ptr<ARDOUR::Region> region = rec_regions.back().first;
611 region->set_length (_trackview.track()->current_capture_end () - _trackview.track()->current_capture_start());
613 MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (rec_regions.back().second);
614 mrv->extend_active_notes ();
618 MidiStreamView::y_to_note (double y) const
620 int const n = ((contents_height() - y - 1) / contents_height() * (double)contents_note_range())
625 } else if (n > 127) {
632 /** Suspend updates to the regions' note ranges and our
633 * note lines until resume_updates() is called.
636 MidiStreamView::suspend_updates ()
638 _updates_suspended = true;
641 /** Resume updates to region note ranges and note lines,
642 * and update them now.
645 MidiStreamView::resume_updates ()
647 _updates_suspended = false;
650 apply_note_range_to_regions ();