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 boost::shared_ptr<MidiSource> ms = midi_source(0);
182 Source::Lock lm (ms->mutex());
188 /* Lock our source since we'll be reading from it. write_to() will
189 take a lock on newsrc.
192 if (ms->write_to (lm, newsrc, bbegin, bend)) {
193 return boost::shared_ptr<MidiRegion> ();
199 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
200 plist.add (Properties::whole_file, true);
201 plist.add (Properties::start, _start);
202 plist.add (Properties::start_beats, _start_beats);
203 plist.add (Properties::length, _length);
204 plist.add (Properties::beat, _beat);
205 plist.add (Properties::length_beats, _length_beats);
206 plist.add (Properties::layer, 0);
208 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
209 ret->set_quarter_note (quarter_note());
215 MidiRegion::post_set (const PropertyChange& pc)
217 Region::post_set (pc);
219 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
220 /* we're called by Stateful::set_values() which sends a change
221 only if the value is different from _current.
222 session load means we can clobber length_beats here in error (not all properties differ from current),
223 so disallow (this has been set from XML state anyway).
225 if (!_session.loading()) {
226 update_length_beats (0);
230 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
231 set_start_beats_from_start_frames ();
236 MidiRegion::set_start_beats_from_start_frames ()
238 if (position_lock_style() == AudioTime) {
239 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_frame (_position - _start);
244 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
246 Region::set_length_internal (len, sub_num);
247 update_length_beats (sub_num);
251 MidiRegion::update_after_tempo_map_change (bool /* send */)
253 boost::shared_ptr<Playlist> pl (playlist());
259 const framepos_t old_pos = _position;
260 const framepos_t old_length = _length;
261 const framepos_t old_start = _start;
263 PropertyChange s_and_l;
265 if (position_lock_style() == AudioTime) {
266 recompute_position_from_lock_style (0);
269 set _start to new position in tempo map.
271 The user probably expects the region contents to maintain audio position as the
272 tempo changes, but AFAICT this requires modifying the src file to use
273 SMPTE timestamps with the current disk read model (?).
275 We could arguably use _start to set _start_beats here,
276 resulting in viewport-like behaviour (the contents maintain
277 their musical position while the region is stationary).
279 For now, the musical position at the region start is retained, but subsequent events
280 will maintain their beat distance according to the map.
282 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
284 /* _length doesn't change for audio-locked regions. update length_beats to match. */
285 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
287 s_and_l.add (Properties::start);
288 s_and_l.add (Properties::length_beats);
290 send_change (s_and_l);
294 Region::update_after_tempo_map_change (false);
296 /* _start has now been updated. */
297 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
299 if (old_start != _start) {
300 s_and_l.add (Properties::start);
302 if (old_length != _length) {
303 s_and_l.add (Properties::length);
305 if (old_pos != _position) {
306 s_and_l.add (Properties::position);
309 send_change (s_and_l);
313 MidiRegion::update_length_beats (const int32_t sub_num)
315 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - quarter_note();
319 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
321 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
323 /* don't clobber _start _length and _length_beats if session loading.*/
324 if (_session.loading()) {
328 /* set _start to new position in tempo map */
329 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
331 /* in construction from src */
332 if (_length_beats == 0.0) {
333 update_length_beats (sub_num);
336 if (position_lock_style() == AudioTime) {
337 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
339 /* leave _length_beats alone, and change _length to reflect the state of things
340 at the new position (tempo map may dictate a different number of frames).
342 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
347 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
350 Evoral::Range<framepos_t>* loop_range,
354 MidiStateTracker* tracker,
355 MidiChannelFilter* filter) const
357 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
361 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
364 Evoral::Range<framepos_t>* loop_range,
369 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
373 MidiRegion::_read_at (const SourceList& /*srcs*/,
374 Evoral::EventSink<framepos_t>& dst,
377 Evoral::Range<framepos_t>* loop_range,
381 MidiStateTracker* tracker,
382 MidiChannelFilter* filter) const
384 frameoffset_t internal_offset = 0;
385 framecnt_t to_read = 0;
387 /* precondition: caller has verified that we cover the desired section */
392 return 0; /* read nothing */
395 if (position < _position) {
396 /* we are starting the read from before the start of the region */
398 dur -= _position - position;
400 /* we are starting the read from after the start of the region */
401 internal_offset = position - _position;
404 if (internal_offset >= _length) {
405 return 0; /* read nothing */
408 if ((to_read = min (dur, _length - internal_offset)) == 0) {
409 return 0; /* read nothing */
412 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
414 Glib::Threads::Mutex::Lock lm(src->mutex());
416 src->set_note_mode(lm, mode);
419 cerr << "MR " << name () << " read @ " << position << " + " << to_read
420 << " dur was " << dur
421 << " len " << _length
422 << " l-io " << (_length - internal_offset)
423 << " _position = " << _position
424 << " _start = " << _start
425 << " intoffset = " << internal_offset
426 << " quarter_note = " << quarter_note()
427 << " start_beat = " << _start_beats
431 /* This call reads events from a source and writes them to `dst' timed in session frames */
435 dst, // destination buffer
436 _position - _start, // start position of the source in session frames
437 _start + internal_offset, // where to start reading in the source
438 to_read, // read duration in frames
443 _filtered_parameters,
447 return 0; /* "read nothing" */
456 return Region::state ();
460 MidiRegion::set_state (const XMLNode& node, int version)
462 int ret = Region::set_state (node, version);
468 MidiRegion::recompute_at_end ()
470 /* our length has changed
471 * so what? stuck notes are dealt with via a note state tracker
476 MidiRegion::recompute_at_start ()
478 /* as above, but the shift was from the front
479 * maybe bump currently active note's note-ons up so they sound here?
480 * that could be undesireable in certain situations though.. maybe
481 * remove the note entirely, including it's note off? something needs to
482 * be done to keep the played MIDI sane to avoid messing up voices of
483 * polyhonic things etc........
488 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
494 boost::shared_ptr<Evoral::Control>
495 MidiRegion::control (const Evoral::Parameter& id, bool create)
497 return model()->control(id, create);
500 boost::shared_ptr<const Evoral::Control>
501 MidiRegion::control (const Evoral::Parameter& id) const
503 return model()->control(id);
506 boost::shared_ptr<MidiModel>
509 return midi_source()->model();
512 boost::shared_ptr<const MidiModel>
513 MidiRegion::model() const
515 return midi_source()->model();
518 boost::shared_ptr<MidiSource>
519 MidiRegion::midi_source (uint32_t n) const
521 // Guaranteed to succeed (use a static cast?)
522 return boost::dynamic_pointer_cast<MidiSource>(source(n));
525 /* don't use this. hopefully it will go away.
526 currently used by headless-chicken session utility.
529 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
533 _sources.push_back (s);
535 _master_sources.push_back (s);
538 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
543 MidiRegion::model_changed ()
549 /* build list of filtered Parameters, being those whose automation state is not `Play' */
551 _filtered_parameters.clear ();
553 Automatable::Controls const & c = model()->controls();
555 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
556 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
558 if (ac->alist()->automation_state() != Play) {
559 _filtered_parameters.insert (ac->parameter ());
563 /* watch for changes to controls' AutoState */
564 midi_source()->AutomationStateChanged.connect_same_thread (
565 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
570 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
572 /* Update our filtered parameters list after a change to a parameter's AutoState */
574 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
575 if (!ac || ac->alist()->automation_state() == Play) {
576 /* It should be "impossible" for ac to be NULL, but if it is, don't
577 filter the parameter so events aren't lost. */
578 _filtered_parameters.erase (p);
580 _filtered_parameters.insert (p);
583 /* the source will have an iterator into the model, and that iterator will have been set up
584 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
587 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
589 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
590 midi_source(0)->invalidate (lm);
594 /** This is called when a trim drag has resulted in a -ve _start time for this region.
595 * Fix it up by adding some empty space to the source.
598 MidiRegion::fix_negative_start ()
600 BeatsFramesConverter c (_session.tempo_map(), _position);
602 model()->insert_silence_at_start (c.from (-_start));
608 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
610 Region::set_start_internal (s, sub_num);
611 set_start_beats_from_start_frames ();
615 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
621 PropertyChange what_changed;
624 /* Set position before length, otherwise for MIDI regions this bad thing happens:
625 * 1. we call set_length_internal; length in beats is computed using the region's current
626 * (soon-to-be old) position
627 * 2. we call set_position_internal; position is set and length in frames re-computed using
628 * length in beats from (1) but at the new position, which is wrong if the region
629 * straddles a tempo/meter change.
632 if (_position != position) {
634 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
635 const double old_pos_qn = quarter_note();
637 /* sets _pulse to new position.*/
638 set_position_internal (position, true, sub_num);
639 what_changed.add (Properties::position);
641 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
642 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
644 if (!verify_start_and_length (new_start, length)) {
648 _start_beats = new_start_qn;
649 what_changed.add (Properties::start_beats);
651 set_start_internal (new_start, sub_num);
652 what_changed.add (Properties::start);
655 if (_length != length) {
657 if (!verify_start_and_length (_start, length)) {
661 set_length_internal (length, sub_num);
662 what_changed.add (Properties::length);
663 what_changed.add (Properties::length_beats);
666 set_whole_file (false);
668 PropertyChange start_and_length;
670 start_and_length.add (Properties::start);
671 start_and_length.add (Properties::length);
673 if (what_changed.contains (start_and_length)) {
677 if (!what_changed.empty()) {
678 send_change (what_changed);