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" */
487 return Region::state ();
491 MidiRegion::set_state (const XMLNode& node, int version)
493 int ret = Region::set_state (node, version);
499 MidiRegion::recompute_at_end ()
501 /* our length has changed
502 * so what? stuck notes are dealt with via a note state tracker
507 MidiRegion::recompute_at_start ()
509 /* as above, but the shift was from the front
510 * maybe bump currently active note's note-ons up so they sound here?
511 * that could be undesireable in certain situations though.. maybe
512 * remove the note entirely, including it's note off? something needs to
513 * be done to keep the played MIDI sane to avoid messing up voices of
514 * polyhonic things etc........
519 MidiRegion::separate_by_channel (vector< boost::shared_ptr<Region> >&) const
525 boost::shared_ptr<Evoral::Control>
526 MidiRegion::control (const Evoral::Parameter& id, bool create)
528 return model()->control(id, create);
531 boost::shared_ptr<const Evoral::Control>
532 MidiRegion::control (const Evoral::Parameter& id) const
534 return model()->control(id);
537 boost::shared_ptr<MidiModel>
540 return midi_source()->model();
543 boost::shared_ptr<const MidiModel>
544 MidiRegion::model() const
546 return midi_source()->model();
549 boost::shared_ptr<MidiSource>
550 MidiRegion::midi_source (uint32_t n) const
552 // Guaranteed to succeed (use a static cast?)
553 return boost::dynamic_pointer_cast<MidiSource>(source(n));
556 /* don't use this. hopefully it will go away.
557 currently used by headless-chicken session utility.
560 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
564 _sources.push_back (s);
566 _master_sources.push_back (s);
569 s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
574 MidiRegion::model_changed ()
580 /* build list of filtered Parameters, being those whose automation state is not `Play' */
582 _filtered_parameters.clear ();
584 Automatable::Controls const & c = model()->controls();
586 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
587 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
589 if (ac->alist()->automation_state() != Play) {
590 _filtered_parameters.insert (ac->parameter ());
594 /* watch for changes to controls' AutoState */
595 midi_source()->AutomationStateChanged.connect_same_thread (
596 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
599 model()->ContentsShifted.connect_same_thread (_model_shift_connection, boost::bind (&MidiRegion::model_shifted, this, _1));
602 MidiRegion::model_shifted (double qn_distance)
608 if (!_ignore_shift) {
609 PropertyChange what_changed;
610 _start_beats += qn_distance;
611 samplepos_t const new_start = _session.tempo_map().samples_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
613 what_changed.add (Properties::start);
614 what_changed.add (Properties::start_beats);
615 send_change (what_changed);
617 _ignore_shift = false;
622 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
624 /* Update our filtered parameters list after a change to a parameter's AutoState */
626 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
627 if (!ac || ac->alist()->automation_state() == Play) {
628 /* It should be "impossible" for ac to be NULL, but if it is, don't
629 filter the parameter so events aren't lost. */
630 _filtered_parameters.erase (p);
632 _filtered_parameters.insert (p);
635 /* the source will have an iterator into the model, and that iterator will have been set up
636 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
639 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
641 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
642 midi_source(0)->invalidate (lm);
646 /** This is called when a trim drag has resulted in a -ve _start time for this region.
647 * Fix it up by adding some empty space to the source.
650 MidiRegion::fix_negative_start ()
652 BeatsSamplesConverter c (_session.tempo_map(), _position);
654 _ignore_shift = true;
656 model()->insert_silence_at_start (Temporal::Beats (- _start_beats));
663 MidiRegion::set_start_internal (samplecnt_t s, const int32_t sub_num)
665 Region::set_start_internal (s, sub_num);
667 set_start_beats_from_start_samples ();
671 MidiRegion::trim_to_internal (samplepos_t position, samplecnt_t length, const int32_t sub_num)
677 PropertyChange what_changed;
680 /* Set position before length, otherwise for MIDI regions this bad thing happens:
681 * 1. we call set_length_internal; length in beats is computed using the region's current
682 * (soon-to-be old) position
683 * 2. we call set_position_internal; position is set and length in samples re-computed using
684 * length in beats from (1) but at the new position, which is wrong if the region
685 * straddles a tempo/meter change.
688 if (_position != position) {
690 const double pos_qn = _session.tempo_map().exact_qn_at_sample (position, sub_num);
691 const double old_pos_qn = quarter_note();
693 /* sets _pulse to new position.*/
694 set_position_internal (position, true, sub_num);
695 what_changed.add (Properties::position);
697 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
698 samplepos_t new_start = _session.tempo_map().samples_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
700 if (!verify_start_and_length (new_start, length)) {
704 _start_beats = new_start_qn;
705 what_changed.add (Properties::start_beats);
707 set_start_internal (new_start, sub_num);
708 what_changed.add (Properties::start);
711 if (_length != length) {
713 if (!verify_start_and_length (_start, length)) {
717 set_length_internal (length, sub_num);
718 what_changed.add (Properties::length);
719 what_changed.add (Properties::length_beats);
722 set_whole_file (false);
724 PropertyChange start_and_length;
726 start_and_length.add (Properties::start);
727 start_and_length.add (Properties::length);
729 if (what_changed.contains (start_and_length)) {
733 if (!what_changed.empty()) {
734 send_change (what_changed);