2 * Copyright (C) 2006-2016 David Robillard <d@drobilla.net>
3 * Copyright (C) 2007-2017 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2008 Hans Baier <hansfbaier@googlemail.com>
5 * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
6 * Copyright (C) 2012-2015 Tim Mayberry <mojofunk@gmail.com>
7 * Copyright (C) 2016-2017 Nick Mainsbridge <mainsbridge@gmail.com>
8 * Copyright (C) 2016-2017 Robin Gareus <robin@gareus.org>
9 * Copyright (C) 2016 Julien "_FrnchFrgg_" RIVAUD <frnchfrgg@free.fr>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
32 #include <glibmm/threads.h>
33 #include <glibmm/fileutils.h>
34 #include <glibmm/miscutils.h>
36 #include "temporal/beats.h"
38 #include "pbd/xml++.h"
39 #include "pbd/basename.h"
41 #include "ardour/automation_control.h"
42 #include "ardour/midi_cursor.h"
43 #include "ardour/midi_model.h"
44 #include "ardour/midi_region.h"
45 #include "ardour/midi_ring_buffer.h"
46 #include "ardour/midi_source.h"
47 #include "ardour/playlist.h"
48 #include "ardour/region_factory.h"
49 #include "ardour/session.h"
50 #include "ardour/source_factory.h"
51 #include "ardour/tempo.h"
52 #include "ardour/types.h"
53 #include "ardour/evoral_types_convert.h"
59 using namespace ARDOUR;
63 namespace Properties {
64 PBD::PropertyDescriptor<double> start_beats;
65 PBD::PropertyDescriptor<double> length_beats;
70 MidiRegion::make_property_quarks ()
72 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
73 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
74 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
75 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
79 MidiRegion::register_properties ()
81 add_property (_start_beats);
82 add_property (_length_beats);
85 /* Basic MidiRegion constructor (many channels) */
86 MidiRegion::MidiRegion (const SourceList& srcs)
88 , _start_beats (Properties::start_beats, 0.0)
89 , _length_beats (Properties::length_beats, midi_source(0)->length_beats().to_double())
90 , _ignore_shift (false)
92 register_properties ();
93 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
95 assert(_name.val().find("/") == string::npos);
96 assert(_type == DataType::MIDI);
99 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
101 , _start_beats (Properties::start_beats, other->_start_beats)
102 , _length_beats (Properties::length_beats, other->_length_beats)
103 , _ignore_shift (false)
105 //update_length_beats ();
106 register_properties ();
108 assert(_name.val().find("/") == string::npos);
109 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
113 /** Create a new MidiRegion that is part of an existing one */
114 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, MusicSample offset)
115 : Region (other, offset)
116 , _start_beats (Properties::start_beats, other->_start_beats)
117 , _length_beats (Properties::length_beats, other->_length_beats)
118 , _ignore_shift (false)
121 register_properties ();
123 const double offset_quarter_note = _session.tempo_map().exact_qn_at_sample (other->_position + offset.sample, offset.division) - other->_quarter_note;
124 if (offset.sample != 0) {
125 _start_beats = other->_start_beats + offset_quarter_note;
126 _length_beats = other->_length_beats - offset_quarter_note;
129 assert(_name.val().find("/") == string::npos);
130 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
134 MidiRegion::~MidiRegion ()
138 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
141 MidiRegion::do_export (string path) const
143 boost::shared_ptr<MidiSource> newsrc;
145 /* caller must check for pre-existing file */
146 assert (!path.empty());
147 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
148 newsrc = boost::dynamic_pointer_cast<MidiSource>(
149 SourceFactory::createWritable(DataType::MIDI, _session,
150 path, false, _session.sample_rate()));
152 BeatsSamplesConverter bfc (_session.tempo_map(), _position);
153 Temporal::Beats const bbegin = bfc.from (_start);
154 Temporal::Beats const bend = bfc.from (_start + _length);
157 /* Lock our source since we'll be reading from it. write_to() will
158 take a lock on newsrc. */
159 Source::Lock lm (midi_source(0)->mutex());
160 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
169 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
171 boost::shared_ptr<MidiRegion>
172 MidiRegion::clone (string path) const
174 boost::shared_ptr<MidiSource> newsrc;
176 /* caller must check for pre-existing file */
177 assert (!path.empty());
178 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
179 newsrc = boost::dynamic_pointer_cast<MidiSource>(
180 SourceFactory::createWritable(DataType::MIDI, _session,
181 path, false, _session.sample_rate()));
182 return clone (newsrc);
185 boost::shared_ptr<MidiRegion>
186 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
188 BeatsSamplesConverter bfc (_session.tempo_map(), _position);
189 Temporal::Beats const bbegin = bfc.from (_start);
190 Temporal::Beats const bend = bfc.from (_start + _length);
193 boost::shared_ptr<MidiSource> ms = midi_source(0);
194 Source::Lock lm (ms->mutex());
200 /* Lock our source since we'll be reading from it. write_to() will
201 take a lock on newsrc.
204 if (ms->write_to (lm, newsrc, bbegin, bend)) {
205 return boost::shared_ptr<MidiRegion> ();
211 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
212 plist.add (Properties::whole_file, true);
213 plist.add (Properties::start, _start);
214 plist.add (Properties::start_beats, _start_beats);
215 plist.add (Properties::length, _length);
216 plist.add (Properties::position, _position);
217 plist.add (Properties::beat, _beat);
218 plist.add (Properties::length_beats, _length_beats);
219 plist.add (Properties::layer, 0);
221 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
222 ret->set_quarter_note (quarter_note());
228 MidiRegion::post_set (const PropertyChange& pc)
230 Region::post_set (pc);
232 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
233 /* we're called by Stateful::set_values() which sends a change
234 only if the value is different from _current.
235 session load means we can clobber length_beats here in error (not all properties differ from current),
236 so disallow (this has been set from XML state anyway).
238 if (!_session.loading()) {
239 update_length_beats (0);
243 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
244 set_start_beats_from_start_samples ();
249 MidiRegion::set_start_beats_from_start_samples ()
251 if (position_lock_style() == AudioTime) {
252 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_sample (_position - _start);
257 MidiRegion::set_length_internal (samplecnt_t len, const int32_t sub_num)
259 Region::set_length_internal (len, sub_num);
260 update_length_beats (sub_num);
264 MidiRegion::update_after_tempo_map_change (bool /* send */)
266 boost::shared_ptr<Playlist> pl (playlist());
272 const samplepos_t old_pos = _position;
273 const samplepos_t old_length = _length;
274 const samplepos_t old_start = _start;
276 PropertyChange s_and_l;
278 if (position_lock_style() == AudioTime) {
279 recompute_position_from_lock_style (0);
282 set _start to new position in tempo map.
284 The user probably expects the region contents to maintain audio position as the
285 tempo changes, but AFAICT this requires modifying the src file to use
286 SMPTE timestamps with the current disk read model (?).
288 We could arguably use _start to set _start_beats here,
289 resulting in viewport-like behaviour (the contents maintain
290 their musical position while the region is stationary).
292 For now, the musical position at the region start is retained, but subsequent events
293 will maintain their beat distance according to the map.
295 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
297 /* _length doesn't change for audio-locked regions. update length_beats to match. */
298 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
300 s_and_l.add (Properties::start);
301 s_and_l.add (Properties::length_beats);
303 send_change (s_and_l);
307 Region::update_after_tempo_map_change (false);
309 /* _start has now been updated. */
310 _length = max ((samplecnt_t) 1, _session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
312 if (old_start != _start) {
313 s_and_l.add (Properties::start);
315 if (old_length != _length) {
316 s_and_l.add (Properties::length);
318 if (old_pos != _position) {
319 s_and_l.add (Properties::position);
322 send_change (s_and_l);
326 MidiRegion::update_length_beats (const int32_t sub_num)
328 _length_beats = _session.tempo_map().exact_qn_at_sample (_position + _length, sub_num) - quarter_note();
332 MidiRegion::set_position_internal (samplepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
334 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
336 /* don't clobber _start _length and _length_beats if session loading.*/
337 if (_session.loading()) {
341 /* set _start to new position in tempo map */
342 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
344 /* in construction from src */
345 if (_length_beats == 0.0) {
346 update_length_beats (sub_num);
349 if (position_lock_style() == AudioTime) {
350 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
352 /* leave _length_beats alone, and change _length to reflect the state of things
353 at the new position (tempo map may dictate a different number of samples).
355 Region::set_length_internal (_session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
360 MidiRegion::set_position_music_internal (double qn)
362 Region::set_position_music_internal (qn);
363 /* set _start to new position in tempo map */
364 _start = _session.tempo_map().samples_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
366 if (position_lock_style() == AudioTime) {
367 _length_beats = _session.tempo_map().quarter_note_at_sample (_position + _length) - quarter_note();
370 /* leave _length_beats alone, and change _length to reflect the state of things
371 at the new position (tempo map may dictate a different number of samples).
373 _length = _session.tempo_map().samples_between_quarter_notes (quarter_note(), quarter_note() + length_beats());
378 MidiRegion::read_at (Evoral::EventSink<samplepos_t>& out,
379 samplepos_t position,
381 Evoral::Range<samplepos_t>* loop_range,
385 MidiStateTracker* tracker,
386 MidiChannelFilter* filter) const
388 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
392 MidiRegion::master_read_at (MidiRingBuffer<samplepos_t>& out,
393 samplepos_t position,
395 Evoral::Range<samplepos_t>* loop_range,
400 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
404 MidiRegion::_read_at (const SourceList& /*srcs*/,
405 Evoral::EventSink<samplepos_t>& dst,
406 samplepos_t position,
408 Evoral::Range<samplepos_t>* loop_range,
412 MidiStateTracker* tracker,
413 MidiChannelFilter* filter) const
415 sampleoffset_t internal_offset = 0;
416 samplecnt_t to_read = 0;
418 /* precondition: caller has verified that we cover the desired section */
423 return 0; /* read nothing */
426 if (position < _position) {
427 /* we are starting the read from before the start of the region */
429 dur -= _position - position;
431 /* we are starting the read from after the start of the region */
432 internal_offset = position - _position;
435 if (internal_offset >= _length) {
436 return 0; /* read nothing */
439 if ((to_read = min (dur, _length - internal_offset)) == 0) {
440 return 0; /* read nothing */
443 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
445 Glib::Threads::Mutex::Lock lm(src->mutex());
447 src->set_note_mode(lm, mode);
450 cerr << "MR " << name () << " read @ " << position << " + " << to_read
451 << " dur was " << dur
452 << " len " << _length
453 << " l-io " << (_length - internal_offset)
454 << " _position = " << _position
455 << " _start = " << _start
456 << " intoffset = " << internal_offset
457 << " quarter_note = " << quarter_note()
458 << " start_beat = " << _start_beats
462 /* This call reads events from a source and writes them to `dst' timed in session samples */
466 dst, // destination buffer
467 _position - _start, // start position of the source in session samples
468 _start + internal_offset, // where to start reading in the source
469 to_read, // read duration in samples
474 _filtered_parameters,
478 return 0; /* "read nothing" */
486 MidiRegion::render (Evoral::EventSink<samplepos_t>& dst,
489 MidiChannelFilter* filter) const
491 sampleoffset_t internal_offset = 0;
493 /* precondition: caller has verified that we cover the desired section */
498 return 0; /* read nothing */
502 /* dump pulls from zero to infinity ... */
505 /* we are starting the read from before the start of the region */
508 /* we are starting the read from after the start of the region */
509 internal_offset = -_position;
512 if (internal_offset >= _length) {
513 return 0; /* read nothing */
516 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
518 Glib::Threads::Mutex::Lock lm(src->mutex());
520 src->set_note_mode(lm, mode);
523 cerr << "MR " << name () << " read @ " << position << " + " << to_read
524 << " dur was " << dur
525 << " len " << _length
526 << " l-io " << (_length - internal_offset)
527 << " _position = " << _position
528 << " _start = " << _start
529 << " intoffset = " << internal_offset
530 << " quarter_note = " << quarter_note()
531 << " start_beat = " << _start_beats
536 MidiStateTracker tracker;
538 /* This call reads events from a source and writes them to `dst' timed in session samples */
542 dst, // destination buffer
543 _position - _start, // start position of the source in session samples
544 _start + internal_offset, // where to start reading in the source
545 _start + internal_offset + _length,
550 _filtered_parameters,
554 /* resolve any notes that were "cut off" by the end of the region. The
555 * Note-Off's get inserted at the end of the region
558 tracker.resolve_notes (dst, _position - _start + _length);
567 return Region::state ();
571 MidiRegion::set_state (const XMLNode& node, int version)
573 int ret = Region::set_state (node, version);
579 MidiRegion::recompute_at_end ()
581 /* our length has changed
582 * so what? stuck notes are dealt with via a note state tracker
587 MidiRegion::recompute_at_start ()
589 /* as above, but the shift was from the front
590 * maybe bump currently active note's note-ons up so they sound here?
591 * that could be undesireable in certain situations though.. maybe
592 * remove the note entirely, including it's note off? something needs to
593 * be done to keep the played MIDI sane to avoid messing up voices of
594 * polyhonic things etc........
599 MidiRegion::separate_by_channel (vector< boost::shared_ptr<Region> >&) const
605 boost::shared_ptr<Evoral::Control>
606 MidiRegion::control (const Evoral::Parameter& id, bool create)
608 return model()->control(id, create);
611 boost::shared_ptr<const Evoral::Control>
612 MidiRegion::control (const Evoral::Parameter& id) const
614 return model()->control(id);
617 boost::shared_ptr<MidiModel>
620 return midi_source()->model();
623 boost::shared_ptr<const MidiModel>
624 MidiRegion::model() const
626 return midi_source()->model();
629 boost::shared_ptr<MidiSource>
630 MidiRegion::midi_source (uint32_t n) const
632 // Guaranteed to succeed (use a static cast?)
633 return boost::dynamic_pointer_cast<MidiSource>(source(n));
636 /* don't use this. hopefully it will go away.
637 currently used by headless-chicken session utility.
640 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
644 _sources.push_back (s);
646 _master_sources.push_back (s);
649 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
654 MidiRegion::model_changed ()
660 /* build list of filtered Parameters, being those whose automation state is not `Play' */
662 _filtered_parameters.clear ();
664 Automatable::Controls const & c = model()->controls();
666 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
667 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
669 if (ac->alist()->automation_state() != Play) {
670 _filtered_parameters.insert (ac->parameter ());
674 /* watch for changes to controls' AutoState */
675 midi_source()->AutomationStateChanged.connect_same_thread (
676 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
679 model()->ContentsShifted.connect_same_thread (_model_shift_connection, boost::bind (&MidiRegion::model_shifted, this, _1));
680 model()->ContentsChanged.connect_same_thread (_model_changed_connection, boost::bind (&MidiRegion::model_contents_changed, this));
684 MidiRegion::model_contents_changed ()
686 std::cerr << "MIDI Region " << name() << " contents changed\n";
687 send_change (Properties::contents);
691 MidiRegion::model_shifted (double qn_distance)
697 if (!_ignore_shift) {
698 PropertyChange what_changed;
699 _start_beats += qn_distance;
700 samplepos_t const new_start = _session.tempo_map().samples_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
702 what_changed.add (Properties::start);
703 what_changed.add (Properties::start_beats);
704 what_changed.add (Properties::contents);
705 send_change (what_changed);
707 _ignore_shift = false;
712 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
714 /* Update our filtered parameters list after a change to a parameter's AutoState */
716 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
717 if (!ac || ac->alist()->automation_state() == Play) {
718 /* It should be "impossible" for ac to be NULL, but if it is, don't
719 filter the parameter so events aren't lost. */
720 _filtered_parameters.erase (p);
722 _filtered_parameters.insert (p);
725 /* the source will have an iterator into the model, and that iterator will have been set up
726 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
729 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
731 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
732 midi_source(0)->invalidate (lm);
736 /** This is called when a trim drag has resulted in a -ve _start time for this region.
737 * Fix it up by adding some empty space to the source.
740 MidiRegion::fix_negative_start ()
742 BeatsSamplesConverter c (_session.tempo_map(), _position);
744 _ignore_shift = true;
746 model()->insert_silence_at_start (Temporal::Beats (- _start_beats));
753 MidiRegion::set_start_internal (samplecnt_t s, const int32_t sub_num)
755 Region::set_start_internal (s, sub_num);
757 set_start_beats_from_start_samples ();
761 MidiRegion::trim_to_internal (samplepos_t position, samplecnt_t length, const int32_t sub_num)
767 PropertyChange what_changed;
770 /* Set position before length, otherwise for MIDI regions this bad thing happens:
771 * 1. we call set_length_internal; length in beats is computed using the region's current
772 * (soon-to-be old) position
773 * 2. we call set_position_internal; position is set and length in samples re-computed using
774 * length in beats from (1) but at the new position, which is wrong if the region
775 * straddles a tempo/meter change.
778 if (_position != position) {
780 const double pos_qn = _session.tempo_map().exact_qn_at_sample (position, sub_num);
781 const double old_pos_qn = quarter_note();
783 /* sets _pulse to new position.*/
784 set_position_internal (position, true, sub_num);
785 what_changed.add (Properties::position);
787 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
788 samplepos_t new_start = _session.tempo_map().samples_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
790 if (!verify_start_and_length (new_start, length)) {
794 _start_beats = new_start_qn;
795 what_changed.add (Properties::start_beats);
797 set_start_internal (new_start, sub_num);
798 what_changed.add (Properties::start);
801 if (_length != length) {
803 if (!verify_start_and_length (_start, length)) {
807 set_length_internal (length, sub_num);
808 what_changed.add (Properties::length);
809 what_changed.add (Properties::length_beats);
812 set_whole_file (false);
814 PropertyChange start_and_length;
816 start_and_length.add (Properties::start);
817 start_and_length.add (Properties::length);
819 if (what_changed.contains (start_and_length)) {
823 if (!what_changed.empty()) {
824 send_change (what_changed);