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,
339 MidiStateTracker* tracker,
340 MidiChannelFilter* filter) const
342 return _read_at (_sources, out, position, dur, chan_n, mode, tracker, filter);
346 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode) const
348 return _read_at (_master_sources, out, position, dur, chan_n, mode); /* no tracker */
352 MidiRegion::_read_at (const SourceList& /*srcs*/,
353 Evoral::EventSink<framepos_t>& dst,
358 MidiStateTracker* tracker,
359 MidiChannelFilter* filter) const
361 frameoffset_t internal_offset = 0;
362 framecnt_t to_read = 0;
364 /* precondition: caller has verified that we cover the desired section */
369 return 0; /* read nothing */
372 if (position < _position) {
373 /* we are starting the read from before the start of the region */
375 dur -= _position - position;
377 /* we are starting the read from after the start of the region */
378 internal_offset = position - _position;
381 if (internal_offset >= _length) {
382 return 0; /* read nothing */
385 if ((to_read = min (dur, _length - internal_offset)) == 0) {
386 return 0; /* read nothing */
389 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
391 Glib::Threads::Mutex::Lock lm(src->mutex());
393 src->set_note_mode(lm, mode);
396 cerr << "MR " << name () << " read @ " << position << " * " << to_read
397 << " _position = " << _position
398 << " _start = " << _start
399 << " intoffset = " << internal_offset
400 << " pulse = " << pulse()
401 << " start_pulse = " << start_pulse()
402 << " start_beat = " << _start_beats
406 /* This call reads events from a source and writes them to `dst' timed in session frames */
410 dst, // destination buffer
411 _position - _start, // start position of the source in session frames
412 _start + internal_offset, // where to start reading in the source
413 to_read, // read duration in frames
416 _filtered_parameters,
420 return 0; /* "read nothing" */
429 return Region::state ();
433 MidiRegion::set_state (const XMLNode& node, int version)
435 int ret = Region::set_state (node, version);
438 /* set length beats to the frame (non-musical) */
439 if (position_lock_style() == AudioTime) {
440 update_length_beats (0);
443 if (_session.midi_regions_use_bbt_beats()) {
444 info << _("Updating midi region start and length beats") << endmsg;
445 TempoMap& map (_session.tempo_map());
446 _start_beats = Evoral::Beats ((map.pulse_at_beat (_beat) - map.pulse_at_beat (_beat - _start_beats.val().to_double())) * 4.0);
447 _length_beats = Evoral::Beats ((map.pulse_at_beat (_beat + _length_beats.val().to_double()) - map.pulse_at_beat (_beat)) * 4.0);
451 _start_pulse = _start_beats.val().to_double() / 4.0;
452 _length_pulse = _length_beats.val().to_double() / 4.0;
459 MidiRegion::recompute_at_end ()
461 /* our length has changed
462 * so what? stuck notes are dealt with via a note state tracker
467 MidiRegion::recompute_at_start ()
469 /* as above, but the shift was from the front
470 * maybe bump currently active note's note-ons up so they sound here?
471 * that could be undesireable in certain situations though.. maybe
472 * remove the note entirely, including it's note off? something needs to
473 * be done to keep the played MIDI sane to avoid messing up voices of
474 * polyhonic things etc........
479 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
485 boost::shared_ptr<Evoral::Control>
486 MidiRegion::control (const Evoral::Parameter& id, bool create)
488 return model()->control(id, create);
491 boost::shared_ptr<const Evoral::Control>
492 MidiRegion::control (const Evoral::Parameter& id) const
494 return model()->control(id);
497 boost::shared_ptr<MidiModel>
500 return midi_source()->model();
503 boost::shared_ptr<const MidiModel>
504 MidiRegion::model() const
506 return midi_source()->model();
509 boost::shared_ptr<MidiSource>
510 MidiRegion::midi_source (uint32_t n) const
512 // Guaranteed to succeed (use a static cast?)
513 return boost::dynamic_pointer_cast<MidiSource>(source(n));
517 MidiRegion::model_changed ()
523 /* build list of filtered Parameters, being those whose automation state is not `Play' */
525 _filtered_parameters.clear ();
527 Automatable::Controls const & c = model()->controls();
529 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
530 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
532 if (ac->alist()->automation_state() != Play) {
533 _filtered_parameters.insert (ac->parameter ());
537 /* watch for changes to controls' AutoState */
538 midi_source()->AutomationStateChanged.connect_same_thread (
539 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
544 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
546 /* Update our filtered parameters list after a change to a parameter's AutoState */
548 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
549 if (!ac || ac->alist()->automation_state() == Play) {
550 /* It should be "impossible" for ac to be NULL, but if it is, don't
551 filter the parameter so events aren't lost. */
552 _filtered_parameters.erase (p);
554 _filtered_parameters.insert (p);
557 /* the source will have an iterator into the model, and that iterator will have been set up
558 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
561 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
563 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
564 midi_source(0)->invalidate (lm);
568 /** This is called when a trim drag has resulted in a -ve _start time for this region.
569 * Fix it up by adding some empty space to the source.
572 MidiRegion::fix_negative_start ()
574 BeatsFramesConverter c (_session.tempo_map(), _position);
576 model()->insert_silence_at_start (c.from (-_start));
578 _start_beats = Evoral::Beats();
583 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
585 Region::set_start_internal (s, sub_num);
587 if (position_lock_style() == AudioTime) {
588 set_start_beats_from_start_frames ();
593 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
599 PropertyChange what_changed;
601 /* beat has been set exactly by set_position_internal, but the source starts on a frame.
602 working in beats seems the correct thing to do, but reports of a missing first note
603 on playback suggest otherwise. for now, we work in exact beats.
605 const double pos_beat = _session.tempo_map().exact_beat_at_frame (position, sub_num);
606 const double beat_delta = pos_beat - beat();
607 const double pos_pulse = _session.tempo_map().exact_qn_at_frame (position, sub_num) / 4.0;
608 const double pulse_delta = pos_pulse - pulse();
610 /* Set position before length, otherwise for MIDI regions this bad thing happens:
611 * 1. we call set_length_internal; length in beats is computed using the region's current
612 * (soon-to-be old) position
613 * 2. we call set_position_internal; position is set and length in frames re-computed using
614 * length in beats from (1) but at the new position, which is wrong if the region
615 * straddles a tempo/meter change.
618 if (_position != position) {
619 /* sets _beat to new position.*/
620 set_position_internal (position, true, sub_num);
621 what_changed.add (Properties::position);
623 const double new_start_beat = _start_beats.val().to_double() + beat_delta;
624 const double new_start_pulse = _start_pulse + pulse_delta;
625 const framepos_t new_start = _position - _session.tempo_map().frame_at_pulse (pulse() - new_start_pulse);
627 if (!verify_start_and_length (new_start, length)) {
631 _start_beats = Evoral::Beats (new_start_beat);
632 what_changed.add (Properties::start_beats);
634 _start_pulse = new_start_pulse;
636 set_start_internal (new_start, sub_num);
637 what_changed.add (Properties::start);
640 if (_length != length) {
642 if (!verify_start_and_length (_start, length)) {
646 set_length_internal (length, sub_num);
647 what_changed.add (Properties::length);
648 what_changed.add (Properties::length_beats);
651 set_whole_file (false);
653 PropertyChange start_and_length;
655 start_and_length.add (Properties::start);
656 start_and_length.add (Properties::length);
658 if (what_changed.contains (start_and_length)) {
662 if (!what_changed.empty()) {
663 send_change (what_changed);