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_model.h"
38 #include "ardour/midi_region.h"
39 #include "ardour/midi_ring_buffer.h"
40 #include "ardour/midi_source.h"
41 #include "ardour/region_factory.h"
42 #include "ardour/session.h"
43 #include "ardour/source_factory.h"
44 #include "ardour/tempo.h"
45 #include "ardour/types.h"
51 using namespace ARDOUR;
55 namespace Properties {
56 PBD::PropertyDescriptor<Evoral::Beats> start_beats;
57 PBD::PropertyDescriptor<Evoral::Beats> length_beats;
62 MidiRegion::make_property_quarks ()
64 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
65 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
66 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
67 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
71 MidiRegion::register_properties ()
73 add_property (_start_beats);
74 add_property (_length_beats);
77 /* Basic MidiRegion constructor (many channels) */
78 MidiRegion::MidiRegion (const SourceList& srcs)
80 , _start_beats (Properties::start_beats, Evoral::Beats())
81 , _length_beats (Properties::length_beats, midi_source(0)->length_beats())
83 register_properties ();
84 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
86 assert(_name.val().find("/") == string::npos);
87 assert(_type == DataType::MIDI);
90 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
92 , _start_beats (Properties::start_beats, other->_start_beats)
93 , _length_beats (Properties::length_beats, other->_length_beats)
95 //update_length_beats ();
96 register_properties ();
98 assert(_name.val().find("/") == string::npos);
99 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
103 /** Create a new MidiRegion that is part of an existing one */
104 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
105 : Region (other, offset, sub_num)
106 , _start_beats (Properties::start_beats, Evoral::Beats())
107 , _length_beats (Properties::length_beats, other->_length_beats)
109 _start_beats = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) - (other->pulse() * 4.0)) + other->_start_beats;
111 update_length_beats (sub_num);
112 register_properties ();
114 assert(_name.val().find("/") == string::npos);
115 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
119 MidiRegion::~MidiRegion ()
123 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
126 MidiRegion::do_export (string path) const
128 boost::shared_ptr<MidiSource> newsrc;
130 /* caller must check for pre-existing file */
131 assert (!path.empty());
132 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
133 newsrc = boost::dynamic_pointer_cast<MidiSource>(
134 SourceFactory::createWritable(DataType::MIDI, _session,
135 path, false, _session.frame_rate()));
137 BeatsFramesConverter bfc (_session.tempo_map(), _position);
138 Evoral::Beats const bbegin = bfc.from (_start);
139 Evoral::Beats const bend = bfc.from (_start + _length);
142 /* Lock our source since we'll be reading from it. write_to() will
143 take a lock on newsrc. */
144 Source::Lock lm (midi_source(0)->mutex());
145 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
154 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
156 boost::shared_ptr<MidiRegion>
157 MidiRegion::clone (string path) const
159 boost::shared_ptr<MidiSource> newsrc;
161 /* caller must check for pre-existing file */
162 assert (!path.empty());
163 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
164 newsrc = boost::dynamic_pointer_cast<MidiSource>(
165 SourceFactory::createWritable(DataType::MIDI, _session,
166 path, false, _session.frame_rate()));
167 return clone (newsrc);
170 boost::shared_ptr<MidiRegion>
171 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
173 BeatsFramesConverter bfc (_session.tempo_map(), _position);
174 Evoral::Beats const bbegin = bfc.from (_start);
175 Evoral::Beats const bend = bfc.from (_start + _length);
178 /* Lock our source since we'll be reading from it. write_to() will
179 take a lock on newsrc. */
180 Source::Lock lm (midi_source(0)->mutex());
181 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
182 return boost::shared_ptr<MidiRegion> ();
188 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
189 plist.add (Properties::whole_file, true);
190 plist.add (Properties::start, _start);
191 plist.add (Properties::start_beats, _start_beats);
192 plist.add (Properties::length, _length);
193 plist.add (Properties::length_beats, _length_beats);
194 plist.add (Properties::layer, 0);
196 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
197 ret->set_beat (beat());
198 ret->set_pulse (pulse());
204 MidiRegion::post_set (const PropertyChange& pc)
206 Region::post_set (pc);
208 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
209 /* update non-musically */
210 update_length_beats (0);
211 } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
212 set_start_beats_from_start_frames ();
217 MidiRegion::set_start_beats_from_start_frames ()
219 _start_beats = Evoral::Beats ((pulse() * 4.0) - _session.tempo_map().quarter_note_at_frame (_position - _start));
223 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
225 Region::set_length_internal (len, sub_num);
226 update_length_beats (sub_num);
230 MidiRegion::update_after_tempo_map_change (bool /* send */)
232 boost::shared_ptr<Playlist> pl (playlist());
238 const framepos_t old_pos = _position;
239 const framepos_t old_length = _length;
240 const framepos_t old_start = _start;
242 PropertyChange s_and_l;
244 if (position_lock_style() == AudioTime) {
245 recompute_position_from_lock_style (0);
248 set _start to new position in tempo map.
250 The user probably expects the region contents to maintain audio position as the
251 tempo changes, but AFAICT this requires modifying the src file to use
252 SMPTE timestamps with the current disk read model (?).
254 We could arguably use _start to set _start_beats here,
255 resulting in viewport-like behaviour (the contents maintain
256 their musical position while the region is stationary).
258 For now, the musical position at the region start is retained, but subsequent events
259 will maintain their beat distance according to the map.
261 _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
263 /* _length doesn't change for audio-locked regions. update length_beats to match. */
264 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
266 s_and_l.add (Properties::start);
267 s_and_l.add (Properties::length_beats);
269 send_change (s_and_l);
273 Region::update_after_tempo_map_change (false);
275 /* _start has now been updated. */
276 _length = _session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position;
278 if (old_start != _start) {
279 s_and_l.add (Properties::start);
281 if (old_length != _length) {
282 s_and_l.add (Properties::length);
284 if (old_pos != _position) {
285 s_and_l.add (Properties::position);
288 send_change (s_and_l);
292 MidiRegion::update_length_beats (const int32_t sub_num)
294 _length_beats = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - (pulse() * 4.0));
298 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
300 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
302 /* set _start to new position in tempo map */
303 _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
305 /* in construction from src */
306 if (_length_beats == Evoral::Beats()) {
307 update_length_beats (sub_num);
310 if (position_lock_style() == AudioTime) {
311 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
313 /* leave _length_beats alone, and change _length to reflect the state of things
314 at the new position (tempo map may dictate a different number of frames).
316 Region::set_length_internal (_session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position, sub_num);
321 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
324 Evoral::Range<framepos_t>* loop_range,
327 MidiStateTracker* tracker,
328 MidiChannelFilter* filter) const
330 return _read_at (_sources, out, position, dur, loop_range, chan_n, mode, tracker, filter);
334 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
337 Evoral::Range<framepos_t>* loop_range,
341 return _read_at (_master_sources, out, position, dur, loop_range, chan_n, mode); /* no tracker */
345 MidiRegion::_read_at (const SourceList& /*srcs*/,
346 Evoral::EventSink<framepos_t>& dst,
349 Evoral::Range<framepos_t>* loop_range,
352 MidiStateTracker* tracker,
353 MidiChannelFilter* filter) const
355 frameoffset_t internal_offset = 0;
356 framecnt_t to_read = 0;
358 /* precondition: caller has verified that we cover the desired section */
363 return 0; /* read nothing */
366 if (position < _position) {
367 /* we are starting the read from before the start of the region */
369 dur -= _position - position;
371 /* we are starting the read from after the start of the region */
372 internal_offset = position - _position;
375 if (internal_offset >= _length) {
376 return 0; /* read nothing */
379 if ((to_read = min (dur, _length - internal_offset)) == 0) {
380 return 0; /* read nothing */
383 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
385 Glib::Threads::Mutex::Lock lm(src->mutex());
387 src->set_note_mode(lm, mode);
390 cerr << "MR " << name () << " read @ " << position << " + " << to_read
391 << " dur was " << dur
392 << " len " << _length
393 << " l-io " << (_length - internal_offset)
394 << " _position = " << _position
395 << " _start = " << _start
396 << " intoffset = " << internal_offset
397 << " pulse = " << pulse()
398 << " start_pulse = " << start_pulse()
399 << " start_beat = " << _start_beats
403 /* This call reads events from a source and writes them to `dst' timed in session frames */
407 dst, // destination buffer
408 _position - _start, // start position of the source in session frames
409 _start + internal_offset, // where to start reading in the source
410 to_read, // read duration in frames
414 _filtered_parameters,
416 _start_beats.val().to_double()
418 return 0; /* "read nothing" */
427 return Region::state ();
431 MidiRegion::set_state (const XMLNode& node, int version)
433 int ret = Region::set_state (node, version);
436 /* set length beats to the frame (non-musical) */
437 if (position_lock_style() == AudioTime) {
438 update_length_beats (0);
446 MidiRegion::recompute_at_end ()
448 /* our length has changed
449 * so what? stuck notes are dealt with via a note state tracker
454 MidiRegion::recompute_at_start ()
456 /* as above, but the shift was from the front
457 * maybe bump currently active note's note-ons up so they sound here?
458 * that could be undesireable in certain situations though.. maybe
459 * remove the note entirely, including it's note off? something needs to
460 * be done to keep the played MIDI sane to avoid messing up voices of
461 * polyhonic things etc........
466 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
472 boost::shared_ptr<Evoral::Control>
473 MidiRegion::control (const Evoral::Parameter& id, bool create)
475 return model()->control(id, create);
478 boost::shared_ptr<const Evoral::Control>
479 MidiRegion::control (const Evoral::Parameter& id) const
481 return model()->control(id);
484 boost::shared_ptr<MidiModel>
487 return midi_source()->model();
490 boost::shared_ptr<const MidiModel>
491 MidiRegion::model() const
493 return midi_source()->model();
496 boost::shared_ptr<MidiSource>
497 MidiRegion::midi_source (uint32_t n) const
499 // Guaranteed to succeed (use a static cast?)
500 return boost::dynamic_pointer_cast<MidiSource>(source(n));
503 /* don't use this. hopefully it will go away.
504 currently used by headless-chicken session utility.
507 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
511 _sources.push_back (s);
513 _master_sources.push_back (s);
516 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
521 MidiRegion::model_changed ()
527 /* build list of filtered Parameters, being those whose automation state is not `Play' */
529 _filtered_parameters.clear ();
531 Automatable::Controls const & c = model()->controls();
533 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
534 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
536 if (ac->alist()->automation_state() != Play) {
537 _filtered_parameters.insert (ac->parameter ());
541 /* watch for changes to controls' AutoState */
542 midi_source()->AutomationStateChanged.connect_same_thread (
543 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
548 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
550 /* Update our filtered parameters list after a change to a parameter's AutoState */
552 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
553 if (!ac || ac->alist()->automation_state() == Play) {
554 /* It should be "impossible" for ac to be NULL, but if it is, don't
555 filter the parameter so events aren't lost. */
556 _filtered_parameters.erase (p);
558 _filtered_parameters.insert (p);
561 /* the source will have an iterator into the model, and that iterator will have been set up
562 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
565 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
567 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
568 midi_source(0)->invalidate (lm);
572 /** This is called when a trim drag has resulted in a -ve _start time for this region.
573 * Fix it up by adding some empty space to the source.
576 MidiRegion::fix_negative_start ()
578 BeatsFramesConverter c (_session.tempo_map(), _position);
580 model()->insert_silence_at_start (c.from (-_start));
582 _start_beats = Evoral::Beats();
586 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
588 Region::set_start_internal (s, sub_num);
590 if (position_lock_style() == AudioTime) {
591 set_start_beats_from_start_frames ();
596 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
602 PropertyChange what_changed;
604 /* beat has been set exactly by set_position_internal, but the source starts on a frame.
605 working in beats seems the correct thing to do, but reports of a missing first note
606 on playback suggest otherwise. for now, we work in exact beats.
608 const double pos_pulse = _session.tempo_map().exact_qn_at_frame (position, sub_num) / 4.0;
609 const double pulse_delta = pos_pulse - pulse();
611 /* Set position before length, otherwise for MIDI regions this bad thing happens:
612 * 1. we call set_length_internal; length in beats is computed using the region's current
613 * (soon-to-be old) position
614 * 2. we call set_position_internal; position is set and length in frames re-computed using
615 * length in beats from (1) but at the new position, which is wrong if the region
616 * straddles a tempo/meter change.
619 if (_position != position) {
620 /* sets _beat to new position.*/
621 set_position_internal (position, true, sub_num);
622 what_changed.add (Properties::position);
624 const double new_start_pulse = (_start_beats.val().to_double() / 4.0) + pulse_delta;
625 const framepos_t new_start = _position - _session.tempo_map().frame_at_pulse (pulse() - new_start_pulse);
627 if (!verify_start_and_length (new_start, length)) {
631 _start_beats = Evoral::Beats (new_start_pulse * 4.0);
632 what_changed.add (Properties::start_beats);
634 set_start_internal (new_start, sub_num);
635 what_changed.add (Properties::start);
638 if (_length != length) {
640 if (!verify_start_and_length (_start, length)) {
644 set_length_internal (length, sub_num);
645 what_changed.add (Properties::length);
646 what_changed.add (Properties::length_beats);
649 set_whole_file (false);
651 PropertyChange start_and_length;
653 start_and_length.add (Properties::start);
654 start_and_length.add (Properties::length);
656 if (what_changed.contains (start_and_length)) {
660 if (!what_changed.empty()) {
661 send_change (what_changed);