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 "temporal/beats.h"
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/playlist.h"
43 #include "ardour/region_factory.h"
44 #include "ardour/session.h"
45 #include "ardour/source_factory.h"
46 #include "ardour/tempo.h"
47 #include "ardour/types.h"
48 #include "ardour/evoral_types_convert.h"
54 using namespace ARDOUR;
58 namespace Properties {
59 PBD::PropertyDescriptor<double> start_beats;
60 PBD::PropertyDescriptor<double> length_beats;
65 MidiRegion::make_property_quarks ()
67 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
68 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
69 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
70 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
74 MidiRegion::register_properties ()
76 add_property (_start_beats);
77 add_property (_length_beats);
80 /* Basic MidiRegion constructor (many channels) */
81 MidiRegion::MidiRegion (const SourceList& srcs)
83 , _start_beats (Properties::start_beats, 0.0)
84 , _length_beats (Properties::length_beats, midi_source(0)->length_beats().to_double())
85 , _ignore_shift (false)
87 register_properties ();
88 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
90 assert(_name.val().find("/") == string::npos);
91 assert(_type == DataType::MIDI);
94 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
96 , _start_beats (Properties::start_beats, other->_start_beats)
97 , _length_beats (Properties::length_beats, other->_length_beats)
98 , _ignore_shift (false)
100 //update_length_beats ();
101 register_properties ();
103 assert(_name.val().find("/") == string::npos);
104 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
108 /** Create a new MidiRegion that is part of an existing one */
109 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, MusicSample offset)
110 : Region (other, offset)
111 , _start_beats (Properties::start_beats, other->_start_beats)
112 , _length_beats (Properties::length_beats, other->_length_beats)
113 , _ignore_shift (false)
116 register_properties ();
118 const double offset_quarter_note = _session.tempo_map().exact_qn_at_sample (other->_position + offset.sample, offset.division) - other->_quarter_note;
119 if (offset.sample != 0) {
120 _start_beats = other->_start_beats + offset_quarter_note;
121 _length_beats = other->_length_beats - offset_quarter_note;
124 assert(_name.val().find("/") == string::npos);
125 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
129 MidiRegion::~MidiRegion ()
133 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
136 MidiRegion::do_export (string path) const
138 boost::shared_ptr<MidiSource> newsrc;
140 /* caller must check for pre-existing file */
141 assert (!path.empty());
142 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
143 newsrc = boost::dynamic_pointer_cast<MidiSource>(
144 SourceFactory::createWritable(DataType::MIDI, _session,
145 path, false, _session.sample_rate()));
147 BeatsSamplesConverter bfc (_session.tempo_map(), _position);
148 Temporal::Beats const bbegin = bfc.from (_start);
149 Temporal::Beats const bend = bfc.from (_start + _length);
152 /* Lock our source since we'll be reading from it. write_to() will
153 take a lock on newsrc. */
154 Source::Lock lm (midi_source(0)->mutex());
155 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
164 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
166 boost::shared_ptr<MidiRegion>
167 MidiRegion::clone (string path) const
169 boost::shared_ptr<MidiSource> newsrc;
171 /* caller must check for pre-existing file */
172 assert (!path.empty());
173 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
174 newsrc = boost::dynamic_pointer_cast<MidiSource>(
175 SourceFactory::createWritable(DataType::MIDI, _session,
176 path, false, _session.sample_rate()));
177 return clone (newsrc);
180 boost::shared_ptr<MidiRegion>
181 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
183 BeatsSamplesConverter bfc (_session.tempo_map(), _position);
184 Temporal::Beats const bbegin = bfc.from (_start);
185 Temporal::Beats const bend = bfc.from (_start + _length);
188 boost::shared_ptr<MidiSource> ms = midi_source(0);
189 Source::Lock lm (ms->mutex());
195 /* Lock our source since we'll be reading from it. write_to() will
196 take a lock on newsrc.
199 if (ms->write_to (lm, newsrc, bbegin, bend)) {
200 return boost::shared_ptr<MidiRegion> ();
206 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
207 plist.add (Properties::whole_file, true);
208 plist.add (Properties::start, _start);
209 plist.add (Properties::start_beats, _start_beats);
210 plist.add (Properties::length, _length);
211 plist.add (Properties::position, _position);
212 plist.add (Properties::beat, _beat);
213 plist.add (Properties::length_beats, _length_beats);
214 plist.add (Properties::layer, 0);
216 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
217 ret->set_quarter_note (quarter_note());
223 MidiRegion::post_set (const PropertyChange& pc)
225 Region::post_set (pc);
227 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
228 /* we're called by Stateful::set_values() which sends a change
229 only if the value is different from _current.
230 session load means we can clobber length_beats here in error (not all properties differ from current),
231 so disallow (this has been set from XML state anyway).
233 if (!_session.loading()) {
234 update_length_beats (0);
238 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
239 set_start_beats_from_start_samples ();
244 MidiRegion::set_start_beats_from_start_samples ()
246 if (position_lock_style() == AudioTime) {
247 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_sample (_position - _start);
252 MidiRegion::set_length_internal (samplecnt_t len, const int32_t sub_num)
254 Region::set_length_internal (len, sub_num);
255 update_length_beats (sub_num);
259 MidiRegion::update_after_tempo_map_change (bool /* send */)
261 boost::shared_ptr<Playlist> pl (playlist());
267 const samplepos_t old_pos = _position;
268 const samplepos_t old_length = _length;
269 const samplepos_t old_start = _start;
271 PropertyChange s_and_l;
273 if (position_lock_style() == AudioTime) {
274 recompute_position_from_lock_style (0);
277 set _start to new position in tempo map.
279 The user probably expects the region contents to maintain audio position as the
280 tempo changes, but AFAICT this requires modifying the src file to use
281 SMPTE timestamps with the current disk read model (?).
283 We could arguably use _start to set _start_beats here,
284 resulting in viewport-like behaviour (the contents maintain
285 their musical position while the region is stationary).
287 For now, the musical position at the region start is retained, but subsequent events
288 will maintain their beat distance according to the map.
290 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
292 /* _length doesn't change for audio-locked regions. update length_beats to match. */
293 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
295 s_and_l.add (Properties::start);
296 s_and_l.add (Properties::length_beats);
298 send_change (s_and_l);
302 Region::update_after_tempo_map_change (false);
304 /* _start has now been updated. */
305 _length = max ((samplecnt_t) 1, _session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
307 if (old_start != _start) {
308 s_and_l.add (Properties::start);
310 if (old_length != _length) {
311 s_and_l.add (Properties::length);
313 if (old_pos != _position) {
314 s_and_l.add (Properties::position);
317 send_change (s_and_l);
321 MidiRegion::update_length_beats (const int32_t sub_num)
323 _length_beats = _session.tempo_map().exact_qn_at_sample (_position + _length, sub_num) - quarter_note();
327 MidiRegion::set_position_internal (samplepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
329 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
331 /* don't clobber _start _length and _length_beats if session loading.*/
332 if (_session.loading()) {
336 /* set _start to new position in tempo map */
337 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
339 /* in construction from src */
340 if (_length_beats == 0.0) {
341 update_length_beats (sub_num);
344 if (position_lock_style() == AudioTime) {
345 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
347 /* leave _length_beats alone, and change _length to reflect the state of things
348 at the new position (tempo map may dictate a different number of samples).
350 Region::set_length_internal (_session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
355 MidiRegion::set_position_music_internal (double qn)
357 Region::set_position_music_internal (qn);
358 /* set _start to new position in tempo map */
359 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
361 if (position_lock_style() == AudioTime) {
362 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
365 /* leave _length_beats alone, and change _length to reflect the state of things
366 at the new position (tempo map may dictate a different number of samples).
368 _length = _session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + length_beats());
373 MidiRegion::read_at (Evoral::EventSink<samplepos_t>& out,
374 samplepos_t position,
376 Evoral::Range<samplepos_t>* loop_range,
380 MidiStateTracker* tracker,
381 MidiChannelFilter* filter) const
383 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
387 MidiRegion::master_read_at (MidiRingBuffer<samplepos_t>& out,
388 samplepos_t position,
390 Evoral::Range<samplepos_t>* loop_range,
395 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
399 MidiRegion::_read_at (const SourceList& /*srcs*/,
400 Evoral::EventSink<samplepos_t>& dst,
401 samplepos_t position,
403 Evoral::Range<samplepos_t>* loop_range,
407 MidiStateTracker* tracker,
408 MidiChannelFilter* filter) const
410 sampleoffset_t internal_offset = 0;
411 samplecnt_t to_read = 0;
413 /* precondition: caller has verified that we cover the desired section */
418 return 0; /* read nothing */
421 if (position < _position) {
422 /* we are starting the read from before the start of the region */
424 dur -= _position - position;
426 /* we are starting the read from after the start of the region */
427 internal_offset = position - _position;
430 if (internal_offset >= _length) {
431 return 0; /* read nothing */
434 if ((to_read = min (dur, _length - internal_offset)) == 0) {
435 return 0; /* read nothing */
438 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
440 Glib::Threads::Mutex::Lock lm(src->mutex());
442 src->set_note_mode(lm, mode);
445 cerr << "MR " << name () << " read @ " << position << " + " << to_read
446 << " dur was " << dur
447 << " len " << _length
448 << " l-io " << (_length - internal_offset)
449 << " _position = " << _position
450 << " _start = " << _start
451 << " intoffset = " << internal_offset
452 << " quarter_note = " << quarter_note()
453 << " start_beat = " << _start_beats
457 /* This call reads events from a source and writes them to `dst' timed in session samples */
461 dst, // destination buffer
462 _position - _start, // start position of the source in session samples
463 _start + internal_offset, // where to start reading in the source
464 to_read, // read duration in samples
469 _filtered_parameters,
473 return 0; /* "read nothing" */
482 return Region::state ();
486 MidiRegion::set_state (const XMLNode& node, int version)
488 int ret = Region::set_state (node, version);
494 MidiRegion::recompute_at_end ()
496 /* our length has changed
497 * so what? stuck notes are dealt with via a note state tracker
502 MidiRegion::recompute_at_start ()
504 /* as above, but the shift was from the front
505 * maybe bump currently active note's note-ons up so they sound here?
506 * that could be undesireable in certain situations though.. maybe
507 * remove the note entirely, including it's note off? something needs to
508 * be done to keep the played MIDI sane to avoid messing up voices of
509 * polyhonic things etc........
514 MidiRegion::separate_by_channel (vector< boost::shared_ptr<Region> >&) const
520 boost::shared_ptr<Evoral::Control>
521 MidiRegion::control (const Evoral::Parameter& id, bool create)
523 return model()->control(id, create);
526 boost::shared_ptr<const Evoral::Control>
527 MidiRegion::control (const Evoral::Parameter& id) const
529 return model()->control(id);
532 boost::shared_ptr<MidiModel>
535 return midi_source()->model();
538 boost::shared_ptr<const MidiModel>
539 MidiRegion::model() const
541 return midi_source()->model();
544 boost::shared_ptr<MidiSource>
545 MidiRegion::midi_source (uint32_t n) const
547 // Guaranteed to succeed (use a static cast?)
548 return boost::dynamic_pointer_cast<MidiSource>(source(n));
551 /* don't use this. hopefully it will go away.
552 currently used by headless-chicken session utility.
555 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
559 _sources.push_back (s);
561 _master_sources.push_back (s);
564 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
569 MidiRegion::model_changed ()
575 /* build list of filtered Parameters, being those whose automation state is not `Play' */
577 _filtered_parameters.clear ();
579 Automatable::Controls const & c = model()->controls();
581 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
582 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
584 if (ac->alist()->automation_state() != Play) {
585 _filtered_parameters.insert (ac->parameter ());
589 /* watch for changes to controls' AutoState */
590 midi_source()->AutomationStateChanged.connect_same_thread (
591 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
594 model()->ContentsShifted.connect_same_thread (_model_shift_connection, boost::bind (&MidiRegion::model_shifted, this, _1));
597 MidiRegion::model_shifted (double qn_distance)
603 if (!_ignore_shift) {
604 PropertyChange what_changed;
605 _start_beats += qn_distance;
606 samplepos_t const new_start = _session.tempo_map().samples_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
608 what_changed.add (Properties::start);
609 what_changed.add (Properties::start_beats);
610 send_change (what_changed);
612 _ignore_shift = false;
617 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
619 /* Update our filtered parameters list after a change to a parameter's AutoState */
621 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
622 if (!ac || ac->alist()->automation_state() == Play) {
623 /* It should be "impossible" for ac to be NULL, but if it is, don't
624 filter the parameter so events aren't lost. */
625 _filtered_parameters.erase (p);
627 _filtered_parameters.insert (p);
630 /* the source will have an iterator into the model, and that iterator will have been set up
631 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
634 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
636 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
637 midi_source(0)->invalidate (lm);
641 /** This is called when a trim drag has resulted in a -ve _start time for this region.
642 * Fix it up by adding some empty space to the source.
645 MidiRegion::fix_negative_start ()
647 BeatsSamplesConverter c (_session.tempo_map(), _position);
649 _ignore_shift = true;
651 model()->insert_silence_at_start (Temporal::Beats (- _start_beats));
658 MidiRegion::set_start_internal (samplecnt_t s, const int32_t sub_num)
660 Region::set_start_internal (s, sub_num);
662 set_start_beats_from_start_samples ();
666 MidiRegion::trim_to_internal (samplepos_t position, samplecnt_t length, const int32_t sub_num)
672 PropertyChange what_changed;
675 /* Set position before length, otherwise for MIDI regions this bad thing happens:
676 * 1. we call set_length_internal; length in beats is computed using the region's current
677 * (soon-to-be old) position
678 * 2. we call set_position_internal; position is set and length in samples re-computed using
679 * length in beats from (1) but at the new position, which is wrong if the region
680 * straddles a tempo/meter change.
683 if (_position != position) {
685 const double pos_qn = _session.tempo_map().exact_qn_at_sample (position, sub_num);
686 const double old_pos_qn = quarter_note();
688 /* sets _pulse to new position.*/
689 set_position_internal (position, true, sub_num);
690 what_changed.add (Properties::position);
692 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
693 samplepos_t new_start = _session.tempo_map().samples_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
695 if (!verify_start_and_length (new_start, length)) {
699 _start_beats = new_start_qn;
700 what_changed.add (Properties::start_beats);
702 set_start_internal (new_start, sub_num);
703 what_changed.add (Properties::start);
706 if (_length != length) {
708 if (!verify_start_and_length (_start, length)) {
712 set_length_internal (length, sub_num);
713 what_changed.add (Properties::length);
714 what_changed.add (Properties::length_beats);
717 set_whole_file (false);
719 PropertyChange start_and_length;
721 start_and_length.add (Properties::start);
722 start_and_length.add (Properties::length);
724 if (what_changed.contains (start_and_length)) {
728 if (!what_changed.empty()) {
729 send_change (what_changed);