2 Copyright (C) 2000-2006 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.
18 $Id: midiregion.cc 746 2006-08-02 02:44:23Z drobilla $
27 #include <glibmm/threads.h>
28 #include <glibmm/fileutils.h>
29 #include <glibmm/miscutils.h>
31 #include "evoral/Beats.hpp"
33 #include "pbd/xml++.h"
34 #include "pbd/basename.h"
36 #include "ardour/automation_control.h"
37 #include "ardour/midi_cursor.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_region.h"
40 #include "ardour/midi_ring_buffer.h"
41 #include "ardour/midi_source.h"
42 #include "ardour/region_factory.h"
43 #include "ardour/session.h"
44 #include "ardour/source_factory.h"
45 #include "ardour/tempo.h"
46 #include "ardour/types.h"
52 using namespace ARDOUR;
56 namespace Properties {
57 PBD::PropertyDescriptor<double> start_beats;
58 PBD::PropertyDescriptor<double> length_beats;
63 MidiRegion::make_property_quarks ()
65 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
66 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
67 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
68 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
72 MidiRegion::register_properties ()
74 add_property (_start_beats);
75 add_property (_length_beats);
78 /* Basic MidiRegion constructor (many channels) */
79 MidiRegion::MidiRegion (const SourceList& srcs)
81 , _start_beats (Properties::start_beats, 0.0)
82 , _length_beats (Properties::length_beats, midi_source(0)->length_beats().to_double())
83 , _ignore_shift (false)
85 register_properties ();
86 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
88 assert(_name.val().find("/") == string::npos);
89 assert(_type == DataType::MIDI);
92 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
94 , _start_beats (Properties::start_beats, other->_start_beats)
95 , _length_beats (Properties::length_beats, other->_length_beats)
96 , _ignore_shift (false)
98 //update_length_beats ();
99 register_properties ();
101 assert(_name.val().find("/") == string::npos);
102 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
106 /** Create a new MidiRegion that is part of an existing one */
107 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, MusicFrame offset)
108 : Region (other, offset)
109 , _start_beats (Properties::start_beats, other->_start_beats)
110 , _length_beats (Properties::length_beats, other->_length_beats)
111 , _ignore_shift (false)
114 register_properties ();
116 const double offset_quarter_note = _session.tempo_map().exact_qn_at_frame (other->_position + offset.frame, offset.division) - other->_quarter_note;
117 if (offset.frame != 0) {
118 _start_beats = other->_start_beats + offset_quarter_note;
119 _length_beats = other->_length_beats - offset_quarter_note;
122 assert(_name.val().find("/") == string::npos);
123 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
127 MidiRegion::~MidiRegion ()
131 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
134 MidiRegion::do_export (string path) const
136 boost::shared_ptr<MidiSource> newsrc;
138 /* caller must check for pre-existing file */
139 assert (!path.empty());
140 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
141 newsrc = boost::dynamic_pointer_cast<MidiSource>(
142 SourceFactory::createWritable(DataType::MIDI, _session,
143 path, false, _session.frame_rate()));
145 BeatsFramesConverter bfc (_session.tempo_map(), _position);
146 Evoral::Beats const bbegin = bfc.from (_start);
147 Evoral::Beats const bend = bfc.from (_start + _length);
150 /* Lock our source since we'll be reading from it. write_to() will
151 take a lock on newsrc. */
152 Source::Lock lm (midi_source(0)->mutex());
153 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
162 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
164 boost::shared_ptr<MidiRegion>
165 MidiRegion::clone (string path) const
167 boost::shared_ptr<MidiSource> newsrc;
169 /* caller must check for pre-existing file */
170 assert (!path.empty());
171 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
172 newsrc = boost::dynamic_pointer_cast<MidiSource>(
173 SourceFactory::createWritable(DataType::MIDI, _session,
174 path, false, _session.frame_rate()));
175 return clone (newsrc);
178 boost::shared_ptr<MidiRegion>
179 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
181 BeatsFramesConverter bfc (_session.tempo_map(), _position);
182 Evoral::Beats const bbegin = bfc.from (_start);
183 Evoral::Beats const bend = bfc.from (_start + _length);
186 boost::shared_ptr<MidiSource> ms = midi_source(0);
187 Source::Lock lm (ms->mutex());
193 /* Lock our source since we'll be reading from it. write_to() will
194 take a lock on newsrc.
197 if (ms->write_to (lm, newsrc, bbegin, bend)) {
198 return boost::shared_ptr<MidiRegion> ();
204 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
205 plist.add (Properties::whole_file, true);
206 plist.add (Properties::start, _start);
207 plist.add (Properties::start_beats, _start_beats);
208 plist.add (Properties::length, _length);
209 plist.add (Properties::position, _position);
210 plist.add (Properties::beat, _beat);
211 plist.add (Properties::length_beats, _length_beats);
212 plist.add (Properties::layer, 0);
214 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
215 ret->set_quarter_note (quarter_note());
221 MidiRegion::post_set (const PropertyChange& pc)
223 Region::post_set (pc);
225 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
226 /* we're called by Stateful::set_values() which sends a change
227 only if the value is different from _current.
228 session load means we can clobber length_beats here in error (not all properties differ from current),
229 so disallow (this has been set from XML state anyway).
231 if (!_session.loading()) {
232 update_length_beats (0);
236 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
237 set_start_beats_from_start_frames ();
242 MidiRegion::set_start_beats_from_start_frames ()
244 if (position_lock_style() == AudioTime) {
245 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_frame (_position - _start);
250 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
252 Region::set_length_internal (len, sub_num);
253 update_length_beats (sub_num);
257 MidiRegion::update_after_tempo_map_change (bool /* send */)
259 boost::shared_ptr<Playlist> pl (playlist());
265 const framepos_t old_pos = _position;
266 const framepos_t old_length = _length;
267 const framepos_t old_start = _start;
269 PropertyChange s_and_l;
271 if (position_lock_style() == AudioTime) {
272 recompute_position_from_lock_style (0);
275 set _start to new position in tempo map.
277 The user probably expects the region contents to maintain audio position as the
278 tempo changes, but AFAICT this requires modifying the src file to use
279 SMPTE timestamps with the current disk read model (?).
281 We could arguably use _start to set _start_beats here,
282 resulting in viewport-like behaviour (the contents maintain
283 their musical position while the region is stationary).
285 For now, the musical position at the region start is retained, but subsequent events
286 will maintain their beat distance according to the map.
288 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
290 /* _length doesn't change for audio-locked regions. update length_beats to match. */
291 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
293 s_and_l.add (Properties::start);
294 s_and_l.add (Properties::length_beats);
296 send_change (s_and_l);
300 Region::update_after_tempo_map_change (false);
302 /* _start has now been updated. */
303 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
305 if (old_start != _start) {
306 s_and_l.add (Properties::start);
308 if (old_length != _length) {
309 s_and_l.add (Properties::length);
311 if (old_pos != _position) {
312 s_and_l.add (Properties::position);
315 send_change (s_and_l);
319 MidiRegion::update_length_beats (const int32_t sub_num)
321 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - quarter_note();
325 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
327 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
329 /* don't clobber _start _length and _length_beats if session loading.*/
330 if (_session.loading()) {
334 /* set _start to new position in tempo map */
335 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
337 /* in construction from src */
338 if (_length_beats == 0.0) {
339 update_length_beats (sub_num);
342 if (position_lock_style() == AudioTime) {
343 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
345 /* leave _length_beats alone, and change _length to reflect the state of things
346 at the new position (tempo map may dictate a different number of frames).
348 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
353 MidiRegion::set_position_music_internal (double qn)
355 Region::set_position_music_internal (qn);
356 /* set _start to new position in tempo map */
357 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
359 if (position_lock_style() == AudioTime) {
360 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
363 /* leave _length_beats alone, and change _length to reflect the state of things
364 at the new position (tempo map may dictate a different number of frames).
366 _length = _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats());
371 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
374 Evoral::Range<framepos_t>* loop_range,
378 MidiStateTracker* tracker,
379 MidiChannelFilter* filter) const
381 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
385 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
388 Evoral::Range<framepos_t>* loop_range,
393 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
397 MidiRegion::_read_at (const SourceList& /*srcs*/,
398 Evoral::EventSink<framepos_t>& dst,
401 Evoral::Range<framepos_t>* loop_range,
405 MidiStateTracker* tracker,
406 MidiChannelFilter* filter) const
408 frameoffset_t internal_offset = 0;
409 framecnt_t to_read = 0;
411 /* precondition: caller has verified that we cover the desired section */
416 return 0; /* read nothing */
419 if (position < _position) {
420 /* we are starting the read from before the start of the region */
422 dur -= _position - position;
424 /* we are starting the read from after the start of the region */
425 internal_offset = position - _position;
428 if (internal_offset >= _length) {
429 return 0; /* read nothing */
432 if ((to_read = min (dur, _length - internal_offset)) == 0) {
433 return 0; /* read nothing */
436 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
438 Glib::Threads::Mutex::Lock lm(src->mutex());
440 src->set_note_mode(lm, mode);
443 cerr << "MR " << name () << " read @ " << position << " + " << to_read
444 << " dur was " << dur
445 << " len " << _length
446 << " l-io " << (_length - internal_offset)
447 << " _position = " << _position
448 << " _start = " << _start
449 << " intoffset = " << internal_offset
450 << " quarter_note = " << quarter_note()
451 << " start_beat = " << _start_beats
455 /* This call reads events from a source and writes them to `dst' timed in session frames */
459 dst, // destination buffer
460 _position - _start, // start position of the source in session frames
461 _start + internal_offset, // where to start reading in the source
462 to_read, // read duration in frames
467 _filtered_parameters,
471 return 0; /* "read nothing" */
480 return Region::state ();
484 MidiRegion::set_state (const XMLNode& node, int version)
486 int ret = Region::set_state (node, version);
492 MidiRegion::recompute_at_end ()
494 /* our length has changed
495 * so what? stuck notes are dealt with via a note state tracker
500 MidiRegion::recompute_at_start ()
502 /* as above, but the shift was from the front
503 * maybe bump currently active note's note-ons up so they sound here?
504 * that could be undesireable in certain situations though.. maybe
505 * remove the note entirely, including it's note off? something needs to
506 * be done to keep the played MIDI sane to avoid messing up voices of
507 * polyhonic things etc........
512 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
518 boost::shared_ptr<Evoral::Control>
519 MidiRegion::control (const Evoral::Parameter& id, bool create)
521 return model()->control(id, create);
524 boost::shared_ptr<const Evoral::Control>
525 MidiRegion::control (const Evoral::Parameter& id) const
527 return model()->control(id);
530 boost::shared_ptr<MidiModel>
533 return midi_source()->model();
536 boost::shared_ptr<const MidiModel>
537 MidiRegion::model() const
539 return midi_source()->model();
542 boost::shared_ptr<MidiSource>
543 MidiRegion::midi_source (uint32_t n) const
545 // Guaranteed to succeed (use a static cast?)
546 return boost::dynamic_pointer_cast<MidiSource>(source(n));
549 /* don't use this. hopefully it will go away.
550 currently used by headless-chicken session utility.
553 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
557 _sources.push_back (s);
559 _master_sources.push_back (s);
562 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
567 MidiRegion::model_changed ()
573 /* build list of filtered Parameters, being those whose automation state is not `Play' */
575 _filtered_parameters.clear ();
577 Automatable::Controls const & c = model()->controls();
579 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
580 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
582 if (ac->alist()->automation_state() != Play) {
583 _filtered_parameters.insert (ac->parameter ());
587 /* watch for changes to controls' AutoState */
588 midi_source()->AutomationStateChanged.connect_same_thread (
589 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
592 model()->ContentsShifted.connect_same_thread (_model_shift_connection, boost::bind (&MidiRegion::model_shifted, this, _1));
595 MidiRegion::model_shifted (double qn_distance)
601 if (!_ignore_shift) {
602 PropertyChange what_changed;
603 _start_beats += qn_distance;
604 framepos_t const new_start = _session.tempo_map().frames_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
606 what_changed.add (Properties::start);
607 what_changed.add (Properties::start_beats);
608 send_change (what_changed);
610 _ignore_shift = false;
615 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
617 /* Update our filtered parameters list after a change to a parameter's AutoState */
619 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
620 if (!ac || ac->alist()->automation_state() == Play) {
621 /* It should be "impossible" for ac to be NULL, but if it is, don't
622 filter the parameter so events aren't lost. */
623 _filtered_parameters.erase (p);
625 _filtered_parameters.insert (p);
628 /* the source will have an iterator into the model, and that iterator will have been set up
629 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
632 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
634 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
635 midi_source(0)->invalidate (lm);
639 /** This is called when a trim drag has resulted in a -ve _start time for this region.
640 * Fix it up by adding some empty space to the source.
643 MidiRegion::fix_negative_start ()
645 BeatsFramesConverter c (_session.tempo_map(), _position);
647 _ignore_shift = true;
649 model()->insert_silence_at_start (Evoral::Beats (- _start_beats));
656 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
658 Region::set_start_internal (s, sub_num);
660 set_start_beats_from_start_frames ();
664 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
670 PropertyChange what_changed;
673 /* Set position before length, otherwise for MIDI regions this bad thing happens:
674 * 1. we call set_length_internal; length in beats is computed using the region's current
675 * (soon-to-be old) position
676 * 2. we call set_position_internal; position is set and length in frames re-computed using
677 * length in beats from (1) but at the new position, which is wrong if the region
678 * straddles a tempo/meter change.
681 if (_position != position) {
683 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
684 const double old_pos_qn = quarter_note();
686 /* sets _pulse to new position.*/
687 set_position_internal (position, true, sub_num);
688 what_changed.add (Properties::position);
690 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
691 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
693 if (!verify_start_and_length (new_start, length)) {
697 _start_beats = new_start_qn;
698 what_changed.add (Properties::start_beats);
700 set_start_internal (new_start, sub_num);
701 what_changed.add (Properties::start);
704 if (_length != length) {
706 if (!verify_start_and_length (_start, length)) {
710 set_length_internal (length, sub_num);
711 what_changed.add (Properties::length);
712 what_changed.add (Properties::length_beats);
715 set_whole_file (false);
717 PropertyChange start_and_length;
719 start_and_length.add (Properties::start);
720 start_and_length.add (Properties::length);
722 if (what_changed.contains (start_and_length)) {
726 if (!what_changed.empty()) {
727 send_change (what_changed);