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 veto_note_range(uint8_t& min
, uint8_t& max
)
109 /* Legal notes, thanks */
113 /* Always display at least one octave in [0, 127] */
115 if (min
> (127 - 11)) {
118 } else if (max
< min
+ 11) {
119 uint8_t d
= 11 - (max
- min
);
120 if (max
+ d
/2 > 127) {
127 assert(max
- min
>= 11);
133 MidiStreamView::create_region_view (boost::shared_ptr
<Region
> r
, bool /*wfd*/, bool)
135 boost::shared_ptr
<MidiRegion
> region
= boost::dynamic_pointer_cast
<MidiRegion
> (r
);
141 RegionView
* region_view
= new MidiRegionView (_canvas_group
, _trackview
, region
,
142 _samples_per_unit
, region_color
);
144 region_view
->init (region_color
, false);
150 MidiStreamView::add_region_view_internal (boost::shared_ptr
<Region
> r
, bool wfd
, bool recording
)
152 boost::shared_ptr
<MidiRegion
> region
= boost::dynamic_pointer_cast
<MidiRegion
> (r
);
158 for (list
<RegionView
*>::iterator i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
159 if ((*i
)->region() == r
) {
161 /* great. we already have a MidiRegionView for this Region. use it again. */
163 (*i
)->set_valid (true);
165 display_region(dynamic_cast<MidiRegionView
*>(*i
), wfd
);
171 MidiRegionView
* region_view
= dynamic_cast<MidiRegionView
*> (create_region_view (r
, wfd
, recording
));
172 if (region_view
== 0) {
176 region_views
.push_front (region_view
);
178 if (_trackview
.editor().internal_editing()) {
179 region_view
->hide_rect ();
181 region_view
->show_rect ();
184 /* display events and find note range */
185 display_region (region_view
, wfd
);
187 /* catch regionview going away */
188 region
->DropReferences
.connect (*this, invalidator (*this), boost::bind (&MidiStreamView::remove_region_view
, this, region
), gui_context());
190 RegionViewAdded (region_view
);
196 MidiStreamView::display_region(MidiRegionView
* region_view
, bool load_model
)
202 region_view
->enable_display(true);
204 boost::shared_ptr
<MidiSource
> source(region_view
->midi_region()->midi_source(0));
207 source
->load_model();
210 _range_dirty
= update_data_note_range(
211 source
->model()->lowest_note(),
212 source
->model()->highest_note());
214 // Display region contents
215 region_view
->set_height (child_height());
216 region_view
->display_model(source
->model());
220 MidiStreamView::display_track (boost::shared_ptr
<Track
> tr
)
222 StreamView::display_track (tr
);
230 MidiStreamView::update_contents_metrics(boost::shared_ptr
<Region
> r
)
232 boost::shared_ptr
<MidiRegion
> mr
= boost::dynamic_pointer_cast
<MidiRegion
>(r
);
234 mr
->midi_source(0)->load_model();
235 _range_dirty
= update_data_note_range(
236 mr
->model()->lowest_note(),
237 mr
->model()->highest_note());
242 MidiStreamView::update_data_note_range(uint8_t min
, uint8_t max
)
245 if (min
< _data_note_min
) {
246 _data_note_min
= min
;
249 if (max
> _data_note_max
) {
250 _data_note_max
= max
;
257 MidiStreamView::redisplay_track ()
259 if (!_trackview
.is_midi_track()) {
263 list
<RegionView
*>::iterator i
;
265 // Load models if necessary, and find note range of all our contents
266 _range_dirty
= false;
267 _data_note_min
= 127;
269 _trackview
.track()->playlist()->foreach_region(
270 sigc::mem_fun (*this, &StreamView::update_contents_metrics
));
272 // No notes, use default range
278 // Extend visible range to show newly recorded data, if necessary
279 _lowest_note
= std::min(_lowest_note
, _data_note_min
);
280 _highest_note
= std::max(_highest_note
, _data_note_max
);
282 veto_note_range(_lowest_note
, _highest_note
);
284 // Flag region views as invalid and disable drawing
285 for (i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
286 (*i
)->set_valid(false);
287 (*i
)->enable_display(false);
290 // Add and display region views, and flag them as valid
291 _trackview
.track()->playlist()->foreach_region(
292 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 max_note_height
= 20; // This should probably be based on text size...
377 int const range
= _highest_note
- _lowest_note
;
378 int const pixels_per_note
= floor (child_height () / range
);
380 /* do not grow note height beyond 10 pixels */
381 if (pixels_per_note
> max_note_height
) {
383 int const available_note_range
= floor (child_height() / max_note_height
);
384 int additional_notes
= available_note_range
- range
;
386 /* distribute additional notes to higher and lower ranges, clamp at 0 and 127 */
387 for (int i
= 0; i
< additional_notes
; i
++){
389 if (i
% 2 && _highest_note
< 127){
395 else if (_lowest_note
> 0){
404 note_range_adjustment
.set_page_size(_highest_note
- _lowest_note
);
405 note_range_adjustment
.set_value(_lowest_note
);
409 if (to_region_views
) {
410 apply_note_range_to_regions ();
417 MidiStreamView::apply_note_range_to_regions ()
419 if (!_updates_suspended
) {
420 for (list
<RegionView
*>::iterator i
= region_views
.begin(); i
!= region_views
.end(); ++i
) {
421 ((MidiRegionView
*)(*i
))->apply_note_range(_lowest_note
, _highest_note
);
427 MidiStreamView::update_note_range(uint8_t note_num
)
429 assert(note_num
<= 127);
430 _data_note_min
= min(_data_note_min
, note_num
);
431 _data_note_max
= max(_data_note_max
, note_num
);
435 MidiStreamView::setup_rec_box ()
437 // cerr << _trackview.name() << " streamview SRB\n";
439 if (_trackview
.session()->transport_rolling()) {
442 _trackview
.session()->record_status() == Session::Recording
&&
443 _trackview
.track()->record_enabled()) {
445 if (Config
->get_show_waveforms_while_recording() && rec_regions
.size() == rec_rects
.size()) {
447 /* add a new region, but don't bother if they set show-waveforms-while-recording mid-record */
449 MidiRegion::SourceList sources
;
451 rec_data_ready_connections
.drop_connections ();
453 sources
.push_back (_trackview
.midi_track()->write_source());
457 framepos_t start
= 0;
458 if (rec_regions
.size() > 0) {
459 start
= rec_regions
.back().first
->start()
460 + _trackview
.track()->get_captured_frames(rec_regions
.size()-1);
463 if (!rec_regions
.empty()) {
464 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rec_regions
.back().second
);
470 plist
.add (ARDOUR::Properties::start
, start
);
471 plist
.add (ARDOUR::Properties::length
, 1);
472 /* Just above we're setting this nascent region's length to 1. I think this
473 is so that the RegionView gets created with a non-zero width, as apparently
474 creating a RegionView with a zero width causes it never to be displayed
475 (there is a warning in TimeAxisViewItem::init about this). However, we
476 must also set length_beats to something non-zero, otherwise the frame length
477 of 1 causes length_beats to be set to some small quantity << 1. Then
478 when the position is set up below, this length_beats is used to recompute
479 length using BeatsFramesConverter::to, which is slightly innacurate for small
480 beats values because it converts floating point beats to bars, beats and
481 integer ticks. The upshot of which being that length gets set back to 0,
482 meaning no region view is ever seen, meaning no MIDI notes during record (#3820).
484 plist
.add (ARDOUR::Properties::length_beats
, 1);
485 plist
.add (ARDOUR::Properties::name
, string());
486 plist
.add (ARDOUR::Properties::layer
, 0);
488 boost::shared_ptr
<MidiRegion
> region (boost::dynamic_pointer_cast
<MidiRegion
>
489 (RegionFactory::create (sources
, plist
, false)));
492 region
->set_start (_trackview
.track()->current_capture_start() - _trackview
.track()->get_capture_start_frame (0));
493 region
->set_position (_trackview
.track()->current_capture_start());
494 RegionView
* rv
= add_region_view_internal (region
, false);
495 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rv
);
498 rec_regions
.push_back (make_pair (region
, rv
));
500 // rec regions are destroyed in setup_rec_box
502 /* we add the region later */
504 setup_new_rec_layer_time (region
);
507 /* start a new rec box */
509 boost::shared_ptr
<MidiTrack
> mt
= _trackview
.midi_track(); /* we know what it is already */
510 framepos_t
const frame_pos
= mt
->current_capture_start ();
511 gdouble
const xstart
= _trackview
.editor().frame_to_pixel (frame_pos
);
512 gdouble
const xend
= xstart
;
515 assert(_trackview
.midi_track()->mode() == Normal
);
517 fill_color
= ARDOUR_UI::config()->canvasvar_RecordingRect
.get();
519 ArdourCanvas::SimpleRect
* rec_rect
= new Gnome::Canvas::SimpleRect (*_canvas_group
);
520 rec_rect
->property_x1() = xstart
;
521 rec_rect
->property_y1() = 1.0;
522 rec_rect
->property_x2() = xend
;
523 rec_rect
->property_y2() = (double) _trackview
.current_height() - 1;
524 rec_rect
->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RecordingRect
.get();
525 rec_rect
->property_fill_color_rgba() = fill_color
;
526 rec_rect
->lower_to_bottom();
529 recbox
.rectangle
= rec_rect
;
530 recbox
.start
= _trackview
.session()->transport_frame();
533 rec_rects
.push_back (recbox
);
535 screen_update_connection
.disconnect();
536 screen_update_connection
= ARDOUR_UI::instance()->SuperRapidScreenUpdate
.connect (
537 sigc::mem_fun (*this, &MidiStreamView::update_rec_box
));
541 } else if (rec_active
&&
542 (_trackview
.session()->record_status() != Session::Recording
||
543 !_trackview
.track()->record_enabled())) {
544 screen_update_connection
.disconnect();
546 rec_updating
= false;
551 // cerr << "\tNOT rolling, rec_rects = " << rec_rects.size() << " rec_regions = " << rec_regions.size() << endl;
553 if (!rec_rects
.empty() || !rec_regions
.empty()) {
555 /* disconnect rapid update */
556 screen_update_connection
.disconnect();
557 rec_data_ready_connections
.drop_connections ();
559 rec_updating
= false;
562 /* remove temp regions */
564 for (list
<pair
<boost::shared_ptr
<Region
>,RegionView
*> >::iterator iter
= rec_regions
.begin(); iter
!= rec_regions
.end();) {
565 list
<pair
<boost::shared_ptr
<Region
>,RegionView
*> >::iterator tmp
;
570 (*iter
).first
->drop_references ();
577 // cerr << "\tclear " << rec_rects.size() << " rec rects\n";
579 /* transport stopped, clear boxes */
580 for (vector
<RecBoxInfo
>::iterator iter
=rec_rects
.begin(); iter
!= rec_rects
.end(); ++iter
) {
581 RecBoxInfo
&rect
= (*iter
);
582 delete rect
.rectangle
;
592 MidiStreamView::color_handler ()
596 if (_trackview
.is_midi_track()) {
597 canvas_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiTrackBase
.get();
599 canvas_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MidiBusBase
.get();;
604 MidiStreamView::note_range_adjustment_changed()
606 double sum
= note_range_adjustment
.get_value() + note_range_adjustment
.get_page_size();
607 int lowest
= (int) floor(note_range_adjustment
.get_value());
610 if (sum
== _range_sum_cache
) {
611 //cerr << "cached" << endl;
612 highest
= (int) floor(sum
);
614 //cerr << "recalc" << endl;
615 highest
= lowest
+ (int) floor(note_range_adjustment
.get_page_size());
616 _range_sum_cache
= sum
;
619 if (lowest
== _lowest_note
&& highest
== _highest_note
) {
623 //cerr << "note range adjustment changed: " << lowest << " " << highest << endl;
624 //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;
626 _lowest_note
= lowest
;
627 _highest_note
= highest
;
628 apply_note_range(lowest
, highest
, true);
632 MidiStreamView::update_rec_box ()
634 StreamView::update_rec_box ();
636 if (rec_regions
.empty()) {
640 /* Update the region being recorded to reflect where we currently are */
641 boost::shared_ptr
<ARDOUR::Region
> region
= rec_regions
.back().first
;
642 region
->set_length (_trackview
.track()->current_capture_end () - _trackview
.track()->current_capture_start());
644 MidiRegionView
* mrv
= dynamic_cast<MidiRegionView
*> (rec_regions
.back().second
);
645 mrv
->extend_active_notes ();
649 MidiStreamView::y_to_note (double y
) const
651 int const n
= ((contents_height() - y
- 1) / contents_height() * (double)contents_note_range())
656 } else if (n
> 127) {
663 /** Suspend updates to the regions' note ranges and our
664 * note lines until resume_updates() is called.
667 MidiStreamView::suspend_updates ()
669 _updates_suspended
= true;
672 /** Resume updates to region note ranges and note lines,
673 * and update them now.
676 MidiStreamView::resume_updates ()
678 _updates_suspended
= false;
681 apply_note_range_to_regions ();