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)
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
, ArdourCanvas::LineSet::Horizontal
);
77 _note_lines
->property_x1() = 0;
78 _note_lines
->property_y1() = 0;
79 _note_lines
->property_x2() = DBL_MAX
;
80 _note_lines
->property_y2() = 0;
82 _note_lines
->signal_event().connect(sigc::bind(
83 sigc::mem_fun(_trackview
.editor(), &PublicEditor::canvas_stream_view_event
),
84 _note_lines
, &_trackview
));
86 _note_lines
->lower_to_bottom();
90 ColorsChanged
.connect(sigc::mem_fun(*this, &MidiStreamView::color_handler
));
92 note_range_adjustment
.set_page_size(_highest_note
- _lowest_note
);
93 note_range_adjustment
.set_value(_lowest_note
);
95 note_range_adjustment
.signal_value_changed().connect(
96 sigc::mem_fun(*this, &MidiStreamView::note_range_adjustment_changed
));
99 MidiStreamView::~MidiStreamView ()
104 veto_note_range(uint8_t& min
, uint8_t& max
)
106 /* Legal notes, thanks */
110 /* Always display at least one octave in [0, 127] */
112 if (min
> (127 - 11)) {
115 } else if (max
< min
+ 11) {
116 uint8_t d
= 11 - (max
- min
);
117 if (max
+ d
/2 > 127) {
124 assert(max
- min
>= 11);
130 MidiStreamView::create_region_view (boost::shared_ptr
<Region
> r
, bool /*wfd*/, bool)
132 boost::shared_ptr
<MidiRegion
> region
= boost::dynamic_pointer_cast
<MidiRegion
> (r
);
138 RegionView
* region_view
= new MidiRegionView (_canvas_group
, _trackview
, region
,
139 _samples_per_unit
, region_color
);
141 region_view
->init (region_color
, false);
147 MidiStreamView::add_region_view_internal (boost::shared_ptr
<Region
> r
, bool wfd
, bool recording
)
149 boost::shared_ptr
<MidiRegion
> region
= boost::dynamic_pointer_cast
<MidiRegion
> (r
);
155 for (list
<RegionView
*>::iterator i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
156 if ((*i
)->region() == r
) {
158 /* great. we already have a MidiRegionView for this Region. use it again. */
160 (*i
)->set_valid (true);
162 display_region(dynamic_cast<MidiRegionView
*>(*i
), wfd
);
168 MidiRegionView
* region_view
= dynamic_cast<MidiRegionView
*> (create_region_view (r
, wfd
, recording
));
169 if (region_view
== 0) {
173 region_views
.push_front (region_view
);
175 if (_trackview
.editor().internal_editing()) {
176 region_view
->hide_rect ();
178 region_view
->show_rect ();
181 /* display events and find note range */
182 display_region (region_view
, wfd
);
184 /* catch regionview going away */
185 region
->DropReferences
.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view
, this, region
), gui_context());
187 RegionViewAdded (region_view
);
193 MidiStreamView::display_region(MidiRegionView
* region_view
, bool load_model
)
199 region_view
->enable_display(true);
201 boost::shared_ptr
<MidiSource
> source(region_view
->midi_region()->midi_source(0));
204 source
->load_model();
207 _range_dirty
= update_data_note_range(
208 source
->model()->lowest_note(),
209 source
->model()->highest_note());
211 // Display region contents
212 region_view
->set_height (child_height());
213 region_view
->display_model(source
->model());
217 MidiStreamView::display_track (boost::shared_ptr
<Track
> tr
)
219 StreamView::display_track (tr
);
227 MidiStreamView::update_contents_metrics(boost::shared_ptr
<Region
> r
)
229 boost::shared_ptr
<MidiRegion
> mr
= boost::dynamic_pointer_cast
<MidiRegion
>(r
);
231 mr
->midi_source(0)->load_model();
232 _range_dirty
= update_data_note_range(
233 mr
->model()->lowest_note(),
234 mr
->model()->highest_note());
239 MidiStreamView::update_data_note_range(uint8_t min
, uint8_t max
)
242 if (min
< _data_note_min
) {
243 _data_note_min
= min
;
246 if (max
> _data_note_max
) {
247 _data_note_max
= max
;
254 MidiStreamView::redisplay_track ()
256 if (!_trackview
.is_midi_track()) {
260 list
<RegionView
*>::iterator i
;
262 // Load models if necessary, and find note range of all our contents
263 _range_dirty
= false;
264 _data_note_min
= 127;
266 _trackview
.track()->playlist()->foreach_region(
267 sigc::mem_fun (*this, &StreamView::update_contents_metrics
)
270 // No notes, use default range
276 // Extend visible range to show newly recorded data, if necessary
277 _lowest_note
= std::min(_lowest_note
, _data_note_min
);
278 _highest_note
= std::max(_highest_note
, _data_note_max
);
280 veto_note_range(_lowest_note
, _highest_note
);
282 // Flag region views as invalid and disable drawing
283 for (i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
284 (*i
)->set_valid(false);
285 (*i
)->enable_display(false);
288 // Add and display region views, and flag them as valid
289 _trackview
.track()->playlist()->foreach_region(
290 sigc::hide_return (sigc::mem_fun (*this, &StreamView::add_region_view
))
293 // Stack regions by layer, and remove invalid regions
296 // Update note range (not regions which are correct) and draw note lines
297 apply_note_range(_lowest_note
, _highest_note
, false);
302 MidiStreamView::update_contents_height ()
304 StreamView::update_contents_height();
305 _note_lines
->property_y2() = child_height ();
307 apply_note_range (lowest_note(), highest_note(), true);
311 MidiStreamView::draw_note_lines()
318 double prev_y
= contents_height();
321 _note_lines
->clear();
323 if (child_height() < 140){
327 for (int i
= lowest_note(); i
<= highest_note(); ++i
) {
328 y
= floor(note_to_y(i
));
330 _note_lines
->add_line(prev_y
, 1.0, ARDOUR_UI::config()->canvasvar_PianoRollBlackOutline
.get());
338 color
= ARDOUR_UI::config()->canvasvar_PianoRollBlack
.get();
341 color
= ARDOUR_UI::config()->canvasvar_PianoRollWhite
.get();
345 if (i
== highest_note()) {
346 _note_lines
->add_line(y
, prev_y
- y
, color
);
348 _note_lines
->add_line(y
+ 1.0, prev_y
- y
- 1.0, color
);
356 MidiStreamView::set_note_range(VisibleNoteRange r
)
358 if (r
== FullRange
) {
362 _lowest_note
= _data_note_min
;
363 _highest_note
= _data_note_max
;
366 apply_note_range(_lowest_note
, _highest_note
, true);
370 MidiStreamView::apply_note_range(uint8_t lowest
, uint8_t highest
, bool to_region_views
)
372 _highest_note
= highest
;
373 _lowest_note
= lowest
;
375 int const range
= _highest_note
- _lowest_note
;
376 int const pixels_per_note
= floor (child_height () / range
);
378 /* do not grow note height beyond 10 pixels */
379 if (pixels_per_note
> 10) {
381 int const available_note_range
= floor (child_height() / 10);
382 int additional_notes
= available_note_range
- range
;
384 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
385 for (int i
= 0; i
< additional_notes
; i
++){
387 if (i
% 2 && _highest_note
< 127){
393 else if (_lowest_note
> 0){
402 note_range_adjustment
.set_page_size(_highest_note
- _lowest_note
);
403 note_range_adjustment
.set_value(_lowest_note
);
407 if (to_region_views
) {
408 for (list
<RegionView
*>::iterator i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
409 ((MidiRegionView
*)(*i
))->apply_note_range(_lowest_note
, _highest_note
);
417 MidiStreamView::update_note_range(uint8_t note_num
)
419 assert(note_num
<= 127);
420 _data_note_min
= min(_data_note_min
, note_num
);
421 _data_note_max
= max(_data_note_max
, note_num
);
425 MidiStreamView::setup_rec_box ()
427 // cerr << _trackview.name() << " streamview SRB\n";
429 if (_trackview
.session()->transport_rolling()) {
432 _trackview
.session()->record_status() == Session::Recording
&&
433 _trackview
.track()->record_enabled()) {
435 if (Config
->get_show_waveforms_while_recording() && rec_regions
.size() == rec_rects
.size()) {
437 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
439 MidiRegion::SourceList sources
;
441 rec_data_ready_connections
.drop_connections ();
443 sources
.push_back (_trackview
.midi_track()->write_source());
447 framepos_t start
= 0;
448 if (rec_regions
.size() > 0) {
449 start
= rec_regions
.back().first
->start()
450 + _trackview
.track()->get_captured_frames(rec_regions
.size()-1);
453 if (!rec_regions
.empty()) {
454 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rec_regions
.back().second
);
460 plist
.add (ARDOUR::Properties::start
, start
);
461 plist
.add (ARDOUR::Properties::length
, 1);
462 /* Just above we're setting this nascent region's length to 1. I think this
463 is so that the RegionView gets created with a non-zero width, as apparently
464 creating a RegionView with a zero width causes it never to be displayed
465 (there is a warning in TimeAxisViewItem::init about this). However, we
466 must also set length_beats to something non-zero, otherwise the frame length
467 of 1 causes length_beats to be set to some small quantity << 1. Then
468 when the position is set up below, this length_beats is used to recompute
469 length using BeatsFramesConverter::to, which is slightly innacurate for small
470 beats values because it converts floating point beats to bars, beats and
471 integer ticks. The upshot of which being that length gets set back to 0,
472 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
474 plist
.add (ARDOUR::Properties::length_beats
, 1);
475 plist
.add (ARDOUR::Properties::name
, string());
476 plist
.add (ARDOUR::Properties::layer
, 0);
478 boost::shared_ptr
<MidiRegion
> region (boost::dynamic_pointer_cast
<MidiRegion
>
479 (RegionFactory::create (sources
, plist
, false)));
482 region
->set_start (_trackview
.track()->current_capture_start() - _trackview
.track()->get_capture_start_frame (0), this);
483 region
->set_position (_trackview
.track()->current_capture_start(), this);
484 RegionView
* rv
= add_region_view_internal (region
, false);
485 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rv
);
488 rec_regions
.push_back (make_pair (region
, rv
));
490 // rec regions are destroyed in setup_rec_box
492 /* we add the region later */
494 setup_new_rec_layer_time (region
);
497 /* start a new rec box */
499 boost::shared_ptr
<MidiTrack
> mt
= _trackview
.midi_track(); /* we know what it is already */
500 framepos_t
const frame_pos
= mt
->current_capture_start ();
501 gdouble
const xstart
= _trackview
.editor().frame_to_pixel (frame_pos
);
502 gdouble
const xend
= xstart
;
505 assert(_trackview
.midi_track()->mode() == Normal
);
507 fill_color
= ARDOUR_UI::config()->canvasvar_RecordingRect
.get();
509 ArdourCanvas::SimpleRect
* rec_rect
= new Gnome::Canvas::SimpleRect (*_canvas_group
);
510 rec_rect
->property_x1() = xstart
;
511 rec_rect
->property_y1() = 1.0;
512 rec_rect
->property_x2() = xend
;
513 rec_rect
->property_y2() = (double) _trackview
.current_height() - 1;
514 rec_rect
->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect
.get();
515 rec_rect
->property_fill_color_rgba() = fill_color
;
516 rec_rect
->lower_to_bottom();
519 recbox
.rectangle
= rec_rect
;
520 recbox
.start
= _trackview
.session()->transport_frame();
523 rec_rects
.push_back (recbox
);
525 screen_update_connection
.disconnect();
526 screen_update_connection
= ARDOUR_UI::instance()->SuperRapidScreenUpdate
.connect (
527 sigc::mem_fun (*this, &MidiStreamView::update_rec_box
));
531 } else if (rec_active
&&
532 (_trackview
.session()->record_status() != Session::Recording
||
533 !_trackview
.track()->record_enabled())) {
534 screen_update_connection
.disconnect();
536 rec_updating
= false;
541 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
543 if (!rec_rects
.empty() || !rec_regions
.empty()) {
545 /* disconnect rapid update */
546 screen_update_connection
.disconnect();
547 rec_data_ready_connections
.drop_connections ();
549 rec_updating
= false;
552 /* remove temp regions */
554 for (list
<pair
<boost::shared_ptr
<Region
>,RegionView
*> >::iterator iter
= rec_regions
.begin(); iter
!= rec_regions
.end();) {
555 list
<pair
<boost::shared_ptr
<Region
>,RegionView
*> >::iterator tmp
;
560 (*iter
).first
->drop_references ();
567 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
569 /* transport stopped, clear boxes */
570 for (vector
<RecBoxInfo
>::iterator iter
=rec_rects
.begin(); iter
!= rec_rects
.end(); ++iter
) {
571 RecBoxInfo
&rect
= (*iter
);
572 delete rect
.rectangle
;
582 MidiStreamView::color_handler ()
586 if (_trackview
.is_midi_track()) {
587 canvas_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase
.get();
589 canvas_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase
.get();;
594 MidiStreamView::note_range_adjustment_changed()
596 double sum
= note_range_adjustment
.get_value() + note_range_adjustment
.get_page_size();
597 int lowest
= (int) floor(note_range_adjustment
.get_value());
600 if (sum
== _range_sum_cache
) {
601 //cerr << "cached" << endl;
602 highest
= (int) floor(sum
);
604 //cerr << "recalc" << endl;
605 highest
= lowest
+ (int) floor(note_range_adjustment
.get_page_size());
606 _range_sum_cache
= sum
;
609 if (lowest
== _lowest_note
&& highest
== _highest_note
) {
613 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
614 //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;
616 _lowest_note
= lowest
;
617 _highest_note
= highest
;
618 apply_note_range(lowest
, highest
, true);
622 MidiStreamView::update_rec_box ()
624 StreamView::update_rec_box ();
626 if (rec_regions
.empty()) {
630 /* Update the region being recorded to reflect where we currently are */
631 boost::shared_ptr
<ARDOUR::Region
> region
= rec_regions
.back().first
;
632 region
->set_length (_trackview
.track()->current_capture_end () - _trackview
.track()->current_capture_start(), this);
634 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rec_regions
.back().second
);
635 mrv
->extend_active_notes ();
639 MidiStreamView::y_to_note (double y
) const
641 int const n
= ((contents_height() - y
- 1) / contents_height() * (double)contents_note_range())
646 } else if (n
> 127) {