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 ();
85 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
87 assert(_name.val().find("/") == string::npos);
88 assert(_type == DataType::MIDI);
91 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
93 , _start_beats (Properties::start_beats, other->_start_beats)
94 , _length_beats (Properties::length_beats, other->_length_beats)
96 //update_length_beats ();
97 register_properties ();
99 assert(_name.val().find("/") == string::npos);
100 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
104 /** Create a new MidiRegion that is part of an existing one */
105 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
106 : Region (other, offset, sub_num)
107 , _start_beats (Properties::start_beats, Evoral::Beats())
108 , _length_beats (Properties::length_beats, other->_length_beats)
110 _start_beats = Evoral::Beats (_session.tempo_map().exact_beat_at_frame ((other->_position + offset), sub_num) - other->beat()) + 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());
203 MidiRegion::post_set (const PropertyChange& pc)
205 Region::post_set (pc);
207 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
208 /* update non-musically */
209 update_length_beats (0);
210 } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
211 set_start_beats_from_start_frames ();
216 MidiRegion::set_start_beats_from_start_frames ()
218 _start_beats = Evoral::Beats (beat() - _session.tempo_map().beat_at_frame (_position - _start));
222 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
224 Region::set_length_internal (len, sub_num);
225 update_length_beats (sub_num);
229 MidiRegion::update_after_tempo_map_change (bool /* send */)
231 boost::shared_ptr<Playlist> pl (playlist());
237 const framepos_t old_pos = _position;
238 const framepos_t old_length = _length;
239 const framepos_t old_start = _start;
241 PropertyChange s_and_l;
243 if (position_lock_style() == AudioTime) {
244 recompute_position_from_lock_style (0);
247 set _start to new position in tempo map.
249 The user probably expects the region contents to maintain audio position as the
250 tempo changes, but AFAICT this requires modifying the src file to use
251 SMPTE timestamps with the current disk read model (?).
253 We could arguably use _start to set _start_beats here,
254 resulting in viewport-like behaviour (the contents maintain
255 their musical position while the region is stationary).
257 For now, the musical position at the region start is retained, but subsequent events
258 will maintain their beat distance according to the map.
260 _start = _position - _session.tempo_map().frame_at_beat (beat() - _start_beats.val().to_double());
262 /* _length doesn't change for audio-locked regions. update length_beats to match. */
263 _length_beats = Evoral::Beats (_session.tempo_map().beat_at_frame (_position + _length) - _session.tempo_map().beat_at_frame (_position));
265 s_and_l.add (Properties::start);
266 s_and_l.add (Properties::length_beats);
268 send_change (s_and_l);
272 Region::update_after_tempo_map_change (false);
274 /* _start has now been updated. */
275 _length = _session.tempo_map().frame_at_beat (beat() + _length_beats.val().to_double()) - _position;
277 if (old_start != _start) {
278 s_and_l.add (Properties::start);
280 if (old_length != _length) {
281 s_and_l.add (Properties::length);
283 if (old_pos != _position) {
284 s_and_l.add (Properties::position);
287 send_change (s_and_l);
291 MidiRegion::update_length_beats (const int32_t sub_num)
293 _length_beats = Evoral::Beats (_session.tempo_map().exact_beat_at_frame (_position + _length, sub_num) - beat());
297 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
299 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
301 /* set _start to new position in tempo map */
302 _start = _position - _session.tempo_map().frame_at_beat (beat() - _start_beats.val().to_double());
304 /* in construction from src */
305 if (_length_beats == Evoral::Beats()) {
306 update_length_beats (sub_num);
309 if (position_lock_style() == AudioTime) {
310 _length_beats = Evoral::Beats (_session.tempo_map().beat_at_frame (_position + _length) - _session.tempo_map().beat_at_frame (_position));
312 /* leave _length_beats alone, and change _length to reflect the state of things
313 at the new position (tempo map may dictate a different number of frames).
315 Region::set_length_internal (_session.tempo_map().frame_at_beat (beat() + _length_beats.val().to_double()) - _position, sub_num);
320 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
325 MidiStateTracker* tracker,
326 MidiChannelFilter* filter) const
328 return _read_at (_sources, out, position, dur, chan_n, mode, tracker, filter);
332 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode) const
334 return _read_at (_master_sources, out, position, dur, chan_n, mode); /* no tracker */
338 MidiRegion::_read_at (const SourceList& /*srcs*/,
339 Evoral::EventSink<framepos_t>& dst,
344 MidiStateTracker* tracker,
345 MidiChannelFilter* filter) const
347 frameoffset_t internal_offset = 0;
348 framecnt_t to_read = 0;
350 /* precondition: caller has verified that we cover the desired section */
355 return 0; /* read nothing */
358 if (position < _position) {
359 /* we are starting the read from before the start of the region */
361 dur -= _position - position;
363 /* we are starting the read from after the start of the region */
364 internal_offset = position - _position;
367 if (internal_offset >= _length) {
368 return 0; /* read nothing */
371 if ((to_read = min (dur, _length - internal_offset)) == 0) {
372 return 0; /* read nothing */
375 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
377 Glib::Threads::Mutex::Lock lm(src->mutex());
379 src->set_note_mode(lm, mode);
382 cerr << "MR " << name () << " read @ " << position << " * " << to_read
383 << " _position = " << _position
384 << " _start = " << _start
385 << " intoffset = " << internal_offset
389 /* This call reads events from a source and writes them to `dst' timed in session frames */
393 dst, // destination buffer
394 _position - _start, // start position of the source in session frames
395 _start + internal_offset, // where to start reading in the source
396 to_read, // read duration in frames
399 _filtered_parameters,
401 _start_beats.val().to_double()
403 return 0; /* "read nothing" */
412 return Region::state ();
416 MidiRegion::set_state (const XMLNode& node, int version)
418 int ret = Region::set_state (node, version);
421 /* set length beats to the frame (non-musical) */
422 if (position_lock_style() == AudioTime) {
423 update_length_beats (0);
431 MidiRegion::recompute_at_end ()
433 /* our length has changed
434 * so what? stuck notes are dealt with via a note state tracker
439 MidiRegion::recompute_at_start ()
441 /* as above, but the shift was from the front
442 * maybe bump currently active note's note-ons up so they sound here?
443 * that could be undesireable in certain situations though.. maybe
444 * remove the note entirely, including it's note off? something needs to
445 * be done to keep the played MIDI sane to avoid messing up voices of
446 * polyhonic things etc........
451 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
457 boost::shared_ptr<Evoral::Control>
458 MidiRegion::control (const Evoral::Parameter& id, bool create)
460 return model()->control(id, create);
463 boost::shared_ptr<const Evoral::Control>
464 MidiRegion::control (const Evoral::Parameter& id) const
466 return model()->control(id);
469 boost::shared_ptr<MidiModel>
472 return midi_source()->model();
475 boost::shared_ptr<const MidiModel>
476 MidiRegion::model() const
478 return midi_source()->model();
481 boost::shared_ptr<MidiSource>
482 MidiRegion::midi_source (uint32_t n) const
484 // Guaranteed to succeed (use a static cast?)
485 return boost::dynamic_pointer_cast<MidiSource>(source(n));
489 MidiRegion::model_changed ()
495 /* build list of filtered Parameters, being those whose automation state is not `Play' */
497 _filtered_parameters.clear ();
499 Automatable::Controls const & c = model()->controls();
501 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
502 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
504 if (ac->alist()->automation_state() != Play) {
505 _filtered_parameters.insert (ac->parameter ());
509 /* watch for changes to controls' AutoState */
510 midi_source()->AutomationStateChanged.connect_same_thread (
511 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
516 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
518 /* Update our filtered parameters list after a change to a parameter's AutoState */
520 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
521 if (!ac || ac->alist()->automation_state() == Play) {
522 /* It should be "impossible" for ac to be NULL, but if it is, don't
523 filter the parameter so events aren't lost. */
524 _filtered_parameters.erase (p);
526 _filtered_parameters.insert (p);
529 /* the source will have an iterator into the model, and that iterator will have been set up
530 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
533 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
535 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
536 midi_source(0)->invalidate (lm);
540 /** This is called when a trim drag has resulted in a -ve _start time for this region.
541 * Fix it up by adding some empty space to the source.
544 MidiRegion::fix_negative_start ()
546 BeatsFramesConverter c (_session.tempo_map(), _position);
548 model()->insert_silence_at_start (c.from (-_start));
550 _start_beats = Evoral::Beats();
554 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
556 Region::set_start_internal (s, sub_num);
558 if (position_lock_style() == AudioTime) {
559 set_start_beats_from_start_frames ();
564 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
570 PropertyChange what_changed;
572 /* beat has been set exactly by set_position_internal, but the source starts on a frame.
573 working in beats seems the correct thing to do, but reports of a missing first note
574 on playback suggest otherwise. for now, we work in exact beats.
576 const double pos_beat = _session.tempo_map().exact_beat_at_frame (position, sub_num);
577 const double beat_delta = pos_beat - beat();
579 /* Set position before length, otherwise for MIDI regions this bad thing happens:
580 * 1. we call set_length_internal; length in beats is computed using the region's current
581 * (soon-to-be old) position
582 * 2. we call set_position_internal; position is set and length in frames re-computed using
583 * length in beats from (1) but at the new position, which is wrong if the region
584 * straddles a tempo/meter change.
587 if (_position != position) {
588 /* sets _beat to new position.*/
589 set_position_internal (position, true, sub_num);
590 what_changed.add (Properties::position);
592 const double new_start_beat = _start_beats.val().to_double() + beat_delta;
593 const framepos_t new_start = _position - _session.tempo_map().frame_at_beat (beat() - new_start_beat);
595 if (!verify_start_and_length (new_start, length)) {
599 _start_beats = Evoral::Beats (new_start_beat);
600 what_changed.add (Properties::start_beats);
602 set_start_internal (new_start, sub_num);
603 what_changed.add (Properties::start);
606 if (_length != length) {
608 if (!verify_start_and_length (_start, length)) {
612 set_length_internal (length, sub_num);
613 what_changed.add (Properties::length);
614 what_changed.add (Properties::length_beats);
617 set_whole_file (false);
619 PropertyChange start_and_length;
621 start_and_length.add (Properties::start);
622 start_and_length.add (Properties::length);
624 if (what_changed.contains (start_and_length)) {
628 if (!what_changed.empty()) {
629 send_change (what_changed);