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_cursor.h"
38 #include "ardour/midi_model.h"
39 #include "ardour/midi_region.h"
40 #include "ardour/midi_ring_buffer.h"
41 #include "ardour/midi_source.h"
42 #include "ardour/region_factory.h"
43 #include "ardour/session.h"
44 #include "ardour/source_factory.h"
45 #include "ardour/tempo.h"
46 #include "ardour/types.h"
52 using namespace ARDOUR;
56 namespace Properties {
57 PBD::PropertyDescriptor<double> start_beats;
58 PBD::PropertyDescriptor<double> length_beats;
63 MidiRegion::make_property_quarks ()
65 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
66 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
67 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
68 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
72 MidiRegion::register_properties ()
74 add_property (_start_beats);
75 add_property (_length_beats);
78 /* Basic MidiRegion constructor (many channels) */
79 MidiRegion::MidiRegion (const SourceList& srcs)
81 , _start_beats (Properties::start_beats, 0.0)
82 , _length_beats (Properties::length_beats, midi_source(0)->length_beats().to_double())
84 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, 0.0)
108 , _length_beats (Properties::length_beats, other->_length_beats)
110 _start_beats = (_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) - other->_quarter_note) + other->_start_beats;
112 update_length_beats (sub_num);
113 register_properties ();
115 assert(_name.val().find("/") == string::npos);
116 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
120 MidiRegion::~MidiRegion ()
124 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
127 MidiRegion::do_export (string path) const
129 boost::shared_ptr<MidiSource> newsrc;
131 /* caller must check for pre-existing file */
132 assert (!path.empty());
133 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
134 newsrc = boost::dynamic_pointer_cast<MidiSource>(
135 SourceFactory::createWritable(DataType::MIDI, _session,
136 path, false, _session.frame_rate()));
138 BeatsFramesConverter bfc (_session.tempo_map(), _position);
139 Evoral::Beats const bbegin = bfc.from (_start);
140 Evoral::Beats const bend = bfc.from (_start + _length);
143 /* Lock our source since we'll be reading from it. write_to() will
144 take a lock on newsrc. */
145 Source::Lock lm (midi_source(0)->mutex());
146 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
155 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
157 boost::shared_ptr<MidiRegion>
158 MidiRegion::clone (string path) const
160 boost::shared_ptr<MidiSource> newsrc;
162 /* caller must check for pre-existing file */
163 assert (!path.empty());
164 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
165 newsrc = boost::dynamic_pointer_cast<MidiSource>(
166 SourceFactory::createWritable(DataType::MIDI, _session,
167 path, false, _session.frame_rate()));
168 return clone (newsrc);
171 boost::shared_ptr<MidiRegion>
172 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
174 BeatsFramesConverter bfc (_session.tempo_map(), _position);
175 Evoral::Beats const bbegin = bfc.from (_start);
176 Evoral::Beats const bend = bfc.from (_start + _length);
179 /* Lock our source since we'll be reading from it. write_to() will
180 take a lock on newsrc. */
181 Source::Lock lm (midi_source(0)->mutex());
182 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
183 return boost::shared_ptr<MidiRegion> ();
189 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
190 plist.add (Properties::whole_file, true);
191 plist.add (Properties::start, _start);
192 plist.add (Properties::start_beats, _start_beats);
193 plist.add (Properties::length, _length);
194 plist.add (Properties::beat, _beat);
195 plist.add (Properties::length_beats, _length_beats);
196 plist.add (Properties::layer, 0);
198 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
199 ret->set_quarter_note (quarter_note());
205 MidiRegion::post_set (const PropertyChange& pc)
207 Region::post_set (pc);
209 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
210 /* we're called by Stateful::set_values() which sends a change
211 only if the value is different from _current.
212 session load means we can clobber length_beats here in error (not all properties differ from current),
213 so disallow (this has been set from XML state anyway).
215 if (!_session.loading()) {
216 update_length_beats (0);
220 if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
221 set_start_beats_from_start_frames ();
226 MidiRegion::set_start_beats_from_start_frames ()
228 if (position_lock_style() == AudioTime) {
229 _start_beats = quarter_note() - _session.tempo_map().quarter_note_at_frame (_position - _start);
234 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
236 Region::set_length_internal (len, sub_num);
237 update_length_beats (sub_num);
241 MidiRegion::update_after_tempo_map_change (bool /* send */)
243 boost::shared_ptr<Playlist> pl (playlist());
249 const framepos_t old_pos = _position;
250 const framepos_t old_length = _length;
251 const framepos_t old_start = _start;
253 PropertyChange s_and_l;
255 if (position_lock_style() == AudioTime) {
256 recompute_position_from_lock_style (0);
259 set _start to new position in tempo map.
261 The user probably expects the region contents to maintain audio position as the
262 tempo changes, but AFAICT this requires modifying the src file to use
263 SMPTE timestamps with the current disk read model (?).
265 We could arguably use _start to set _start_beats here,
266 resulting in viewport-like behaviour (the contents maintain
267 their musical position while the region is stationary).
269 For now, the musical position at the region start is retained, but subsequent events
270 will maintain their beat distance according to the map.
272 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
274 /* _length doesn't change for audio-locked regions. update length_beats to match. */
275 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
277 s_and_l.add (Properties::start);
278 s_and_l.add (Properties::length_beats);
280 send_change (s_and_l);
284 Region::update_after_tempo_map_change (false);
286 /* _start has now been updated. */
287 _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + _length_beats));
289 if (old_start != _start) {
290 s_and_l.add (Properties::start);
292 if (old_length != _length) {
293 s_and_l.add (Properties::length);
295 if (old_pos != _position) {
296 s_and_l.add (Properties::position);
299 send_change (s_and_l);
303 MidiRegion::update_length_beats (const int32_t sub_num)
305 _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - quarter_note();
309 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
311 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
313 /* don't clobber _start _length and _length_beats if session loading.*/
314 if (_session.loading()) {
318 /* set _start to new position in tempo map */
319 _start = _session.tempo_map().frames_between_quarter_notes (quarter_note() - start_beats(), quarter_note());
321 /* in construction from src */
322 if (_length_beats == 0.0) {
323 update_length_beats (sub_num);
326 if (position_lock_style() == AudioTime) {
327 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - quarter_note();
329 /* leave _length_beats alone, and change _length to reflect the state of things
330 at the new position (tempo map may dictate a different number of frames).
332 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (quarter_note(), quarter_note() + length_beats()), sub_num);
337 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
340 Evoral::Range<framepos_t>* loop_range,
344 MidiStateTracker* tracker,
345 MidiChannelFilter* filter) const
347 return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
351 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
354 Evoral::Range<framepos_t>* loop_range,
359 return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
363 MidiRegion::_read_at (const SourceList& /*srcs*/,
364 Evoral::EventSink<framepos_t>& dst,
367 Evoral::Range<framepos_t>* loop_range,
371 MidiStateTracker* tracker,
372 MidiChannelFilter* filter) const
374 frameoffset_t internal_offset = 0;
375 framecnt_t to_read = 0;
377 /* precondition: caller has verified that we cover the desired section */
382 return 0; /* read nothing */
385 if (position < _position) {
386 /* we are starting the read from before the start of the region */
388 dur -= _position - position;
390 /* we are starting the read from after the start of the region */
391 internal_offset = position - _position;
394 if (internal_offset >= _length) {
395 return 0; /* read nothing */
398 if ((to_read = min (dur, _length - internal_offset)) == 0) {
399 return 0; /* read nothing */
402 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
404 Glib::Threads::Mutex::Lock lm(src->mutex());
406 src->set_note_mode(lm, mode);
409 cerr << "MR " << name () << " read @ " << position << " + " << to_read
410 << " dur was " << dur
411 << " len " << _length
412 << " l-io " << (_length - internal_offset)
413 << " _position = " << _position
414 << " _start = " << _start
415 << " intoffset = " << internal_offset
416 << " quarter_note = " << quarter_note()
417 << " start_beat = " << _start_beats
421 /* This call reads events from a source and writes them to `dst' timed in session frames */
425 dst, // destination buffer
426 _position - _start, // start position of the source in session frames
427 _start + internal_offset, // where to start reading in the source
428 to_read, // read duration in frames
433 _filtered_parameters,
437 return 0; /* "read nothing" */
446 return Region::state ();
450 MidiRegion::set_state (const XMLNode& node, int version)
452 int ret = Region::set_state (node, version);
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));
598 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
600 Region::set_start_internal (s, sub_num);
601 set_start_beats_from_start_frames ();
605 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
611 PropertyChange what_changed;
614 /* Set position before length, otherwise for MIDI regions this bad thing happens:
615 * 1. we call set_length_internal; length in beats is computed using the region's current
616 * (soon-to-be old) position
617 * 2. we call set_position_internal; position is set and length in frames re-computed using
618 * length in beats from (1) but at the new position, which is wrong if the region
619 * straddles a tempo/meter change.
622 if (_position != position) {
624 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
625 const double old_pos_qn = quarter_note();
627 /* sets _pulse to new position.*/
628 set_position_internal (position, true, sub_num);
629 what_changed.add (Properties::position);
631 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
632 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
634 if (!verify_start_and_length (new_start, length)) {
638 _start_beats = new_start_qn;
639 what_changed.add (Properties::start_beats);
641 set_start_internal (new_start, sub_num);
642 what_changed.add (Properties::start);
645 if (_length != length) {
647 if (!verify_start_and_length (_start, length)) {
651 set_length_internal (length, sub_num);
652 what_changed.add (Properties::length);
653 what_changed.add (Properties::length_beats);
656 set_whole_file (false);
658 PropertyChange start_and_length;
660 start_and_length.add (Properties::start);
661 start_and_length.add (Properties::length);
663 if (what_changed.contains (start_and_length)) {
667 if (!what_changed.empty()) {
668 send_change (what_changed);