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
, 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(sigc::bind(
84 sigc::mem_fun(_trackview
.editor(), &PublicEditor::canvas_stream_view_event
),
85 _note_lines
, &_trackview
));
87 _note_lines
->lower_to_bottom();
91 ColorsChanged
.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler
));
93 note_range_adjustment
.set_page_size(_highest_note
- _lowest_note
);
94 note_range_adjustment
.set_value(_lowest_note
);
96 note_range_adjustment
.signal_value_changed().connect(
97 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed
));
100 MidiStreamView::~MidiStreamView ()
105 veto_note_range(uint8_t& min
, uint8_t& max
)
107 /* Legal notes, thanks */
111 /* Always display at least one octave in [0, 127] */
113 if (min
> (127 - 11)) {
116 } else if (max
< min
+ 11) {
117 uint8_t d
= 11 - (max
- min
);
118 if (max
+ d
/2 > 127) {
125 assert(max
- min
>= 11);
131 MidiStreamView::create_region_view (boost::shared_ptr
<Region
> r
, bool /*wfd*/, bool)
133 boost::shared_ptr
<MidiRegion
> region
= boost::dynamic_pointer_cast
<MidiRegion
> (r
);
139 RegionView
* region_view
= new MidiRegionView (_canvas_group
, _trackview
, region
,
140 _samples_per_unit
, region_color
);
142 region_view
->init (region_color
, false);
148 MidiStreamView::add_region_view_internal (boost::shared_ptr
<Region
> r
, bool wfd
, bool recording
)
150 boost::shared_ptr
<MidiRegion
> region
= boost::dynamic_pointer_cast
<MidiRegion
> (r
);
156 for (list
<RegionView
*>::iterator i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
157 if ((*i
)->region() == r
) {
159 /* great. we already have a MidiRegionView for this Region. use it again. */
161 (*i
)->set_valid (true);
163 display_region(dynamic_cast<MidiRegionView
*>(*i
), wfd
);
169 MidiRegionView
* region_view
= dynamic_cast<MidiRegionView
*> (create_region_view (r
, wfd
, recording
));
170 if (region_view
== 0) {
174 region_views
.push_front (region_view
);
176 if (_trackview
.editor().internal_editing()) {
177 region_view
->hide_rect ();
179 region_view
->show_rect ();
182 /* display events and find note range */
183 display_region (region_view
, wfd
);
185 /* catch regionview going away */
186 region
->DropReferences
.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view
, this, region
), gui_context());
188 RegionViewAdded (region_view
);
194 MidiStreamView::display_region(MidiRegionView
* region_view
, bool load_model
)
200 region_view
->enable_display(true);
202 boost::shared_ptr
<MidiSource
> source(region_view
->midi_region()->midi_source(0));
205 source
->load_model();
208 _range_dirty
= update_data_note_range(
209 source
->model()->lowest_note(),
210 source
->model()->highest_note());
212 // Display region contents
213 region_view
->set_height (child_height());
214 region_view
->display_model(source
->model());
218 MidiStreamView::display_track (boost::shared_ptr
<Track
> tr
)
220 StreamView::display_track (tr
);
228 MidiStreamView::update_contents_metrics(boost::shared_ptr
<Region
> r
)
230 boost::shared_ptr
<MidiRegion
> mr
= boost::dynamic_pointer_cast
<MidiRegion
>(r
);
232 mr
->midi_source(0)->load_model();
233 _range_dirty
= update_data_note_range(
234 mr
->model()->lowest_note(),
235 mr
->model()->highest_note());
240 MidiStreamView::update_data_note_range(uint8_t min
, uint8_t max
)
243 if (min
< _data_note_min
) {
244 _data_note_min
= min
;
247 if (max
> _data_note_max
) {
248 _data_note_max
= max
;
255 MidiStreamView::redisplay_track ()
257 if (!_trackview
.is_midi_track()) {
261 list
<RegionView
*>::iterator i
;
263 // Load models if necessary, and find note range of all our contents
264 _range_dirty
= false;
265 _data_note_min
= 127;
267 _trackview
.track()->playlist()->foreach_region(
268 sigc::mem_fun (*this, &StreamView::update_contents_metrics
)
271 // No notes, use default range
277 // Extend visible range to show newly recorded data, if necessary
278 _lowest_note
= std::min(_lowest_note
, _data_note_min
);
279 _highest_note
= std::max(_highest_note
, _data_note_max
);
281 veto_note_range(_lowest_note
, _highest_note
);
283 // Flag region views as invalid and disable drawing
284 for (i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
285 (*i
)->set_valid(false);
286 (*i
)->enable_display(false);
289 // Add and display region views, and flag them as valid
290 _trackview
.track()->playlist()->foreach_region(
291 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view
))
294 // Stack regions by layer, and remove invalid regions
297 // Update note range (not regions which are correct) and draw note lines
298 apply_note_range(_lowest_note
, _highest_note
, false);
303 MidiStreamView::update_contents_height ()
305 StreamView::update_contents_height();
306 _note_lines
->property_y2() = child_height ();
308 apply_note_range (lowest_note(), highest_note(), true);
312 MidiStreamView::draw_note_lines()
314 if (!_note_lines
|| _updates_suspended
) {
319 double prev_y
= contents_height();
322 _note_lines
->clear();
324 if (child_height() < 140){
328 for (int i
= lowest_note(); i
<= highest_note(); ++i
) {
329 y
= floor(note_to_y(i
));
331 _note_lines
->add_line(prev_y
, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline
.get());
339 color
= ARDOUR_UI::config()->canvasvar_PianoRollBlack
.get();
342 color
= ARDOUR_UI::config()->canvasvar_PianoRollWhite
.get();
346 if (i
== highest_note()) {
347 _note_lines
->add_line(y
, prev_y
- y
, color
);
349 _note_lines
->add_line(y
+ 1.0, prev_y
- y
- 1.0, color
);
357 MidiStreamView::set_note_range(VisibleNoteRange r
)
359 if (r
== FullRange
) {
363 _lowest_note
= _data_note_min
;
364 _highest_note
= _data_note_max
;
367 apply_note_range(_lowest_note
, _highest_note
, true);
371 MidiStreamView::apply_note_range(uint8_t lowest
, uint8_t highest
, bool to_region_views
)
373 _highest_note
= highest
;
374 _lowest_note
= lowest
;
376 int const range
= _highest_note
- _lowest_note
;
377 int const pixels_per_note
= floor (child_height () / range
);
379 /* do not grow note height beyond 10 pixels */
380 if (pixels_per_note
> 10) {
382 int const available_note_range
= floor (child_height() / 10);
383 int additional_notes
= available_note_range
- range
;
385 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
386 for (int i
= 0; i
< additional_notes
; i
++){
388 if (i
% 2 && _highest_note
< 127){
394 else if (_lowest_note
> 0){
403 note_range_adjustment
.set_page_size(_highest_note
- _lowest_note
);
404 note_range_adjustment
.set_value(_lowest_note
);
408 if (to_region_views
) {
409 apply_note_range_to_regions ();
416 MidiStreamView::apply_note_range_to_regions ()
418 if (!_updates_suspended
) {
419 for (list
<RegionView
*>::iterator i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
420 ((MidiRegionView
*)(*i
))->apply_note_range(_lowest_note
, _highest_note
);
426 MidiStreamView::update_note_range(uint8_t note_num
)
428 assert(note_num
<= 127);
429 _data_note_min
= min(_data_note_min
, note_num
);
430 _data_note_max
= max(_data_note_max
, note_num
);
434 MidiStreamView::setup_rec_box ()
436 // cerr << _trackview.name() << " streamview SRB\n";
438 if (_trackview
.session()->transport_rolling()) {
441 _trackview
.session()->record_status() == Session::Recording
&&
442 _trackview
.track()->record_enabled()) {
444 if (Config
->get_show_waveforms_while_recording() && rec_regions
.size() == rec_rects
.size()) {
446 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
448 MidiRegion::SourceList sources
;
450 rec_data_ready_connections
.drop_connections ();
452 sources
.push_back (_trackview
.midi_track()->write_source());
456 framepos_t start
= 0;
457 if (rec_regions
.size() > 0) {
458 start
= rec_regions
.back().first
->start()
459 + _trackview
.track()->get_captured_frames(rec_regions
.size()-1);
462 if (!rec_regions
.empty()) {
463 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rec_regions
.back().second
);
469 plist
.add (ARDOUR::Properties::start
, start
);
470 plist
.add (ARDOUR::Properties::length
, 1);
471 /* Just above we're setting this nascent region's length to 1. I think this
472 is so that the RegionView gets created with a non-zero width, as apparently
473 creating a RegionView with a zero width causes it never to be displayed
474 (there is a warning in TimeAxisViewItem::init about this). However, we
475 must also set length_beats to something non-zero, otherwise the frame length
476 of 1 causes length_beats to be set to some small quantity << 1. Then
477 when the position is set up below, this length_beats is used to recompute
478 length using BeatsFramesConverter::to, which is slightly innacurate for small
479 beats values because it converts floating point beats to bars, beats and
480 integer ticks. The upshot of which being that length gets set back to 0,
481 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
483 plist
.add (ARDOUR::Properties::length_beats
, 1);
484 plist
.add (ARDOUR::Properties::name
, string());
485 plist
.add (ARDOUR::Properties::layer
, 0);
487 boost::shared_ptr
<MidiRegion
> region (boost::dynamic_pointer_cast
<MidiRegion
>
488 (RegionFactory::create (sources
, plist
, false)));
491 region
->set_start (_trackview
.track()->current_capture_start() - _trackview
.track()->get_capture_start_frame (0), this);
492 region
->set_position (_trackview
.track()->current_capture_start(), this);
493 RegionView
* rv
= add_region_view_internal (region
, false);
494 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rv
);
497 rec_regions
.push_back (make_pair (region
, rv
));
499 // rec regions are destroyed in setup_rec_box
501 /* we add the region later */
503 setup_new_rec_layer_time (region
);
506 /* start a new rec box */
508 boost::shared_ptr
<MidiTrack
> mt
= _trackview
.midi_track(); /* we know what it is already */
509 framepos_t
const frame_pos
= mt
->current_capture_start ();
510 gdouble
const xstart
= _trackview
.editor().frame_to_pixel (frame_pos
);
511 gdouble
const xend
= xstart
;
514 assert(_trackview
.midi_track()->mode() == Normal
);
516 fill_color
= ARDOUR_UI::config()->canvasvar_RecordingRect
.get();
518 ArdourCanvas::SimpleRect
* rec_rect
= new Gnome::Canvas::SimpleRect (*_canvas_group
);
519 rec_rect
->property_x1() = xstart
;
520 rec_rect
->property_y1() = 1.0;
521 rec_rect
->property_x2() = xend
;
522 rec_rect
->property_y2() = (double) _trackview
.current_height() - 1;
523 rec_rect
->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect
.get();
524 rec_rect
->property_fill_color_rgba() = fill_color
;
525 rec_rect
->lower_to_bottom();
528 recbox
.rectangle
= rec_rect
;
529 recbox
.start
= _trackview
.session()->transport_frame();
532 rec_rects
.push_back (recbox
);
534 screen_update_connection
.disconnect();
535 screen_update_connection
= ARDOUR_UI::instance()->SuperRapidScreenUpdate
.connect (
536 sigc::mem_fun (*this, &MidiStreamView::update_rec_box
));
540 } else if (rec_active
&&
541 (_trackview
.session()->record_status() != Session::Recording
||
542 !_trackview
.track()->record_enabled())) {
543 screen_update_connection
.disconnect();
545 rec_updating
= false;
550 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
552 if (!rec_rects
.empty() || !rec_regions
.empty()) {
554 /* disconnect rapid update */
555 screen_update_connection
.disconnect();
556 rec_data_ready_connections
.drop_connections ();
558 rec_updating
= false;
561 /* remove temp regions */
563 for (list
<pair
<boost::shared_ptr
<Region
>,RegionView
*> >::iterator iter
= rec_regions
.begin(); iter
!= rec_regions
.end();) {
564 list
<pair
<boost::shared_ptr
<Region
>,RegionView
*> >::iterator tmp
;
569 (*iter
).first
->drop_references ();
576 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
578 /* transport stopped, clear boxes */
579 for (vector
<RecBoxInfo
>::iterator iter
=rec_rects
.begin(); iter
!= rec_rects
.end(); ++iter
) {
580 RecBoxInfo
&rect
= (*iter
);
581 delete rect
.rectangle
;
591 MidiStreamView::color_handler ()
595 if (_trackview
.is_midi_track()) {
596 canvas_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase
.get();
598 canvas_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase
.get();;
603 MidiStreamView::note_range_adjustment_changed()
605 double sum
= note_range_adjustment
.get_value() + note_range_adjustment
.get_page_size();
606 int lowest
= (int) floor(note_range_adjustment
.get_value());
609 if (sum
== _range_sum_cache
) {
610 //cerr << "cached" << endl;
611 highest
= (int) floor(sum
);
613 //cerr << "recalc" << endl;
614 highest
= lowest
+ (int) floor(note_range_adjustment
.get_page_size());
615 _range_sum_cache
= sum
;
618 if (lowest
== _lowest_note
&& highest
== _highest_note
) {
622 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
623 //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;
625 _lowest_note
= lowest
;
626 _highest_note
= highest
;
627 apply_note_range(lowest
, highest
, true);
631 MidiStreamView::update_rec_box ()
633 StreamView::update_rec_box ();
635 if (rec_regions
.empty()) {
639 /* Update the region being recorded to reflect where we currently are */
640 boost::shared_ptr
<ARDOUR::Region
> region
= rec_regions
.back().first
;
641 region
->set_length (_trackview
.track()->current_capture_end () - _trackview
.track()->current_capture_start(), this);
643 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rec_regions
.back().second
);
644 mrv
->extend_active_notes ();
648 MidiStreamView::y_to_note (double y
) const
650 int const n
= ((contents_height() - y
- 1) / contents_height() * (double)contents_note_range())
655 } else if (n
> 127) {
662 /** Suspend updates to the regions' note ranges and our
663 * note lines until resume_updates() is called.
666 MidiStreamView::suspend_updates ()
668 _updates_suspended
= true;
671 /** Resume updates to region note ranges and note lines,
672 * and update them now.
675 MidiStreamView::resume_updates ()
677 _updates_suspended
= false;
680 apply_note_range_to_regions ();