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);
113 /* we've potentially shifted _start_beats, now reset _start frames to match */
114 _start = _session.tempo_map().frames_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
117 register_properties ();
119 assert(_name.val().find("/") == string::npos);
120 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
124 MidiRegion::~MidiRegion ()
128 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
131 MidiRegion::do_export (string path) const
133 boost::shared_ptr<MidiSource> newsrc;
135 /* caller must check for pre-existing file */
136 assert (!path.empty());
137 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
138 newsrc = boost::dynamic_pointer_cast<MidiSource>(
139 SourceFactory::createWritable(DataType::MIDI, _session,
140 path, false, _session.frame_rate()));
142 BeatsFramesConverter bfc (_session.tempo_map(), _position);
143 Evoral::Beats const bbegin = bfc.from (_start);
144 Evoral::Beats const bend = bfc.from (_start + _length);
147 /* Lock our source since we'll be reading from it. write_to() will
148 take a lock on newsrc. */
149 Source::Lock lm (midi_source(0)->mutex());
150 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
159 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
161 boost::shared_ptr<MidiRegion>
162 MidiRegion::clone (string path) const
164 boost::shared_ptr<MidiSource> newsrc;
166 /* caller must check for pre-existing file */
167 assert (!path.empty());
168 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
169 newsrc = boost::dynamic_pointer_cast<MidiSource>(
170 SourceFactory::createWritable(DataType::MIDI, _session,
171 path, false, _session.frame_rate()));
172 return clone (newsrc);
175 boost::shared_ptr<MidiRegion>
176 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
178 BeatsFramesConverter bfc (_session.tempo_map(), _position);
179 Evoral::Beats const bbegin = bfc.from (_start);
180 Evoral::Beats const bend = bfc.from (_start + _length);
183 boost::shared_ptr<MidiSource> ms = midi_source(0);
184 Source::Lock lm (ms->mutex());
190 /* Lock our source since we'll be reading from it. write_to() will
191 take a lock on newsrc.
194 if (ms->write_to (lm, newsrc, bbegin, bend)) {
195 return boost::shared_ptr<MidiRegion> ();
201 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
202 plist.add (Properties::whole_file, true);
203 plist.add (Properties::start, _start);
204 plist.add (Properties::start_beats, _start_beats);
205 plist.add (Properties::length, _length);
206 plist.add (Properties::beat, _beat);
207 plist.add (Properties::length_beats, _length_beats);
208 plist.add (Properties::layer, 0);
210 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
211 ret->set_quarter_note (quarter_note());
217 MidiRegion::post_set (const PropertyChange& pc)
219 Region::post_set (pc);
221 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
222 /* we're called by Stateful::set_values() which sends a change
223 only if the value is different from _current.
224 session load means we can clobber length_beats here in error (not all properties differ from current),
225 so disallow (this has been set from XML state anyway).
227 if (!_session.loading()) {
228 update_length_beats (0);
232 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
233 set_start_beats_from_start_frames ();
238 MidiRegion::set_start_beats_from_start_frames ()
240 if (position_lock_style() == AudioTime) {
241 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_frame (_position - _start);
246 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
248 Region::set_length_internal (len, sub_num);
249 update_length_beats (sub_num);
253 MidiRegion::update_after_tempo_map_change (bool /* send */)
255 boost::shared_ptr<Playlist> pl (playlist());
261 const framepos_t old_pos = _position;
262 const framepos_t old_length = _length;
263 const framepos_t old_start = _start;
265 PropertyChange s_and_l;
267 if (position_lock_style() == AudioTime) {
268 recompute_position_from_lock_style (0);
271 set _start to new position in tempo map.
273 The user probably expects the region contents to maintain audio position as the
274 tempo changes, but AFAICT this requires modifying the src file to use
275 SMPTE timestamps with the current disk read model (?).
277 We could arguably use _start to set _start_beats here,
278 resulting in viewport-like behaviour (the contents maintain
279 their musical position while the region is stationary).
281 For now, the musical position at the region start is retained, but subsequent events
282 will maintain their beat distance according to the map.
284 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
286 /* _length doesn't change for audio-locked regions. update length_beats to match. */
287 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
289 s_and_l.add (Properties::start);
290 s_and_l.add (Properties::length_beats);
292 send_change (s_and_l);
296 Region::update_after_tempo_map_change (false);
298 /* _start has now been updated. */
299 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
301 if (old_start != _start) {
302 s_and_l.add (Properties::start);
304 if (old_length != _length) {
305 s_and_l.add (Properties::length);
307 if (old_pos != _position) {
308 s_and_l.add (Properties::position);
311 send_change (s_and_l);
315 MidiRegion::update_length_beats (const int32_t sub_num)
317 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - quarter_note();
321 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
323 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
325 /* don't clobber _start _length and _length_beats if session loading.*/
326 if (_session.loading()) {
330 /* set _start to new position in tempo map */
331 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
333 /* in construction from src */
334 if (_length_beats == 0.0) {
335 update_length_beats (sub_num);
338 if (position_lock_style() == AudioTime) {
339 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
341 /* leave _length_beats alone, and change _length to reflect the state of things
342 at the new position (tempo map may dictate a different number of frames).
344 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
349 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
352 Evoral::Range<framepos_t>* loop_range,
356 MidiStateTracker* tracker,
357 MidiChannelFilter* filter) const
359 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
363 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
366 Evoral::Range<framepos_t>* loop_range,
371 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
375 MidiRegion::_read_at (const SourceList& /*srcs*/,
376 Evoral::EventSink<framepos_t>& dst,
379 Evoral::Range<framepos_t>* loop_range,
383 MidiStateTracker* tracker,
384 MidiChannelFilter* filter) const
386 frameoffset_t internal_offset = 0;
387 framecnt_t to_read = 0;
389 /* precondition: caller has verified that we cover the desired section */
394 return 0; /* read nothing */
397 if (position < _position) {
398 /* we are starting the read from before the start of the region */
400 dur -= _position - position;
402 /* we are starting the read from after the start of the region */
403 internal_offset = position - _position;
406 if (internal_offset >= _length) {
407 return 0; /* read nothing */
410 if ((to_read = min (dur, _length - internal_offset)) == 0) {
411 return 0; /* read nothing */
414 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
416 Glib::Threads::Mutex::Lock lm(src->mutex());
418 src->set_note_mode(lm, mode);
421 cerr << "MR " << name () << " read @ " << position << " + " << to_read
422 << " dur was " << dur
423 << " len " << _length
424 << " l-io " << (_length - internal_offset)
425 << " _position = " << _position
426 << " _start = " << _start
427 << " intoffset = " << internal_offset
428 << " quarter_note = " << quarter_note()
429 << " start_beat = " << _start_beats
433 /* This call reads events from a source and writes them to `dst' timed in session frames */
437 dst, // destination buffer
438 _position - _start, // start position of the source in session frames
439 _start + internal_offset, // where to start reading in the source
440 to_read, // read duration in frames
445 _filtered_parameters,
449 return 0; /* "read nothing" */
458 return Region::state ();
462 MidiRegion::set_state (const XMLNode& node, int version)
464 int ret = Region::set_state (node, version);
470 MidiRegion::recompute_at_end ()
472 /* our length has changed
473 * so what? stuck notes are dealt with via a note state tracker
478 MidiRegion::recompute_at_start ()
480 /* as above, but the shift was from the front
481 * maybe bump currently active note's note-ons up so they sound here?
482 * that could be undesireable in certain situations though.. maybe
483 * remove the note entirely, including it's note off? something needs to
484 * be done to keep the played MIDI sane to avoid messing up voices of
485 * polyhonic things etc........
490 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
496 boost::shared_ptr<Evoral::Control>
497 MidiRegion::control (const Evoral::Parameter& id, bool create)
499 return model()->control(id, create);
502 boost::shared_ptr<const Evoral::Control>
503 MidiRegion::control (const Evoral::Parameter& id) const
505 return model()->control(id);
508 boost::shared_ptr<MidiModel>
511 return midi_source()->model();
514 boost::shared_ptr<const MidiModel>
515 MidiRegion::model() const
517 return midi_source()->model();
520 boost::shared_ptr<MidiSource>
521 MidiRegion::midi_source (uint32_t n) const
523 // Guaranteed to succeed (use a static cast?)
524 return boost::dynamic_pointer_cast<MidiSource>(source(n));
527 /* don't use this. hopefully it will go away.
528 currently used by headless-chicken session utility.
531 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
535 _sources.push_back (s);
537 _master_sources.push_back (s);
540 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
545 MidiRegion::model_changed ()
551 /* build list of filtered Parameters, being those whose automation state is not `Play' */
553 _filtered_parameters.clear ();
555 Automatable::Controls const & c = model()->controls();
557 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
558 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
560 if (ac->alist()->automation_state() != Play) {
561 _filtered_parameters.insert (ac->parameter ());
565 /* watch for changes to controls' AutoState */
566 midi_source()->AutomationStateChanged.connect_same_thread (
567 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
572 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
574 /* Update our filtered parameters list after a change to a parameter's AutoState */
576 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
577 if (!ac || ac->alist()->automation_state() == Play) {
578 /* It should be "impossible" for ac to be NULL, but if it is, don't
579 filter the parameter so events aren't lost. */
580 _filtered_parameters.erase (p);
582 _filtered_parameters.insert (p);
585 /* the source will have an iterator into the model, and that iterator will have been set up
586 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
589 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
591 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
592 midi_source(0)->invalidate (lm);
596 /** This is called when a trim drag has resulted in a -ve _start time for this region.
597 * Fix it up by adding some empty space to the source.
600 MidiRegion::fix_negative_start ()
602 BeatsFramesConverter c (_session.tempo_map(), _position);
604 model()->insert_silence_at_start (c.from (-_start));
610 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
612 Region::set_start_internal (s, sub_num);
613 set_start_beats_from_start_frames ();
617 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
623 PropertyChange what_changed;
626 /* Set position before length, otherwise for MIDI regions this bad thing happens:
627 * 1. we call set_length_internal; length in beats is computed using the region's current
628 * (soon-to-be old) position
629 * 2. we call set_position_internal; position is set and length in frames re-computed using
630 * length in beats from (1) but at the new position, which is wrong if the region
631 * straddles a tempo/meter change.
634 if (_position != position) {
636 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
637 const double old_pos_qn = quarter_note();
639 /* sets _pulse to new position.*/
640 set_position_internal (position, true, sub_num);
641 what_changed.add (Properties::position);
643 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
644 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
646 if (!verify_start_and_length (new_start, length)) {
650 _start_beats = new_start_qn;
651 what_changed.add (Properties::start_beats);
653 set_start_internal (new_start, sub_num);
654 what_changed.add (Properties::start);
657 if (_length != length) {
659 if (!verify_start_and_length (_start, length)) {
663 set_length_internal (length, sub_num);
664 what_changed.add (Properties::length);
665 what_changed.add (Properties::length_beats);
668 set_whole_file (false);
670 PropertyChange start_and_length;
672 start_and_length.add (Properties::start);
673 start_and_length.add (Properties::length);
675 if (what_changed.contains (start_and_length)) {
679 if (!what_changed.empty()) {
680 send_change (what_changed);