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;
58 PBD::PropertyDescriptor<double> start_pulse;
59 PBD::PropertyDescriptor<double> length_pulse;
64 MidiRegion::make_property_quarks ()
66 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
67 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
68 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
69 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
70 Properties::start_pulse.property_id = g_quark_from_static_string (X_("start-pulse"));
71 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-pulse = %1\n", Properties::start_pulse.property_id));
72 Properties::length_pulse.property_id = g_quark_from_static_string (X_("length-pulse"));
73 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-pulse = %1\n", Properties::length_pulse.property_id));
77 MidiRegion::register_properties ()
79 add_property (_start_beats);
80 add_property (_length_beats);
81 add_property (_start_pulse);
82 add_property (_length_pulse);
85 /* Basic MidiRegion constructor (many channels) */
86 MidiRegion::MidiRegion (const SourceList& srcs)
88 , _start_beats (Properties::start_beats, Evoral::Beats())
89 , _length_beats (Properties::length_beats, midi_source(0)->length_beats())
90 , _start_pulse (Properties::start_pulse, 0)
91 , _length_pulse (Properties::length_pulse, midi_source(0)->length_pulse())
93 register_properties ();
95 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
97 assert(_name.val().find("/") == string::npos);
98 assert(_type == DataType::MIDI);
101 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
103 , _start_beats (Properties::start_beats, other->_start_beats)
104 , _length_beats (Properties::length_beats, other->_length_beats)
105 , _start_pulse (Properties::start_pulse, other->_start_pulse)
106 , _length_pulse (Properties::length_pulse, other->_length_pulse)
108 //update_length_beats ();
109 register_properties ();
111 assert(_name.val().find("/") == string::npos);
112 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
116 /** Create a new MidiRegion that is part of an existing one */
117 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
118 : Region (other, offset, sub_num)
119 , _start_beats (Properties::start_beats, Evoral::Beats())
120 , _length_beats (Properties::length_beats, other->_length_beats)
121 , _start_pulse (Properties::start_pulse, 0)
122 , _length_pulse (Properties::length_pulse, other->_length_pulse)
124 _start_beats = Evoral::Beats (_session.tempo_map().exact_beat_at_frame (other->_position + offset, sub_num) - other->beat()) + other->_start_beats;
125 _start_pulse = ((_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) / 4.0) - other->_pulse) + other->_start_pulse;
127 update_length_beats (sub_num);
128 register_properties ();
130 assert(_name.val().find("/") == string::npos);
131 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
135 MidiRegion::~MidiRegion ()
139 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
142 MidiRegion::do_export (string path) const
144 boost::shared_ptr<MidiSource> newsrc;
146 /* caller must check for pre-existing file */
147 assert (!path.empty());
148 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
149 newsrc = boost::dynamic_pointer_cast<MidiSource>(
150 SourceFactory::createWritable(DataType::MIDI, _session,
151 path, false, _session.frame_rate()));
153 BeatsFramesConverter bfc (_session.tempo_map(), _position);
154 Evoral::Beats const bbegin = bfc.from (_start);
155 Evoral::Beats const bend = bfc.from (_start + _length);
158 /* Lock our source since we'll be reading from it. write_to() will
159 take a lock on newsrc. */
160 Source::Lock lm (midi_source(0)->mutex());
161 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
170 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
172 boost::shared_ptr<MidiRegion>
173 MidiRegion::clone (string path) const
175 boost::shared_ptr<MidiSource> newsrc;
177 /* caller must check for pre-existing file */
178 assert (!path.empty());
179 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
180 newsrc = boost::dynamic_pointer_cast<MidiSource>(
181 SourceFactory::createWritable(DataType::MIDI, _session,
182 path, false, _session.frame_rate()));
183 return clone (newsrc);
186 boost::shared_ptr<MidiRegion>
187 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
189 BeatsFramesConverter bfc (_session.tempo_map(), _position);
190 Evoral::Beats const bbegin = bfc.from (_start);
191 Evoral::Beats const bend = bfc.from (_start + _length);
194 /* Lock our source since we'll be reading from it. write_to() will
195 take a lock on newsrc. */
196 Source::Lock lm (midi_source(0)->mutex());
197 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
198 return boost::shared_ptr<MidiRegion> ();
204 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
205 plist.add (Properties::whole_file, true);
206 plist.add (Properties::start, _start);
207 plist.add (Properties::start_beats, _start_beats);
208 plist.add (Properties::length, _length);
209 plist.add (Properties::length_beats, _length_beats);
210 plist.add (Properties::start_pulse, _start_pulse);
211 plist.add (Properties::length_pulse, _length_pulse);
212 plist.add (Properties::layer, 0);
214 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
215 ret->set_beat (beat());
216 ret->set_pulse (pulse());
222 MidiRegion::post_set (const PropertyChange& pc)
224 Region::post_set (pc);
226 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
227 /* update non-musically */
228 update_length_beats (0);
229 } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
230 set_start_beats_from_start_frames ();
235 MidiRegion::set_start_beats_from_start_frames ()
237 _start_beats = Evoral::Beats (beat() - _session.tempo_map().beat_at_frame (_position - _start));
238 _start_pulse = pulse() - _session.tempo_map().pulse_at_frame (_position - _start);
242 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
244 Region::set_length_internal (len, sub_num);
245 update_length_beats (sub_num);
249 MidiRegion::update_after_tempo_map_change (bool /* send */)
251 boost::shared_ptr<Playlist> pl (playlist());
257 const framepos_t old_pos = _position;
258 const framepos_t old_length = _length;
259 const framepos_t old_start = _start;
261 PropertyChange s_and_l;
263 if (position_lock_style() == AudioTime) {
264 recompute_position_from_lock_style (0);
267 set _start to new position in tempo map.
269 The user probably expects the region contents to maintain audio position as the
270 tempo changes, but AFAICT this requires modifying the src file to use
271 SMPTE timestamps with the current disk read model (?).
273 We could arguably use _start to set _start_beats here,
274 resulting in viewport-like behaviour (the contents maintain
275 their musical position while the region is stationary).
277 For now, the musical position at the region start is retained, but subsequent events
278 will maintain their beat distance according to the map.
280 _start = _position - _session.tempo_map().frame_at_beat (beat() - _start_beats.val().to_double());
282 /* _length doesn't change for audio-locked regions. update length_beats to match. */
283 _length_beats = Evoral::Beats (_session.tempo_map().beat_at_frame (_position + _length) - _session.tempo_map().beat_at_frame (_position));
284 _length_pulse = _session.tempo_map().pulse_at_frame (_position + _length) - _session.tempo_map().pulse_at_frame (_position);
286 s_and_l.add (Properties::start);
287 s_and_l.add (Properties::length_beats);
289 send_change (s_and_l);
293 Region::update_after_tempo_map_change (false);
295 /* _start has now been updated. */
296 _length = _session.tempo_map().frame_at_pulse (pulse() + _length_pulse) - _position;
298 if (old_start != _start) {
299 s_and_l.add (Properties::start);
301 if (old_length != _length) {
302 s_and_l.add (Properties::length);
304 if (old_pos != _position) {
305 s_and_l.add (Properties::position);
308 send_change (s_and_l);
312 MidiRegion::update_length_beats (const int32_t sub_num)
314 _length_beats = Evoral::Beats (_session.tempo_map().exact_beat_at_frame (_position + _length, sub_num) - beat());
315 _length_pulse = (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) / 4.0) - pulse();
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 /* set _start to new position in tempo map */
324 _start = _position - _session.tempo_map().frame_at_beat (beat() - _start_beats.val().to_double());
326 /* in construction from src */
327 if (_length_beats == Evoral::Beats()) {
328 update_length_beats (sub_num);
331 if (position_lock_style() == AudioTime) {
332 _length_beats = Evoral::Beats (_session.tempo_map().beat_at_frame (_position + _length) - _session.tempo_map().beat_at_frame (_position));
333 _length_pulse = _session.tempo_map().pulse_at_frame (_position + _length) - _session.tempo_map().pulse_at_frame (_position);
335 /* leave _length_beats alone, and change _length to reflect the state of things
336 at the new position (tempo map may dictate a different number of frames).
338 Region::set_length_internal (_session.tempo_map().frame_at_pulse (pulse() + _length_pulse) - _position, sub_num);
343 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
348 MidiStateTracker* tracker,
349 MidiChannelFilter* filter) const
351 return _read_at (_sources, out, position, dur, chan_n, mode, tracker, filter);
355 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode) const
357 return _read_at (_master_sources, out, position, dur, chan_n, mode); /* no tracker */
361 MidiRegion::_read_at (const SourceList& /*srcs*/,
362 Evoral::EventSink<framepos_t>& dst,
367 MidiStateTracker* tracker,
368 MidiChannelFilter* filter) const
370 frameoffset_t internal_offset = 0;
371 framecnt_t to_read = 0;
373 /* precondition: caller has verified that we cover the desired section */
378 return 0; /* read nothing */
381 if (position < _position) {
382 /* we are starting the read from before the start of the region */
384 dur -= _position - position;
386 /* we are starting the read from after the start of the region */
387 internal_offset = position - _position;
390 if (internal_offset >= _length) {
391 return 0; /* read nothing */
394 if ((to_read = min (dur, _length - internal_offset)) == 0) {
395 return 0; /* read nothing */
398 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
400 Glib::Threads::Mutex::Lock lm(src->mutex());
402 src->set_note_mode(lm, mode);
405 cerr << "MR " << name () << " read @ " << position << " * " << to_read
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
425 _filtered_parameters,
429 return 0; /* "read nothing" */
438 return Region::state ();
442 MidiRegion::set_state (const XMLNode& node, int version)
444 int ret = Region::set_state (node, version);
447 /* set length beats to the frame (non-musical) */
448 if (position_lock_style() == AudioTime) {
449 update_length_beats (0);
457 MidiRegion::recompute_at_end ()
459 /* our length has changed
460 * so what? stuck notes are dealt with via a note state tracker
465 MidiRegion::recompute_at_start ()
467 /* as above, but the shift was from the front
468 * maybe bump currently active note's note-ons up so they sound here?
469 * that could be undesireable in certain situations though.. maybe
470 * remove the note entirely, including it's note off? something needs to
471 * be done to keep the played MIDI sane to avoid messing up voices of
472 * polyhonic things etc........
477 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
483 boost::shared_ptr<Evoral::Control>
484 MidiRegion::control (const Evoral::Parameter& id, bool create)
486 return model()->control(id, create);
489 boost::shared_ptr<const Evoral::Control>
490 MidiRegion::control (const Evoral::Parameter& id) const
492 return model()->control(id);
495 boost::shared_ptr<MidiModel>
498 return midi_source()->model();
501 boost::shared_ptr<const MidiModel>
502 MidiRegion::model() const
504 return midi_source()->model();
507 boost::shared_ptr<MidiSource>
508 MidiRegion::midi_source (uint32_t n) const
510 // Guaranteed to succeed (use a static cast?)
511 return boost::dynamic_pointer_cast<MidiSource>(source(n));
515 MidiRegion::model_changed ()
521 /* build list of filtered Parameters, being those whose automation state is not `Play' */
523 _filtered_parameters.clear ();
525 Automatable::Controls const & c = model()->controls();
527 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
528 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
530 if (ac->alist()->automation_state() != Play) {
531 _filtered_parameters.insert (ac->parameter ());
535 /* watch for changes to controls' AutoState */
536 midi_source()->AutomationStateChanged.connect_same_thread (
537 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
542 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
544 /* Update our filtered parameters list after a change to a parameter's AutoState */
546 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
547 if (!ac || ac->alist()->automation_state() == Play) {
548 /* It should be "impossible" for ac to be NULL, but if it is, don't
549 filter the parameter so events aren't lost. */
550 _filtered_parameters.erase (p);
552 _filtered_parameters.insert (p);
555 /* the source will have an iterator into the model, and that iterator will have been set up
556 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
559 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
561 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
562 midi_source(0)->invalidate (lm);
566 /** This is called when a trim drag has resulted in a -ve _start time for this region.
567 * Fix it up by adding some empty space to the source.
570 MidiRegion::fix_negative_start ()
572 BeatsFramesConverter c (_session.tempo_map(), _position);
574 model()->insert_silence_at_start (c.from (-_start));
576 _start_beats = Evoral::Beats();
581 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
583 Region::set_start_internal (s, sub_num);
585 if (position_lock_style() == AudioTime) {
586 set_start_beats_from_start_frames ();
591 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
597 PropertyChange what_changed;
599 /* beat has been set exactly by set_position_internal, but the source starts on a frame.
600 working in beats seems the correct thing to do, but reports of a missing first note
601 on playback suggest otherwise. for now, we work in exact beats.
603 const double pos_beat = _session.tempo_map().exact_beat_at_frame (position, sub_num);
604 const double beat_delta = pos_beat - beat();
605 const double pos_pulse = _session.tempo_map().exact_qn_at_frame (position, sub_num) / 4.0;
606 const double pulse_delta = pos_pulse - pulse();
608 /* Set position before length, otherwise for MIDI regions this bad thing happens:
609 * 1. we call set_length_internal; length in beats is computed using the region's current
610 * (soon-to-be old) position
611 * 2. we call set_position_internal; position is set and length in frames re-computed using
612 * length in beats from (1) but at the new position, which is wrong if the region
613 * straddles a tempo/meter change.
616 if (_position != position) {
617 /* sets _beat to new position.*/
618 set_position_internal (position, true, sub_num);
619 what_changed.add (Properties::position);
621 const double new_start_beat = _start_beats.val().to_double() + beat_delta;
622 const double new_start_pulse = _start_pulse + pulse_delta;
623 const framepos_t new_start = _position - _session.tempo_map().frame_at_pulse (pulse() - new_start_pulse);
625 if (!verify_start_and_length (new_start, length)) {
629 _start_beats = Evoral::Beats (new_start_beat);
630 what_changed.add (Properties::start_beats);
632 _start_pulse = new_start_pulse;
633 what_changed.add (Properties::start_pulse);
635 set_start_internal (new_start, sub_num);
636 what_changed.add (Properties::start);
639 if (_length != length) {
641 if (!verify_start_and_length (_start, length)) {
645 set_length_internal (length, sub_num);
646 what_changed.add (Properties::length);
647 what_changed.add (Properties::length_beats);
650 set_whole_file (false);
652 PropertyChange start_and_length;
654 start_and_length.add (Properties::start);
655 start_and_length.add (Properties::length);
657 if (what_changed.contains (start_and_length)) {
661 if (!what_changed.empty()) {
662 send_change (what_changed);