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 , _length_pulse (midi_source(0)->length_pulse())
85 register_properties ();
86 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
88 assert(_name.val().find("/") == string::npos);
89 assert(_type == DataType::MIDI);
92 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
94 , _start_beats (Properties::start_beats, other->_start_beats)
95 , _length_beats (Properties::length_beats, other->_length_beats)
96 , _start_pulse (other->_start_pulse)
97 , _length_pulse (other->_length_pulse)
99 //update_length_beats ();
100 register_properties ();
102 assert(_name.val().find("/") == string::npos);
103 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
107 /** Create a new MidiRegion that is part of an existing one */
108 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
109 : Region (other, offset, sub_num)
110 , _start_beats (Properties::start_beats, Evoral::Beats())
111 , _length_beats (Properties::length_beats, other->_length_beats)
113 , _length_pulse (other->_length_pulse)
115 _start_beats = Evoral::Beats (_session.tempo_map().exact_beat_at_frame (other->_position + offset, sub_num) - other->beat()) + other->_start_beats;
116 _start_pulse = ((_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) / 4.0) - other->_pulse) + other->_start_pulse;
118 update_length_beats (sub_num);
119 register_properties ();
121 assert(_name.val().find("/") == string::npos);
122 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
126 MidiRegion::~MidiRegion ()
130 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
133 MidiRegion::do_export (string path) const
135 boost::shared_ptr<MidiSource> newsrc;
137 /* caller must check for pre-existing file */
138 assert (!path.empty());
139 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
140 newsrc = boost::dynamic_pointer_cast<MidiSource>(
141 SourceFactory::createWritable(DataType::MIDI, _session,
142 path, false, _session.frame_rate()));
144 BeatsFramesConverter bfc (_session.tempo_map(), _position);
145 Evoral::Beats const bbegin = bfc.from (_start);
146 Evoral::Beats const bend = bfc.from (_start + _length);
149 /* Lock our source since we'll be reading from it. write_to() will
150 take a lock on newsrc. */
151 Source::Lock lm (midi_source(0)->mutex());
152 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
161 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
163 boost::shared_ptr<MidiRegion>
164 MidiRegion::clone (string path) const
166 boost::shared_ptr<MidiSource> newsrc;
168 /* caller must check for pre-existing file */
169 assert (!path.empty());
170 assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
171 newsrc = boost::dynamic_pointer_cast<MidiSource>(
172 SourceFactory::createWritable(DataType::MIDI, _session,
173 path, false, _session.frame_rate()));
174 return clone (newsrc);
177 boost::shared_ptr<MidiRegion>
178 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
180 BeatsFramesConverter bfc (_session.tempo_map(), _position);
181 Evoral::Beats const bbegin = bfc.from (_start);
182 Evoral::Beats const bend = bfc.from (_start + _length);
185 /* Lock our source since we'll be reading from it. write_to() will
186 take a lock on newsrc. */
187 Source::Lock lm (midi_source(0)->mutex());
188 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
189 return boost::shared_ptr<MidiRegion> ();
195 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
196 plist.add (Properties::whole_file, true);
197 plist.add (Properties::start, _start);
198 plist.add (Properties::start_beats, _start_beats);
199 plist.add (Properties::length, _length);
200 plist.add (Properties::length_beats, _length_beats);
201 plist.add (Properties::layer, 0);
203 boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
204 ret->set_beat (beat());
205 ret->set_pulse (pulse());
206 ret->set_start_pulse (start_pulse());
207 ret->set_length_pulse (length_pulse());
213 MidiRegion::post_set (const PropertyChange& pc)
215 Region::post_set (pc);
217 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
218 /* update non-musically */
219 update_length_beats (0);
220 } else 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 _start_beats = Evoral::Beats (beat() - _session.tempo_map().beat_at_frame (_position - _start));
229 _start_pulse = pulse() - _session.tempo_map().pulse_at_frame (_position - _start);
233 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
235 Region::set_length_internal (len, sub_num);
236 update_length_beats (sub_num);
240 MidiRegion::update_after_tempo_map_change (bool /* send */)
242 boost::shared_ptr<Playlist> pl (playlist());
248 const framepos_t old_pos = _position;
249 const framepos_t old_length = _length;
250 const framepos_t old_start = _start;
252 PropertyChange s_and_l;
254 if (position_lock_style() == AudioTime) {
255 recompute_position_from_lock_style (0);
258 set _start to new position in tempo map.
260 The user probably expects the region contents to maintain audio position as the
261 tempo changes, but AFAICT this requires modifying the src file to use
262 SMPTE timestamps with the current disk read model (?).
264 We could arguably use _start to set _start_beats here,
265 resulting in viewport-like behaviour (the contents maintain
266 their musical position while the region is stationary).
268 For now, the musical position at the region start is retained, but subsequent events
269 will maintain their beat distance according to the map.
271 _start = _position - _session.tempo_map().frame_at_beat (beat() - _start_beats.val().to_double());
273 /* _length doesn't change for audio-locked regions. update length_beats to match. */
274 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
275 _length_pulse = _session.tempo_map().pulse_at_frame (_position + _length) - _session.tempo_map().pulse_at_frame (_position);
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 = _session.tempo_map().frame_at_pulse (pulse() + _length_pulse) - _position;
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 = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - (pulse() * 4.0));
306 _length_pulse = (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) / 4.0) - pulse();
310 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
312 Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
314 /* set _start to new position in tempo map */
315 _start = _position - _session.tempo_map().frame_at_beat (beat() - _start_beats.val().to_double());
317 /* in construction from src */
318 if (_length_beats == Evoral::Beats()) {
319 update_length_beats (sub_num);
322 if (position_lock_style() == AudioTime) {
323 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
324 _length_pulse = _session.tempo_map().pulse_at_frame (_position + _length) - _session.tempo_map().pulse_at_frame (_position);
326 /* leave _length_beats alone, and change _length to reflect the state of things
327 at the new position (tempo map may dictate a different number of frames).
329 Region::set_length_internal (_session.tempo_map().frame_at_pulse (pulse() + _length_pulse) - _position, sub_num);
334 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
337 Evoral::Range<framepos_t>* loop_range,
340 MidiStateTracker* tracker,
341 MidiChannelFilter* filter) const
343 return _read_at (_sources, out, position, dur, loop_range, chan_n, mode, tracker, filter);
347 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
350 Evoral::Range<framepos_t>* loop_range,
354 return _read_at (_master_sources, out, position, dur, loop_range, chan_n, mode); /* no tracker */
358 MidiRegion::_read_at (const SourceList& /*srcs*/,
359 Evoral::EventSink<framepos_t>& dst,
362 Evoral::Range<framepos_t>* loop_range,
365 MidiStateTracker* tracker,
366 MidiChannelFilter* filter) const
368 frameoffset_t internal_offset = 0;
369 framecnt_t to_read = 0;
371 /* precondition: caller has verified that we cover the desired section */
376 return 0; /* read nothing */
379 if (position < _position) {
380 /* we are starting the read from before the start of the region */
382 dur -= _position - position;
384 /* we are starting the read from after the start of the region */
385 internal_offset = position - _position;
388 if (internal_offset >= _length) {
389 return 0; /* read nothing */
392 if ((to_read = min (dur, _length - internal_offset)) == 0) {
393 return 0; /* read nothing */
396 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
398 Glib::Threads::Mutex::Lock lm(src->mutex());
400 src->set_note_mode(lm, mode);
403 cerr << "MR " << name () << " read @ " << position << " + " << to_read
404 << " dur was " << dur
405 << " len " << _length
406 << " l-io " << (_length - internal_offset)
407 << " _position = " << _position
408 << " _start = " << _start
409 << " intoffset = " << internal_offset
410 << " pulse = " << pulse()
411 << " start_pulse = " << start_pulse()
412 << " start_beat = " << _start_beats
416 /* This call reads events from a source and writes them to `dst' timed in session frames */
420 dst, // destination buffer
421 _position - _start, // start position of the source in session frames
422 _start + internal_offset, // where to start reading in the source
423 to_read, // read duration in frames
427 _filtered_parameters,
431 return 0; /* "read nothing" */
440 return Region::state ();
444 MidiRegion::set_state (const XMLNode& node, int version)
446 int ret = Region::set_state (node, version);
449 /* set length beats to the frame (non-musical) */
450 if (position_lock_style() == AudioTime) {
451 update_length_beats (0);
454 if (_session.midi_regions_use_bbt_beats()) {
455 info << _("Updating midi region start and length beats") << endmsg;
456 TempoMap& map (_session.tempo_map());
457 _start_beats = Evoral::Beats ((map.pulse_at_beat (_beat) - map.pulse_at_beat (_beat - _start_beats.val().to_double())) * 4.0);
458 _length_beats = Evoral::Beats ((map.pulse_at_beat (_beat + _length_beats.val().to_double()) - map.pulse_at_beat (_beat)) * 4.0);
462 _start_pulse = _start_beats.val().to_double() / 4.0;
463 _length_pulse = _length_beats.val().to_double() / 4.0;
470 MidiRegion::recompute_at_end ()
472 /* our length has changed
473 * so what? stuck notes are dealt with via a note state tracker
478 MidiRegion::recompute_at_start ()
480 /* as above, but the shift was from the front
481 * maybe bump currently active note's note-ons up so they sound here?
482 * that could be undesireable in certain situations though.. maybe
483 * remove the note entirely, including it's note off? something needs to
484 * be done to keep the played MIDI sane to avoid messing up voices of
485 * polyhonic things etc........
490 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
496 boost::shared_ptr<Evoral::Control>
497 MidiRegion::control (const Evoral::Parameter& id, bool create)
499 return model()->control(id, create);
502 boost::shared_ptr<const Evoral::Control>
503 MidiRegion::control (const Evoral::Parameter& id) const
505 return model()->control(id);
508 boost::shared_ptr<MidiModel>
511 return midi_source()->model();
514 boost::shared_ptr<const MidiModel>
515 MidiRegion::model() const
517 return midi_source()->model();
520 boost::shared_ptr<MidiSource>
521 MidiRegion::midi_source (uint32_t n) const
523 // Guaranteed to succeed (use a static cast?)
524 return boost::dynamic_pointer_cast<MidiSource>(source(n));
528 MidiRegion::model_changed ()
534 /* build list of filtered Parameters, being those whose automation state is not `Play' */
536 _filtered_parameters.clear ();
538 Automatable::Controls const & c = model()->controls();
540 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
541 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
543 if (ac->alist()->automation_state() != Play) {
544 _filtered_parameters.insert (ac->parameter ());
548 /* watch for changes to controls' AutoState */
549 midi_source()->AutomationStateChanged.connect_same_thread (
550 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
555 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
557 /* Update our filtered parameters list after a change to a parameter's AutoState */
559 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
560 if (!ac || ac->alist()->automation_state() == Play) {
561 /* It should be "impossible" for ac to be NULL, but if it is, don't
562 filter the parameter so events aren't lost. */
563 _filtered_parameters.erase (p);
565 _filtered_parameters.insert (p);
568 /* the source will have an iterator into the model, and that iterator will have been set up
569 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
572 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
574 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
575 midi_source(0)->invalidate (lm);
579 /** This is called when a trim drag has resulted in a -ve _start time for this region.
580 * Fix it up by adding some empty space to the source.
583 MidiRegion::fix_negative_start ()
585 BeatsFramesConverter c (_session.tempo_map(), _position);
587 model()->insert_silence_at_start (c.from (-_start));
589 _start_beats = Evoral::Beats();
594 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
596 Region::set_start_internal (s, sub_num);
598 if (position_lock_style() == AudioTime) {
599 set_start_beats_from_start_frames ();
604 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
610 PropertyChange what_changed;
612 /* beat has been set exactly by set_position_internal, but the source starts on a frame.
613 working in beats seems the correct thing to do, but reports of a missing first note
614 on playback suggest otherwise. for now, we work in exact beats.
616 const double pos_beat = _session.tempo_map().exact_beat_at_frame (position, sub_num);
617 const double beat_delta = pos_beat - beat();
618 const double pos_pulse = _session.tempo_map().exact_qn_at_frame (position, sub_num) / 4.0;
619 const double pulse_delta = pos_pulse - pulse();
621 /* Set position before length, otherwise for MIDI regions this bad thing happens:
622 * 1. we call set_length_internal; length in beats is computed using the region's current
623 * (soon-to-be old) position
624 * 2. we call set_position_internal; position is set and length in frames re-computed using
625 * length in beats from (1) but at the new position, which is wrong if the region
626 * straddles a tempo/meter change.
629 if (_position != position) {
630 /* sets _beat to new position.*/
631 set_position_internal (position, true, sub_num);
632 what_changed.add (Properties::position);
634 const double new_start_beat = _start_beats.val().to_double() + beat_delta;
635 const double new_start_pulse = _start_pulse + pulse_delta;
636 const framepos_t new_start = _position - _session.tempo_map().frame_at_pulse (pulse() - new_start_pulse);
638 if (!verify_start_and_length (new_start, length)) {
642 _start_beats = Evoral::Beats (new_start_beat);
643 what_changed.add (Properties::start_beats);
645 _start_pulse = new_start_pulse;
647 set_start_internal (new_start, sub_num);
648 what_changed.add (Properties::start);
651 if (_length != length) {
653 if (!verify_start_and_length (_start, length)) {
657 set_length_internal (length, sub_num);
658 what_changed.add (Properties::length);
659 what_changed.add (Properties::length_beats);
662 set_whole_file (false);
664 PropertyChange start_and_length;
666 start_and_length.add (Properties::start);
667 start_and_length.add (Properties::length);
669 if (what_changed.contains (start_and_length)) {
673 if (!what_changed.empty()) {
674 send_change (what_changed);