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::beat, _beat);
194 plist.add (Properties::length_beats, _length_beats);
195 plist.add (Properties::layer, 0);
197 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
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 /* we're called by Stateful::set_values() which sends a change
210 only if the value is different from _current.
211 session load means we can clobber length_beats here in error (not all properties differ from current),
212 so disallow (this has been set from XML state anyway).
214 if (!_session.loading()) {
215 /* update non-musically */
216 update_length_beats (0);
218 } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
219 set_start_beats_from_start_frames ();
224 MidiRegion::set_start_beats_from_start_frames ()
226 _start_beats = Evoral::Beats ((pulse() * 4.0) - _session.tempo_map().quarter_note_at_frame (_position - _start));
230 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
232 Region::set_length_internal (len, sub_num);
233 update_length_beats (sub_num);
237 MidiRegion::update_after_tempo_map_change (bool /* send */)
239 boost::shared_ptr<Playlist> pl (playlist());
245 const framepos_t old_pos = _position;
246 const framepos_t old_length = _length;
247 const framepos_t old_start = _start;
249 PropertyChange s_and_l;
251 if (position_lock_style() == AudioTime) {
252 recompute_position_from_lock_style (0);
255 set _start to new position in tempo map.
257 The user probably expects the region contents to maintain audio position as the
258 tempo changes, but AFAICT this requires modifying the src file to use
259 SMPTE timestamps with the current disk read model (?).
261 We could arguably use _start to set _start_beats here,
262 resulting in viewport-like behaviour (the contents maintain
263 their musical position while the region is stationary).
265 For now, the musical position at the region start is retained, but subsequent events
266 will maintain their beat distance according to the map.
268 _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
270 /* _length doesn't change for audio-locked regions. update length_beats to match. */
271 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
273 s_and_l.add (Properties::start);
274 s_and_l.add (Properties::length_beats);
276 send_change (s_and_l);
280 Region::update_after_tempo_map_change (false);
282 /* _start has now been updated. */
283 _length = _session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position;
285 if (old_start != _start) {
286 s_and_l.add (Properties::start);
288 if (old_length != _length) {
289 s_and_l.add (Properties::length);
291 if (old_pos != _position) {
292 s_and_l.add (Properties::position);
295 send_change (s_and_l);
299 MidiRegion::update_length_beats (const int32_t sub_num)
301 _length_beats = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - (pulse() * 4.0));
305 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
307 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
309 /* don't clobber _start _length and _length_beats if session loading.*/
310 if (_session.loading()) {
314 /* set _start to new position in tempo map */
315 _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
317 /* in construction from src */
318 if (_length_beats == Evoral::Beats()) {
319 update_length_beats (sub_num);
322 if (position_lock_style() == AudioTime) {
323 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
325 /* leave _length_beats alone, and change _length to reflect the state of things
326 at the new position (tempo map may dictate a different number of frames).
328 Region::set_length_internal (_session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position, sub_num);
333 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
336 Evoral::Range<framepos_t>* loop_range,
339 MidiStateTracker* tracker,
340 MidiChannelFilter* filter) const
342 return _read_at (_sources, out, position, dur, loop_range, chan_n, mode, tracker, filter);
346 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
349 Evoral::Range<framepos_t>* loop_range,
353 return _read_at (_master_sources, out, position, dur, loop_range, chan_n, mode); /* no tracker */
357 MidiRegion::_read_at (const SourceList& /*srcs*/,
358 Evoral::EventSink<framepos_t>& dst,
361 Evoral::Range<framepos_t>* loop_range,
364 MidiStateTracker* tracker,
365 MidiChannelFilter* filter) const
367 frameoffset_t internal_offset = 0;
368 framecnt_t to_read = 0;
370 /* precondition: caller has verified that we cover the desired section */
375 return 0; /* read nothing */
378 if (position < _position) {
379 /* we are starting the read from before the start of the region */
381 dur -= _position - position;
383 /* we are starting the read from after the start of the region */
384 internal_offset = position - _position;
387 if (internal_offset >= _length) {
388 return 0; /* read nothing */
391 if ((to_read = min (dur, _length - internal_offset)) == 0) {
392 return 0; /* read nothing */
395 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
397 Glib::Threads::Mutex::Lock lm(src->mutex());
399 src->set_note_mode(lm, mode);
402 cerr << "MR " << name () << " read @ " << position << " + " << to_read
403 << " dur was " << dur
404 << " len " << _length
405 << " l-io " << (_length - internal_offset)
406 << " _position = " << _position
407 << " _start = " << _start
408 << " intoffset = " << internal_offset
409 << " pulse = " << pulse()
410 << " start_pulse = " << start_pulse()
411 << " start_beat = " << _start_beats
415 /* This call reads events from a source and writes them to `dst' timed in session frames */
419 dst, // destination buffer
420 _position - _start, // start position of the source in session frames
421 _start + internal_offset, // where to start reading in the source
422 to_read, // read duration in frames
426 _filtered_parameters,
428 _start_beats.val().to_double()
430 return 0; /* "read nothing" */
439 return Region::state ();
443 MidiRegion::set_state (const XMLNode& node, int version)
445 int ret = Region::set_state (node, version);
448 /* set length beats to the frame (non-musical) */
449 if (position_lock_style() == AudioTime) {
450 update_length_beats (0);
458 MidiRegion::recompute_at_end ()
460 /* our length has changed
461 * so what? stuck notes are dealt with via a note state tracker
466 MidiRegion::recompute_at_start ()
468 /* as above, but the shift was from the front
469 * maybe bump currently active note's note-ons up so they sound here?
470 * that could be undesireable in certain situations though.. maybe
471 * remove the note entirely, including it's note off? something needs to
472 * be done to keep the played MIDI sane to avoid messing up voices of
473 * polyhonic things etc........
478 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
484 boost::shared_ptr<Evoral::Control>
485 MidiRegion::control (const Evoral::Parameter& id, bool create)
487 return model()->control(id, create);
490 boost::shared_ptr<const Evoral::Control>
491 MidiRegion::control (const Evoral::Parameter& id) const
493 return model()->control(id);
496 boost::shared_ptr<MidiModel>
499 return midi_source()->model();
502 boost::shared_ptr<const MidiModel>
503 MidiRegion::model() const
505 return midi_source()->model();
508 boost::shared_ptr<MidiSource>
509 MidiRegion::midi_source (uint32_t n) const
511 // Guaranteed to succeed (use a static cast?)
512 return boost::dynamic_pointer_cast<MidiSource>(source(n));
515 /* don't use this. hopefully it will go away.
516 currently used by headless-chicken session utility.
519 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
523 _sources.push_back (s);
525 _master_sources.push_back (s);
528 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
533 MidiRegion::model_changed ()
539 /* build list of filtered Parameters, being those whose automation state is not `Play' */
541 _filtered_parameters.clear ();
543 Automatable::Controls const & c = model()->controls();
545 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
546 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
548 if (ac->alist()->automation_state() != Play) {
549 _filtered_parameters.insert (ac->parameter ());
553 /* watch for changes to controls' AutoState */
554 midi_source()->AutomationStateChanged.connect_same_thread (
555 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
560 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
562 /* Update our filtered parameters list after a change to a parameter's AutoState */
564 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
565 if (!ac || ac->alist()->automation_state() == Play) {
566 /* It should be "impossible" for ac to be NULL, but if it is, don't
567 filter the parameter so events aren't lost. */
568 _filtered_parameters.erase (p);
570 _filtered_parameters.insert (p);
573 /* the source will have an iterator into the model, and that iterator will have been set up
574 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
577 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
579 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
580 midi_source(0)->invalidate (lm);
584 /** This is called when a trim drag has resulted in a -ve _start time for this region.
585 * Fix it up by adding some empty space to the source.
588 MidiRegion::fix_negative_start ()
590 BeatsFramesConverter c (_session.tempo_map(), _position);
592 model()->insert_silence_at_start (c.from (-_start));
594 _start_beats = Evoral::Beats();
598 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
600 Region::set_start_internal (s, sub_num);
601 if (position_lock_style() == AudioTime) {
602 set_start_beats_from_start_frames ();
607 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
613 PropertyChange what_changed;
616 /* Set position before length, otherwise for MIDI regions this bad thing happens:
617 * 1. we call set_length_internal; length in beats is computed using the region's current
618 * (soon-to-be old) position
619 * 2. we call set_position_internal; position is set and length in frames re-computed using
620 * length in beats from (1) but at the new position, which is wrong if the region
621 * straddles a tempo/meter change.
624 if (_position != position) {
626 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
627 const double old_pos_qn = pulse() * 4.0;
629 /* sets _pulse to new position.*/
630 set_position_internal (position, true, sub_num);
631 what_changed.add (Properties::position);
633 double new_start_qn = start_beats().to_double() + (pos_qn - old_pos_qn);
634 const framepos_t new_start = _position - _session.tempo_map().frame_at_quarter_note (pos_qn - new_start_qn);
636 if (!verify_start_and_length (new_start, length)) {
640 /* at small deltas, (high zooms) the property will not change without this (tick resolution of Beats::operator!=)*/
641 _start_beats += Evoral::Beats (new_start_qn) + start_beats().tick() * 2.0;
642 _start_beats = Evoral::Beats (new_start_qn);
643 what_changed.add (Properties::start_beats);
645 set_start_internal (new_start, sub_num);
646 what_changed.add (Properties::start);
649 if (_length != length) {
651 if (!verify_start_and_length (_start, length)) {
655 set_length_internal (length, sub_num);
656 what_changed.add (Properties::length);
657 what_changed.add (Properties::length_beats);
660 set_whole_file (false);
662 PropertyChange start_and_length;
664 start_and_length.add (Properties::start);
665 start_and_length.add (Properties::length);
667 if (what_changed.contains (start_and_length)) {
671 if (!what_changed.empty()) {
672 send_change (what_changed);