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, MusicFrame offset)
106 : Region (other, offset)
107 , _start_beats (Properties::start_beats, other->_start_beats)
108 , _length_beats (Properties::length_beats, other->_length_beats)
111 register_properties ();
113 const double offset_quarter_note = _session.tempo_map().exact_qn_at_frame (other->_position + offset.frame, offset.division) - other->_quarter_note;
114 if (offset.frame != 0) {
115 _start_beats = other->_start_beats + offset_quarter_note;
116 _length_beats = other->_length_beats - offset_quarter_note;
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::position, _position);
207 plist.add (Properties::beat, _beat);
208 plist.add (Properties::length_beats, _length_beats);
209 plist.add (Properties::layer, 0);
211 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
212 ret->set_quarter_note (quarter_note());
218 MidiRegion::post_set (const PropertyChange& pc)
220 Region::post_set (pc);
222 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
223 /* we're called by Stateful::set_values() which sends a change
224 only if the value is different from _current.
225 session load means we can clobber length_beats here in error (not all properties differ from current),
226 so disallow (this has been set from XML state anyway).
228 if (!_session.loading()) {
229 update_length_beats (0);
233 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
234 set_start_beats_from_start_frames ();
239 MidiRegion::set_start_beats_from_start_frames ()
241 if (position_lock_style() == AudioTime) {
242 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_frame (_position - _start);
247 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
249 Region::set_length_internal (len, sub_num);
250 update_length_beats (sub_num);
254 MidiRegion::update_after_tempo_map_change (bool /* send */)
256 boost::shared_ptr<Playlist> pl (playlist());
262 const framepos_t old_pos = _position;
263 const framepos_t old_length = _length;
264 const framepos_t old_start = _start;
266 PropertyChange s_and_l;
268 if (position_lock_style() == AudioTime) {
269 recompute_position_from_lock_style (0);
272 set _start to new position in tempo map.
274 The user probably expects the region contents to maintain audio position as the
275 tempo changes, but AFAICT this requires modifying the src file to use
276 SMPTE timestamps with the current disk read model (?).
278 We could arguably use _start to set _start_beats here,
279 resulting in viewport-like behaviour (the contents maintain
280 their musical position while the region is stationary).
282 For now, the musical position at the region start is retained, but subsequent events
283 will maintain their beat distance according to the map.
285 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
287 /* _length doesn't change for audio-locked regions. update length_beats to match. */
288 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
290 s_and_l.add (Properties::start);
291 s_and_l.add (Properties::length_beats);
293 send_change (s_and_l);
297 Region::update_after_tempo_map_change (false);
299 /* _start has now been updated. */
300 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
302 if (old_start != _start) {
303 s_and_l.add (Properties::start);
305 if (old_length != _length) {
306 s_and_l.add (Properties::length);
308 if (old_pos != _position) {
309 s_and_l.add (Properties::position);
312 send_change (s_and_l);
316 MidiRegion::update_length_beats (const int32_t sub_num)
318 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - quarter_note();
322 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
324 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
326 /* don't clobber _start _length and _length_beats if session loading.*/
327 if (_session.loading()) {
331 /* set _start to new position in tempo map */
332 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
334 /* in construction from src */
335 if (_length_beats == 0.0) {
336 update_length_beats (sub_num);
339 if (position_lock_style() == AudioTime) {
340 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
342 /* leave _length_beats alone, and change _length to reflect the state of things
343 at the new position (tempo map may dictate a different number of frames).
345 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
350 MidiRegion::set_position_music_internal (double qn)
352 Region::set_position_music_internal (qn);
353 /* set _start to new position in tempo map */
354 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
356 if (position_lock_style() == AudioTime) {
357 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
360 /* leave _length_beats alone, and change _length to reflect the state of things
361 at the new position (tempo map may dictate a different number of frames).
363 _length = _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats());
368 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
371 Evoral::Range<framepos_t>* loop_range,
375 MidiStateTracker* tracker,
376 MidiChannelFilter* filter) const
378 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
382 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
385 Evoral::Range<framepos_t>* loop_range,
390 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
394 MidiRegion::_read_at (const SourceList& /*srcs*/,
395 Evoral::EventSink<framepos_t>& dst,
398 Evoral::Range<framepos_t>* loop_range,
402 MidiStateTracker* tracker,
403 MidiChannelFilter* filter) const
405 frameoffset_t internal_offset = 0;
406 framecnt_t to_read = 0;
408 /* precondition: caller has verified that we cover the desired section */
413 return 0; /* read nothing */
416 if (position < _position) {
417 /* we are starting the read from before the start of the region */
419 dur -= _position - position;
421 /* we are starting the read from after the start of the region */
422 internal_offset = position - _position;
425 if (internal_offset >= _length) {
426 return 0; /* read nothing */
429 if ((to_read = min (dur, _length - internal_offset)) == 0) {
430 return 0; /* read nothing */
433 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
435 Glib::Threads::Mutex::Lock lm(src->mutex());
437 src->set_note_mode(lm, mode);
440 cerr << "MR " << name () << " read @ " << position << " + " << to_read
441 << " dur was " << dur
442 << " len " << _length
443 << " l-io " << (_length - internal_offset)
444 << " _position = " << _position
445 << " _start = " << _start
446 << " intoffset = " << internal_offset
447 << " quarter_note = " << quarter_note()
448 << " start_beat = " << _start_beats
452 /* This call reads events from a source and writes them to `dst' timed in session frames */
456 dst, // destination buffer
457 _position - _start, // start position of the source in session frames
458 _start + internal_offset, // where to start reading in the source
459 to_read, // read duration in frames
464 _filtered_parameters,
468 return 0; /* "read nothing" */
477 return Region::state ();
481 MidiRegion::set_state (const XMLNode& node, int version)
483 int ret = Region::set_state (node, version);
489 MidiRegion::recompute_at_end ()
491 /* our length has changed
492 * so what? stuck notes are dealt with via a note state tracker
497 MidiRegion::recompute_at_start ()
499 /* as above, but the shift was from the front
500 * maybe bump currently active note's note-ons up so they sound here?
501 * that could be undesireable in certain situations though.. maybe
502 * remove the note entirely, including it's note off? something needs to
503 * be done to keep the played MIDI sane to avoid messing up voices of
504 * polyhonic things etc........
509 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
515 boost::shared_ptr<Evoral::Control>
516 MidiRegion::control (const Evoral::Parameter& id, bool create)
518 return model()->control(id, create);
521 boost::shared_ptr<const Evoral::Control>
522 MidiRegion::control (const Evoral::Parameter& id) const
524 return model()->control(id);
527 boost::shared_ptr<MidiModel>
530 return midi_source()->model();
533 boost::shared_ptr<const MidiModel>
534 MidiRegion::model() const
536 return midi_source()->model();
539 boost::shared_ptr<MidiSource>
540 MidiRegion::midi_source (uint32_t n) const
542 // Guaranteed to succeed (use a static cast?)
543 return boost::dynamic_pointer_cast<MidiSource>(source(n));
546 /* don't use this. hopefully it will go away.
547 currently used by headless-chicken session utility.
550 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
554 _sources.push_back (s);
556 _master_sources.push_back (s);
559 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
564 MidiRegion::model_changed ()
570 /* build list of filtered Parameters, being those whose automation state is not `Play' */
572 _filtered_parameters.clear ();
574 Automatable::Controls const & c = model()->controls();
576 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
577 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
579 if (ac->alist()->automation_state() != Play) {
580 _filtered_parameters.insert (ac->parameter ());
584 /* watch for changes to controls' AutoState */
585 midi_source()->AutomationStateChanged.connect_same_thread (
586 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
591 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
593 /* Update our filtered parameters list after a change to a parameter's AutoState */
595 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
596 if (!ac || ac->alist()->automation_state() == Play) {
597 /* It should be "impossible" for ac to be NULL, but if it is, don't
598 filter the parameter so events aren't lost. */
599 _filtered_parameters.erase (p);
601 _filtered_parameters.insert (p);
604 /* the source will have an iterator into the model, and that iterator will have been set up
605 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
608 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
610 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
611 midi_source(0)->invalidate (lm);
615 /** This is called when a trim drag has resulted in a -ve _start time for this region.
616 * Fix it up by adding some empty space to the source.
619 MidiRegion::fix_negative_start ()
621 BeatsFramesConverter c (_session.tempo_map(), _position);
623 model()->insert_silence_at_start (c.from (-_start));
629 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
631 Region::set_start_internal (s, sub_num);
632 set_start_beats_from_start_frames ();
636 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
642 PropertyChange what_changed;
645 /* Set position before length, otherwise for MIDI regions this bad thing happens:
646 * 1. we call set_length_internal; length in beats is computed using the region's current
647 * (soon-to-be old) position
648 * 2. we call set_position_internal; position is set and length in frames re-computed using
649 * length in beats from (1) but at the new position, which is wrong if the region
650 * straddles a tempo/meter change.
653 if (_position != position) {
655 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
656 const double old_pos_qn = quarter_note();
658 /* sets _pulse to new position.*/
659 set_position_internal (position, true, sub_num);
660 what_changed.add (Properties::position);
662 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
663 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
665 if (!verify_start_and_length (new_start, length)) {
669 _start_beats = new_start_qn;
670 what_changed.add (Properties::start_beats);
672 set_start_internal (new_start, sub_num);
673 what_changed.add (Properties::start);
676 if (_length != length) {
678 if (!verify_start_and_length (_start, length)) {
682 set_length_internal (length, sub_num);
683 what_changed.add (Properties::length);
684 what_changed.add (Properties::length_beats);
687 set_whole_file (false);
689 PropertyChange start_and_length;
691 start_and_length.add (Properties::start);
692 start_and_length.add (Properties::length);
694 if (what_changed.contains (start_and_length)) {
698 if (!what_changed.empty()) {
699 send_change (what_changed);