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, 0.0)
108 , _length_beats (Properties::length_beats, other->_length_beats)
110 _start_beats = (_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) - other->pos_beats()) + other->_start_beats;
112 update_length_beats (sub_num);
113 register_properties ();
115 assert(_name.val().find("/") == string::npos);
116 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
120 MidiRegion::~MidiRegion ()
124 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
127 MidiRegion::do_export (string path) const
129 boost::shared_ptr<MidiSource> newsrc;
131 /* caller must check for pre-existing file */
132 assert (!path.empty());
133 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
134 newsrc = boost::dynamic_pointer_cast<MidiSource>(
135 SourceFactory::createWritable(DataType::MIDI, _session,
136 path, false, _session.frame_rate()));
138 BeatsFramesConverter bfc (_session.tempo_map(), _position);
139 Evoral::Beats const bbegin = bfc.from (_start);
140 Evoral::Beats const bend = bfc.from (_start + _length);
143 /* Lock our source since we'll be reading from it. write_to() will
144 take a lock on newsrc. */
145 Source::Lock lm (midi_source(0)->mutex());
146 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
155 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
157 boost::shared_ptr<MidiRegion>
158 MidiRegion::clone (string path) const
160 boost::shared_ptr<MidiSource> newsrc;
162 /* caller must check for pre-existing file */
163 assert (!path.empty());
164 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
165 newsrc = boost::dynamic_pointer_cast<MidiSource>(
166 SourceFactory::createWritable(DataType::MIDI, _session,
167 path, false, _session.frame_rate()));
168 return clone (newsrc);
171 boost::shared_ptr<MidiRegion>
172 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
174 BeatsFramesConverter bfc (_session.tempo_map(), _position);
175 Evoral::Beats const bbegin = bfc.from (_start);
176 Evoral::Beats const bend = bfc.from (_start + _length);
179 /* Lock our source since we'll be reading from it. write_to() will
180 take a lock on newsrc. */
181 Source::Lock lm (midi_source(0)->mutex());
182 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
183 return boost::shared_ptr<MidiRegion> ();
189 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
190 plist.add (Properties::whole_file, true);
191 plist.add (Properties::start, _start);
192 plist.add (Properties::start_beats, _start_beats);
193 plist.add (Properties::length, _length);
194 plist.add (Properties::beat, _beat);
195 plist.add (Properties::length_beats, _length_beats);
196 plist.add (Properties::layer, 0);
198 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
199 ret->set_pos_beats (pos_beats());
205 MidiRegion::post_set (const PropertyChange& pc)
207 Region::post_set (pc);
209 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
210 /* we're called by Stateful::set_values() which sends a change
211 only if the value is different from _current.
212 session load means we can clobber length_beats here in error (not all properties differ from current),
213 so disallow (this has been set from XML state anyway).
215 if (!_session.loading()) {
216 /* ensure this only updates non-musical regions */
217 if (position_lock_style() == AudioTime) {
218 update_length_beats (0);
221 } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
222 set_start_beats_from_start_frames ();
227 MidiRegion::set_start_beats_from_start_frames ()
229 if (position_lock_style() == AudioTime) {
230 _start_beats = pos_beats() - _session.tempo_map().quarter_note_at_frame (_position - _start);
235 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
237 Region::set_length_internal (len, sub_num);
238 update_length_beats (sub_num);
242 MidiRegion::update_after_tempo_map_change (bool /* send */)
244 boost::shared_ptr<Playlist> pl (playlist());
250 const framepos_t old_pos = _position;
251 const framepos_t old_length = _length;
252 const framepos_t old_start = _start;
254 PropertyChange s_and_l;
256 if (position_lock_style() == AudioTime) {
257 recompute_position_from_lock_style (0);
260 set _start to new position in tempo map.
262 The user probably expects the region contents to maintain audio position as the
263 tempo changes, but AFAICT this requires modifying the src file to use
264 SMPTE timestamps with the current disk read model (?).
266 We could arguably use _start to set _start_beats here,
267 resulting in viewport-like behaviour (the contents maintain
268 their musical position while the region is stationary).
270 For now, the musical position at the region start is retained, but subsequent events
271 will maintain their beat distance according to the map.
273 _start = _session.tempo_map().frames_between_quarter_notes (pos_beats() - start_beats(), pos_beats());
275 /* _length doesn't change for audio-locked regions. update length_beats to match. */
276 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - pos_beats();
278 s_and_l.add (Properties::start);
279 s_and_l.add (Properties::length_beats);
281 send_change (s_and_l);
285 Region::update_after_tempo_map_change (false);
287 /* _start has now been updated. */
288 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (pos_beats(), pos_beats() + _length_beats));
290 if (old_start != _start) {
291 s_and_l.add (Properties::start);
293 if (old_length != _length) {
294 s_and_l.add (Properties::length);
296 if (old_pos != _position) {
297 s_and_l.add (Properties::position);
300 send_change (s_and_l);
304 MidiRegion::update_length_beats (const int32_t sub_num)
306 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - pos_beats();
310 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
312 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
314 /* don't clobber _start _length and _length_beats if session loading.*/
315 if (_session.loading()) {
319 /* set _start to new position in tempo map */
320 _start = _session.tempo_map().frames_between_quarter_notes (pos_beats() - start_beats(), pos_beats());
322 /* in construction from src */
323 if (_length_beats == 0.0) {
324 update_length_beats (sub_num);
327 if (position_lock_style() == AudioTime) {
328 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - pos_beats();
330 /* leave _length_beats alone, and change _length to reflect the state of things
331 at the new position (tempo map may dictate a different number of frames).
333 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (pos_beats(), pos_beats() + length_beats()), sub_num);
338 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
341 Evoral::Range<framepos_t>* loop_range,
345 MidiStateTracker* tracker,
346 MidiChannelFilter* filter) const
348 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
352 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
355 Evoral::Range<framepos_t>* loop_range,
360 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
364 MidiRegion::_read_at (const SourceList& /*srcs*/,
365 Evoral::EventSink<framepos_t>& dst,
368 Evoral::Range<framepos_t>* loop_range,
372 MidiStateTracker* tracker,
373 MidiChannelFilter* filter) const
375 frameoffset_t internal_offset = 0;
376 framecnt_t to_read = 0;
378 /* precondition: caller has verified that we cover the desired section */
383 return 0; /* read nothing */
386 if (position < _position) {
387 /* we are starting the read from before the start of the region */
389 dur -= _position - position;
391 /* we are starting the read from after the start of the region */
392 internal_offset = position - _position;
395 if (internal_offset >= _length) {
396 return 0; /* read nothing */
399 if ((to_read = min (dur, _length - internal_offset)) == 0) {
400 return 0; /* read nothing */
403 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
405 Glib::Threads::Mutex::Lock lm(src->mutex());
407 src->set_note_mode(lm, mode);
410 cerr << "MR " << name () << " read @ " << position << " + " << to_read
411 << " dur was " << dur
412 << " len " << _length
413 << " l-io " << (_length - internal_offset)
414 << " _position = " << _position
415 << " _start = " << _start
416 << " intoffset = " << internal_offset
417 << " pos_beats = " << pos_beats()
418 << " start_beat = " << _start_beats
422 /* This call reads events from a source and writes them to `dst' timed in session frames */
426 dst, // destination buffer
427 _position - _start, // start position of the source in session frames
428 _start + internal_offset, // where to start reading in the source
429 to_read, // read duration in frames
434 _filtered_parameters,
438 return 0; /* "read nothing" */
447 return Region::state ();
451 MidiRegion::set_state (const XMLNode& node, int version)
453 int ret = Region::set_state (node, version);
459 MidiRegion::recompute_at_end ()
461 /* our length has changed
462 * so what? stuck notes are dealt with via a note state tracker
467 MidiRegion::recompute_at_start ()
469 /* as above, but the shift was from the front
470 * maybe bump currently active note's note-ons up so they sound here?
471 * that could be undesireable in certain situations though.. maybe
472 * remove the note entirely, including it's note off? something needs to
473 * be done to keep the played MIDI sane to avoid messing up voices of
474 * polyhonic things etc........
479 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
485 boost::shared_ptr<Evoral::Control>
486 MidiRegion::control (const Evoral::Parameter& id, bool create)
488 return model()->control(id, create);
491 boost::shared_ptr<const Evoral::Control>
492 MidiRegion::control (const Evoral::Parameter& id) const
494 return model()->control(id);
497 boost::shared_ptr<MidiModel>
500 return midi_source()->model();
503 boost::shared_ptr<const MidiModel>
504 MidiRegion::model() const
506 return midi_source()->model();
509 boost::shared_ptr<MidiSource>
510 MidiRegion::midi_source (uint32_t n) const
512 // Guaranteed to succeed (use a static cast?)
513 return boost::dynamic_pointer_cast<MidiSource>(source(n));
516 /* don't use this. hopefully it will go away.
517 currently used by headless-chicken session utility.
520 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
524 _sources.push_back (s);
526 _master_sources.push_back (s);
529 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
534 MidiRegion::model_changed ()
540 /* build list of filtered Parameters, being those whose automation state is not `Play' */
542 _filtered_parameters.clear ();
544 Automatable::Controls const & c = model()->controls();
546 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
547 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
549 if (ac->alist()->automation_state() != Play) {
550 _filtered_parameters.insert (ac->parameter ());
554 /* watch for changes to controls' AutoState */
555 midi_source()->AutomationStateChanged.connect_same_thread (
556 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
561 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
563 /* Update our filtered parameters list after a change to a parameter's AutoState */
565 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
566 if (!ac || ac->alist()->automation_state() == Play) {
567 /* It should be "impossible" for ac to be NULL, but if it is, don't
568 filter the parameter so events aren't lost. */
569 _filtered_parameters.erase (p);
571 _filtered_parameters.insert (p);
574 /* the source will have an iterator into the model, and that iterator will have been set up
575 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
578 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
580 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
581 midi_source(0)->invalidate (lm);
585 /** This is called when a trim drag has resulted in a -ve _start time for this region.
586 * Fix it up by adding some empty space to the source.
589 MidiRegion::fix_negative_start ()
591 BeatsFramesConverter c (_session.tempo_map(), _position);
593 model()->insert_silence_at_start (c.from (-_start));
599 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
601 Region::set_start_internal (s, sub_num);
602 set_start_beats_from_start_frames ();
606 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
612 PropertyChange what_changed;
615 /* Set position before length, otherwise for MIDI regions this bad thing happens:
616 * 1. we call set_length_internal; length in beats is computed using the region's current
617 * (soon-to-be old) position
618 * 2. we call set_position_internal; position is set and length in frames re-computed using
619 * length in beats from (1) but at the new position, which is wrong if the region
620 * straddles a tempo/meter change.
623 if (_position != position) {
625 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
626 const double old_pos_qn = pos_beats();
628 /* sets _pulse to new position.*/
629 set_position_internal (position, true, sub_num);
630 what_changed.add (Properties::position);
632 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
633 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
635 if (!verify_start_and_length (new_start, length)) {
639 _start_beats = new_start_qn;
640 what_changed.add (Properties::start_beats);
642 set_start_internal (new_start, sub_num);
643 what_changed.add (Properties::start);
646 if (_length != length) {
648 if (!verify_start_and_length (_start, length)) {
652 set_length_internal (length, sub_num);
653 what_changed.add (Properties::length);
654 what_changed.add (Properties::length_beats);
657 set_whole_file (false);
659 PropertyChange start_and_length;
661 start_and_length.add (Properties::start);
662 start_and_length.add (Properties::length);
664 if (what_changed.contains (start_and_length)) {
668 if (!what_changed.empty()) {
669 send_change (what_changed);