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())
84 register_properties ();
85 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
87 assert(_name.val().find("/") == string::npos);
88 assert(_type == DataType::MIDI);
91 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
93 , _start_beats (Properties::start_beats, other->_start_beats)
94 , _length_beats (Properties::length_beats, other->_length_beats)
96 //update_length_beats ();
97 register_properties ();
99 assert(_name.val().find("/") == string::npos);
100 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
104 /** Create a new MidiRegion that is part of an existing one */
105 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
106 : Region (other, offset, sub_num)
107 , _start_beats (Properties::start_beats, other->_start_beats)
108 , _length_beats (Properties::length_beats, other->_length_beats)
111 _start_beats = (_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) - other->_quarter_note) + other->_start_beats;
112 update_length_beats (sub_num);
115 register_properties ();
117 assert(_name.val().find("/") == string::npos);
118 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
122 MidiRegion::~MidiRegion ()
126 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
129 MidiRegion::do_export (string path) const
131 boost::shared_ptr<MidiSource> newsrc;
133 /* caller must check for pre-existing file */
134 assert (!path.empty());
135 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
136 newsrc = boost::dynamic_pointer_cast<MidiSource>(
137 SourceFactory::createWritable(DataType::MIDI, _session,
138 path, false, _session.frame_rate()));
140 BeatsFramesConverter bfc (_session.tempo_map(), _position);
141 Evoral::Beats const bbegin = bfc.from (_start);
142 Evoral::Beats const bend = bfc.from (_start + _length);
145 /* Lock our source since we'll be reading from it. write_to() will
146 take a lock on newsrc. */
147 Source::Lock lm (midi_source(0)->mutex());
148 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
157 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
159 boost::shared_ptr<MidiRegion>
160 MidiRegion::clone (string path) const
162 boost::shared_ptr<MidiSource> newsrc;
164 /* caller must check for pre-existing file */
165 assert (!path.empty());
166 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
167 newsrc = boost::dynamic_pointer_cast<MidiSource>(
168 SourceFactory::createWritable(DataType::MIDI, _session,
169 path, false, _session.frame_rate()));
170 return clone (newsrc);
173 boost::shared_ptr<MidiRegion>
174 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
176 BeatsFramesConverter bfc (_session.tempo_map(), _position);
177 Evoral::Beats const bbegin = bfc.from (_start);
178 Evoral::Beats const bend = bfc.from (_start + _length);
181 /* Lock our source since we'll be reading from it. write_to() will
182 take a lock on newsrc. */
183 Source::Lock lm (midi_source(0)->mutex());
184 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
185 return boost::shared_ptr<MidiRegion> ();
191 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
192 plist.add (Properties::whole_file, true);
193 plist.add (Properties::start, _start);
194 plist.add (Properties::start_beats, _start_beats);
195 plist.add (Properties::length, _length);
196 plist.add (Properties::beat, _beat);
197 plist.add (Properties::length_beats, _length_beats);
198 plist.add (Properties::layer, 0);
200 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
201 ret->set_quarter_note (quarter_note());
207 MidiRegion::post_set (const PropertyChange& pc)
209 Region::post_set (pc);
211 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
212 /* we're called by Stateful::set_values() which sends a change
213 only if the value is different from _current.
214 session load means we can clobber length_beats here in error (not all properties differ from current),
215 so disallow (this has been set from XML state anyway).
217 if (!_session.loading()) {
218 update_length_beats (0);
222 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
223 set_start_beats_from_start_frames ();
228 MidiRegion::set_start_beats_from_start_frames ()
230 if (position_lock_style() == AudioTime) {
231 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_frame (_position - _start);
236 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
238 Region::set_length_internal (len, sub_num);
239 update_length_beats (sub_num);
243 MidiRegion::update_after_tempo_map_change (bool /* send */)
245 boost::shared_ptr<Playlist> pl (playlist());
251 const framepos_t old_pos = _position;
252 const framepos_t old_length = _length;
253 const framepos_t old_start = _start;
255 PropertyChange s_and_l;
257 if (position_lock_style() == AudioTime) {
258 recompute_position_from_lock_style (0);
261 set _start to new position in tempo map.
263 The user probably expects the region contents to maintain audio position as the
264 tempo changes, but AFAICT this requires modifying the src file to use
265 SMPTE timestamps with the current disk read model (?).
267 We could arguably use _start to set _start_beats here,
268 resulting in viewport-like behaviour (the contents maintain
269 their musical position while the region is stationary).
271 For now, the musical position at the region start is retained, but subsequent events
272 will maintain their beat distance according to the map.
274 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
276 /* _length doesn't change for audio-locked regions. update length_beats to match. */
277 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
279 s_and_l.add (Properties::start);
280 s_and_l.add (Properties::length_beats);
282 send_change (s_and_l);
286 Region::update_after_tempo_map_change (false);
288 /* _start has now been updated. */
289 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
291 if (old_start != _start) {
292 s_and_l.add (Properties::start);
294 if (old_length != _length) {
295 s_and_l.add (Properties::length);
297 if (old_pos != _position) {
298 s_and_l.add (Properties::position);
301 send_change (s_and_l);
305 MidiRegion::update_length_beats (const int32_t sub_num)
307 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - quarter_note();
311 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
313 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
315 /* don't clobber _start _length and _length_beats if session loading.*/
316 if (_session.loading()) {
320 /* set _start to new position in tempo map */
321 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
323 /* in construction from src */
324 if (_length_beats == 0.0) {
325 update_length_beats (sub_num);
328 if (position_lock_style() == AudioTime) {
329 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
331 /* leave _length_beats alone, and change _length to reflect the state of things
332 at the new position (tempo map may dictate a different number of frames).
334 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
339 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
342 Evoral::Range<framepos_t>* loop_range,
346 MidiStateTracker* tracker,
347 MidiChannelFilter* filter) const
349 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
353 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
356 Evoral::Range<framepos_t>* loop_range,
361 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
365 MidiRegion::_read_at (const SourceList& /*srcs*/,
366 Evoral::EventSink<framepos_t>& dst,
369 Evoral::Range<framepos_t>* loop_range,
373 MidiStateTracker* tracker,
374 MidiChannelFilter* filter) const
376 frameoffset_t internal_offset = 0;
377 framecnt_t to_read = 0;
379 /* precondition: caller has verified that we cover the desired section */
384 return 0; /* read nothing */
387 if (position < _position) {
388 /* we are starting the read from before the start of the region */
390 dur -= _position - position;
392 /* we are starting the read from after the start of the region */
393 internal_offset = position - _position;
396 if (internal_offset >= _length) {
397 return 0; /* read nothing */
400 if ((to_read = min (dur, _length - internal_offset)) == 0) {
401 return 0; /* read nothing */
404 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
406 Glib::Threads::Mutex::Lock lm(src->mutex());
408 src->set_note_mode(lm, mode);
411 cerr << "MR " << name () << " read @ " << position << " + " << to_read
412 << " dur was " << dur
413 << " len " << _length
414 << " l-io " << (_length - internal_offset)
415 << " _position = " << _position
416 << " _start = " << _start
417 << " intoffset = " << internal_offset
418 << " quarter_note = " << quarter_note()
419 << " start_beat = " << _start_beats
423 /* This call reads events from a source and writes them to `dst' timed in session frames */
427 dst, // destination buffer
428 _position - _start, // start position of the source in session frames
429 _start + internal_offset, // where to start reading in the source
430 to_read, // read duration in frames
435 _filtered_parameters,
439 return 0; /* "read nothing" */
448 return Region::state ();
452 MidiRegion::set_state (const XMLNode& node, int version)
454 int ret = Region::set_state (node, version);
460 MidiRegion::recompute_at_end ()
462 /* our length has changed
463 * so what? stuck notes are dealt with via a note state tracker
468 MidiRegion::recompute_at_start ()
470 /* as above, but the shift was from the front
471 * maybe bump currently active note's note-ons up so they sound here?
472 * that could be undesireable in certain situations though.. maybe
473 * remove the note entirely, including it's note off? something needs to
474 * be done to keep the played MIDI sane to avoid messing up voices of
475 * polyhonic things etc........
480 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
486 boost::shared_ptr<Evoral::Control>
487 MidiRegion::control (const Evoral::Parameter& id, bool create)
489 return model()->control(id, create);
492 boost::shared_ptr<const Evoral::Control>
493 MidiRegion::control (const Evoral::Parameter& id) const
495 return model()->control(id);
498 boost::shared_ptr<MidiModel>
501 return midi_source()->model();
504 boost::shared_ptr<const MidiModel>
505 MidiRegion::model() const
507 return midi_source()->model();
510 boost::shared_ptr<MidiSource>
511 MidiRegion::midi_source (uint32_t n) const
513 // Guaranteed to succeed (use a static cast?)
514 return boost::dynamic_pointer_cast<MidiSource>(source(n));
517 /* don't use this. hopefully it will go away.
518 currently used by headless-chicken session utility.
521 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
525 _sources.push_back (s);
527 _master_sources.push_back (s);
530 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
535 MidiRegion::model_changed ()
541 /* build list of filtered Parameters, being those whose automation state is not `Play' */
543 _filtered_parameters.clear ();
545 Automatable::Controls const & c = model()->controls();
547 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
548 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
550 if (ac->alist()->automation_state() != Play) {
551 _filtered_parameters.insert (ac->parameter ());
555 /* watch for changes to controls' AutoState */
556 midi_source()->AutomationStateChanged.connect_same_thread (
557 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
562 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
564 /* Update our filtered parameters list after a change to a parameter's AutoState */
566 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
567 if (!ac || ac->alist()->automation_state() == Play) {
568 /* It should be "impossible" for ac to be NULL, but if it is, don't
569 filter the parameter so events aren't lost. */
570 _filtered_parameters.erase (p);
572 _filtered_parameters.insert (p);
575 /* the source will have an iterator into the model, and that iterator will have been set up
576 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
579 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
581 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
582 midi_source(0)->invalidate (lm);
586 /** This is called when a trim drag has resulted in a -ve _start time for this region.
587 * Fix it up by adding some empty space to the source.
590 MidiRegion::fix_negative_start ()
592 BeatsFramesConverter c (_session.tempo_map(), _position);
594 model()->insert_silence_at_start (c.from (-_start));
600 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
602 Region::set_start_internal (s, sub_num);
603 set_start_beats_from_start_frames ();
607 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
613 PropertyChange what_changed;
616 /* Set position before length, otherwise for MIDI regions this bad thing happens:
617 * 1. we call set_length_internal; length in beats is computed using the region's current
618 * (soon-to-be old) position
619 * 2. we call set_position_internal; position is set and length in frames re-computed using
620 * length in beats from (1) but at the new position, which is wrong if the region
621 * straddles a tempo/meter change.
624 if (_position != position) {
626 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
627 const double old_pos_qn = quarter_note();
629 /* sets _pulse to new position.*/
630 set_position_internal (position, true, sub_num);
631 what_changed.add (Properties::position);
633 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
634 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
636 if (!verify_start_and_length (new_start, length)) {
640 _start_beats = new_start_qn;
641 what_changed.add (Properties::start_beats);
643 set_start_internal (new_start, sub_num);
644 what_changed.add (Properties::start);
647 if (_length != length) {
649 if (!verify_start_and_length (_start, length)) {
653 set_length_internal (length, sub_num);
654 what_changed.add (Properties::length);
655 what_changed.add (Properties::length_beats);
658 set_whole_file (false);
660 PropertyChange start_and_length;
662 start_and_length.add (Properties::start);
663 start_and_length.add (Properties::length);
665 if (what_changed.contains (start_and_length)) {
669 if (!what_changed.empty()) {
670 send_change (what_changed);