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/region_factory.h"
43 #include "ardour/session.h"
44 #include "ardour/source_factory.h"
45 #include "ardour/tempo.h"
46 #include "ardour/types.h"
47 #include "ardour/evoral_types_convert.h"
53 using namespace ARDOUR;
57 namespace Properties {
58 PBD::PropertyDescriptor<double> start_beats;
59 PBD::PropertyDescriptor<double> length_beats;
64 MidiRegion::make_property_quarks ()
66 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
67 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
68 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
69 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
73 MidiRegion::register_properties ()
75 add_property (_start_beats);
76 add_property (_length_beats);
79 /* Basic MidiRegion constructor (many channels) */
80 MidiRegion::MidiRegion (const SourceList& srcs)
82 , _start_beats (Properties::start_beats, 0.0)
83 , _length_beats (Properties::length_beats, midi_source(0)->length_beats().to_double())
84 , _ignore_shift (false)
86 register_properties ();
87 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
89 assert(_name.val().find("/") == string::npos);
90 assert(_type == DataType::MIDI);
93 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
95 , _start_beats (Properties::start_beats, other->_start_beats)
96 , _length_beats (Properties::length_beats, other->_length_beats)
97 , _ignore_shift (false)
99 //update_length_beats ();
100 register_properties ();
102 assert(_name.val().find("/") == string::npos);
103 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
107 /** Create a new MidiRegion that is part of an existing one */
108 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, MusicSample offset)
109 : Region (other, offset)
110 , _start_beats (Properties::start_beats, other->_start_beats)
111 , _length_beats (Properties::length_beats, other->_length_beats)
112 , _ignore_shift (false)
115 register_properties ();
117 const double offset_quarter_note = _session.tempo_map().exact_qn_at_sample (other->_position + offset.sample, offset.division) - other->_quarter_note;
118 if (offset.sample != 0) {
119 _start_beats = other->_start_beats + offset_quarter_note;
120 _length_beats = other->_length_beats - offset_quarter_note;
123 assert(_name.val().find("/") == string::npos);
124 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
128 MidiRegion::~MidiRegion ()
132 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
135 MidiRegion::do_export (string path) const
137 boost::shared_ptr<MidiSource> newsrc;
139 /* caller must check for pre-existing file */
140 assert (!path.empty());
141 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
142 newsrc = boost::dynamic_pointer_cast<MidiSource>(
143 SourceFactory::createWritable(DataType::MIDI, _session,
144 path, false, _session.sample_rate()));
146 BeatsSamplesConverter bfc (_session.tempo_map(), _position);
147 Temporal::Beats const bbegin = bfc.from (_start);
148 Temporal::Beats const bend = bfc.from (_start + _length);
151 /* Lock our source since we'll be reading from it. write_to() will
152 take a lock on newsrc. */
153 Source::Lock lm (midi_source(0)->mutex());
154 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
163 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
165 boost::shared_ptr<MidiRegion>
166 MidiRegion::clone (string path) const
168 boost::shared_ptr<MidiSource> newsrc;
170 /* caller must check for pre-existing file */
171 assert (!path.empty());
172 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
173 newsrc = boost::dynamic_pointer_cast<MidiSource>(
174 SourceFactory::createWritable(DataType::MIDI, _session,
175 path, false, _session.sample_rate()));
176 return clone (newsrc);
179 boost::shared_ptr<MidiRegion>
180 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
182 BeatsSamplesConverter bfc (_session.tempo_map(), _position);
183 Temporal::Beats const bbegin = bfc.from (_start);
184 Temporal::Beats const bend = bfc.from (_start + _length);
187 boost::shared_ptr<MidiSource> ms = midi_source(0);
188 Source::Lock lm (ms->mutex());
194 /* Lock our source since we'll be reading from it. write_to() will
195 take a lock on newsrc.
198 if (ms->write_to (lm, newsrc, bbegin, bend)) {
199 return boost::shared_ptr<MidiRegion> ();
205 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
206 plist.add (Properties::whole_file, true);
207 plist.add (Properties::start, _start);
208 plist.add (Properties::start_beats, _start_beats);
209 plist.add (Properties::length, _length);
210 plist.add (Properties::position, _position);
211 plist.add (Properties::beat, _beat);
212 plist.add (Properties::length_beats, _length_beats);
213 plist.add (Properties::layer, 0);
215 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
216 ret->set_quarter_note (quarter_note());
222 MidiRegion::post_set (const PropertyChange& pc)
224 Region::post_set (pc);
226 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
227 /* we're called by Stateful::set_values() which sends a change
228 only if the value is different from _current.
229 session load means we can clobber length_beats here in error (not all properties differ from current),
230 so disallow (this has been set from XML state anyway).
232 if (!_session.loading()) {
233 update_length_beats (0);
237 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
238 set_start_beats_from_start_samples ();
243 MidiRegion::set_start_beats_from_start_samples ()
245 if (position_lock_style() == AudioTime) {
246 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_sample (_position - _start);
251 MidiRegion::set_length_internal (samplecnt_t len, const int32_t sub_num)
253 Region::set_length_internal (len, sub_num);
254 update_length_beats (sub_num);
258 MidiRegion::update_after_tempo_map_change (bool /* send */)
260 boost::shared_ptr<Playlist> pl (playlist());
266 const samplepos_t old_pos = _position;
267 const samplepos_t old_length = _length;
268 const samplepos_t old_start = _start;
270 PropertyChange s_and_l;
272 if (position_lock_style() == AudioTime) {
273 recompute_position_from_lock_style (0);
276 set _start to new position in tempo map.
278 The user probably expects the region contents to maintain audio position as the
279 tempo changes, but AFAICT this requires modifying the src file to use
280 SMPTE timestamps with the current disk read model (?).
282 We could arguably use _start to set _start_beats here,
283 resulting in viewport-like behaviour (the contents maintain
284 their musical position while the region is stationary).
286 For now, the musical position at the region start is retained, but subsequent events
287 will maintain their beat distance according to the map.
289 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
291 /* _length doesn't change for audio-locked regions. update length_beats to match. */
292 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
294 s_and_l.add (Properties::start);
295 s_and_l.add (Properties::length_beats);
297 send_change (s_and_l);
301 Region::update_after_tempo_map_change (false);
303 /* _start has now been updated. */
304 _length = max ((samplecnt_t) 1, _session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
306 if (old_start != _start) {
307 s_and_l.add (Properties::start);
309 if (old_length != _length) {
310 s_and_l.add (Properties::length);
312 if (old_pos != _position) {
313 s_and_l.add (Properties::position);
316 send_change (s_and_l);
320 MidiRegion::update_length_beats (const int32_t sub_num)
322 _length_beats = _session.tempo_map().exact_qn_at_sample (_position + _length, sub_num) - quarter_note();
326 MidiRegion::set_position_internal (samplepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
328 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
330 /* don't clobber _start _length and _length_beats if session loading.*/
331 if (_session.loading()) {
335 /* set _start to new position in tempo map */
336 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
338 /* in construction from src */
339 if (_length_beats == 0.0) {
340 update_length_beats (sub_num);
343 if (position_lock_style() == AudioTime) {
344 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
346 /* leave _length_beats alone, and change _length to reflect the state of things
347 at the new position (tempo map may dictate a different number of samples).
349 Region::set_length_internal (_session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
354 MidiRegion::set_position_music_internal (double qn)
356 Region::set_position_music_internal (qn);
357 /* set _start to new position in tempo map */
358 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
360 if (position_lock_style() == AudioTime) {
361 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
364 /* leave _length_beats alone, and change _length to reflect the state of things
365 at the new position (tempo map may dictate a different number of samples).
367 _length = _session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + length_beats());
372 MidiRegion::read_at (Evoral::EventSink<samplepos_t>& out,
373 samplepos_t position,
375 Evoral::Range<samplepos_t>* loop_range,
379 MidiStateTracker* tracker,
380 MidiChannelFilter* filter) const
382 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
386 MidiRegion::master_read_at (MidiRingBuffer<samplepos_t>& out,
387 samplepos_t position,
389 Evoral::Range<samplepos_t>* loop_range,
394 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
398 MidiRegion::_read_at (const SourceList& /*srcs*/,
399 Evoral::EventSink<samplepos_t>& dst,
400 samplepos_t position,
402 Evoral::Range<samplepos_t>* loop_range,
406 MidiStateTracker* tracker,
407 MidiChannelFilter* filter) const
409 sampleoffset_t internal_offset = 0;
410 samplecnt_t to_read = 0;
412 /* precondition: caller has verified that we cover the desired section */
417 return 0; /* read nothing */
420 if (position < _position) {
421 /* we are starting the read from before the start of the region */
423 dur -= _position - position;
425 /* we are starting the read from after the start of the region */
426 internal_offset = position - _position;
429 if (internal_offset >= _length) {
430 return 0; /* read nothing */
433 if ((to_read = min (dur, _length - internal_offset)) == 0) {
434 return 0; /* read nothing */
437 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
439 Glib::Threads::Mutex::Lock lm(src->mutex());
441 src->set_note_mode(lm, mode);
444 cerr << "MR " << name () << " read @ " << position << " + " << to_read
445 << " dur was " << dur
446 << " len " << _length
447 << " l-io " << (_length - internal_offset)
448 << " _position = " << _position
449 << " _start = " << _start
450 << " intoffset = " << internal_offset
451 << " quarter_note = " << quarter_note()
452 << " start_beat = " << _start_beats
456 /* This call reads events from a source and writes them to `dst' timed in session samples */
460 dst, // destination buffer
461 _position - _start, // start position of the source in session samples
462 _start + internal_offset, // where to start reading in the source
463 to_read, // read duration in samples
468 _filtered_parameters,
472 return 0; /* "read nothing" */
481 return Region::state ();
485 MidiRegion::set_state (const XMLNode& node, int version)
487 int ret = Region::set_state (node, version);
493 MidiRegion::recompute_at_end ()
495 /* our length has changed
496 * so what? stuck notes are dealt with via a note state tracker
501 MidiRegion::recompute_at_start ()
503 /* as above, but the shift was from the front
504 * maybe bump currently active note's note-ons up so they sound here?
505 * that could be undesireable in certain situations though.. maybe
506 * remove the note entirely, including it's note off? something needs to
507 * be done to keep the played MIDI sane to avoid messing up voices of
508 * polyhonic things etc........
513 MidiRegion::separate_by_channel (vector< boost::shared_ptr<Region> >&) const
519 boost::shared_ptr<Evoral::Control>
520 MidiRegion::control (const Evoral::Parameter& id, bool create)
522 return model()->control(id, create);
525 boost::shared_ptr<const Evoral::Control>
526 MidiRegion::control (const Evoral::Parameter& id) const
528 return model()->control(id);
531 boost::shared_ptr<MidiModel>
534 return midi_source()->model();
537 boost::shared_ptr<const MidiModel>
538 MidiRegion::model() const
540 return midi_source()->model();
543 boost::shared_ptr<MidiSource>
544 MidiRegion::midi_source (uint32_t n) const
546 // Guaranteed to succeed (use a static cast?)
547 return boost::dynamic_pointer_cast<MidiSource>(source(n));
550 /* don't use this. hopefully it will go away.
551 currently used by headless-chicken session utility.
554 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
558 _sources.push_back (s);
560 _master_sources.push_back (s);
563 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
568 MidiRegion::model_changed ()
574 /* build list of filtered Parameters, being those whose automation state is not `Play' */
576 _filtered_parameters.clear ();
578 Automatable::Controls const & c = model()->controls();
580 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
581 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
583 if (ac->alist()->automation_state() != Play) {
584 _filtered_parameters.insert (ac->parameter ());
588 /* watch for changes to controls' AutoState */
589 midi_source()->AutomationStateChanged.connect_same_thread (
590 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
593 model()->ContentsShifted.connect_same_thread (_model_shift_connection, boost::bind (&MidiRegion::model_shifted, this, _1));
596 MidiRegion::model_shifted (double qn_distance)
602 if (!_ignore_shift) {
603 PropertyChange what_changed;
604 _start_beats += qn_distance;
605 samplepos_t const new_start = _session.tempo_map().samples_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
607 what_changed.add (Properties::start);
608 what_changed.add (Properties::start_beats);
609 send_change (what_changed);
611 _ignore_shift = false;
616 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
618 /* Update our filtered parameters list after a change to a parameter's AutoState */
620 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
621 if (!ac || ac->alist()->automation_state() == Play) {
622 /* It should be "impossible" for ac to be NULL, but if it is, don't
623 filter the parameter so events aren't lost. */
624 _filtered_parameters.erase (p);
626 _filtered_parameters.insert (p);
629 /* the source will have an iterator into the model, and that iterator will have been set up
630 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
633 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
635 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
636 midi_source(0)->invalidate (lm);
640 /** This is called when a trim drag has resulted in a -ve _start time for this region.
641 * Fix it up by adding some empty space to the source.
644 MidiRegion::fix_negative_start ()
646 BeatsSamplesConverter c (_session.tempo_map(), _position);
648 _ignore_shift = true;
650 model()->insert_silence_at_start (Temporal::Beats (- _start_beats));
657 MidiRegion::set_start_internal (samplecnt_t s, const int32_t sub_num)
659 Region::set_start_internal (s, sub_num);
661 set_start_beats_from_start_samples ();
665 MidiRegion::trim_to_internal (samplepos_t position, samplecnt_t length, const int32_t sub_num)
671 PropertyChange what_changed;
674 /* Set position before length, otherwise for MIDI regions this bad thing happens:
675 * 1. we call set_length_internal; length in beats is computed using the region's current
676 * (soon-to-be old) position
677 * 2. we call set_position_internal; position is set and length in samples re-computed using
678 * length in beats from (1) but at the new position, which is wrong if the region
679 * straddles a tempo/meter change.
682 if (_position != position) {
684 const double pos_qn = _session.tempo_map().exact_qn_at_sample (position, sub_num);
685 const double old_pos_qn = quarter_note();
687 /* sets _pulse to new position.*/
688 set_position_internal (position, true, sub_num);
689 what_changed.add (Properties::position);
691 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
692 samplepos_t new_start = _session.tempo_map().samples_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
694 if (!verify_start_and_length (new_start, length)) {
698 _start_beats = new_start_qn;
699 what_changed.add (Properties::start_beats);
701 set_start_internal (new_start, sub_num);
702 what_changed.add (Properties::start);
705 if (_length != length) {
707 if (!verify_start_and_length (_start, length)) {
711 set_length_internal (length, sub_num);
712 what_changed.add (Properties::length);
713 what_changed.add (Properties::length_beats);
716 set_whole_file (false);
718 PropertyChange start_and_length;
720 start_and_length.add (Properties::start);
721 start_and_length.add (Properties::length);
723 if (what_changed.contains (start_and_length)) {
727 if (!what_changed.empty()) {
728 send_change (what_changed);