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/types.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<void*> midi_data;
57 PBD::PropertyDescriptor<Evoral::MusicalTime> start_beats;
58 PBD::PropertyDescriptor<Evoral::MusicalTime> length_beats;
63 MidiRegion::make_property_quarks ()
65 Properties::midi_data.property_id = g_quark_from_static_string (X_("midi-data"));
66 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for midi-data = %1\n", Properties::midi_data.property_id));
67 Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
68 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
69 Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
70 DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
74 MidiRegion::register_properties ()
76 add_property (_start_beats);
77 add_property (_length_beats);
80 /* Basic MidiRegion constructor (many channels) */
81 MidiRegion::MidiRegion (const SourceList& srcs)
83 , _start_beats (Properties::start_beats, Evoral::MusicalTime())
84 , _length_beats (Properties::length_beats, midi_source(0)->length_beats())
86 register_properties ();
88 midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
90 assert(_name.val().find("/") == string::npos);
91 assert(_type == DataType::MIDI);
94 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
96 , _start_beats (Properties::start_beats, other->_start_beats)
97 , _length_beats (Properties::length_beats, Evoral::MusicalTime())
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)
109 : Region (other, offset)
110 , _start_beats (Properties::start_beats, Evoral::MusicalTime())
111 , _length_beats (Properties::length_beats, Evoral::MusicalTime())
113 BeatsFramesConverter bfc (_session.tempo_map(), _position);
114 Evoral::MusicalTime const offset_beats = bfc.from (offset);
116 _start_beats = other->_start_beats.val() + offset_beats;
117 _length_beats = other->_length_beats.val() - offset_beats;
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 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
132 boost::shared_ptr<MidiRegion>
133 MidiRegion::clone (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()));
143 return clone (newsrc);
146 boost::shared_ptr<MidiRegion>
147 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
149 BeatsFramesConverter bfc (_session.tempo_map(), _position);
150 Evoral::MusicalTime const bbegin = bfc.from (_start);
151 Evoral::MusicalTime const bend = bfc.from (_start + _length);
154 Source::Lock lm (midi_source(0)->mutex());
155 /* ::write_to() will take the lock on newsrc.
157 XXX taking the lock on our own source here seems
158 partly sane and partly odd. We don't write the
159 data to the newsrc from our source, but from
160 the in memory copy. This whole thing (memory vs. disk, SMF
161 versus MidiModel) is so f'ed up that its no wonder
162 stuff is wierd sometimes.
164 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
165 return boost::shared_ptr<MidiRegion> ();
171 plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
172 plist.add (Properties::whole_file, true);
173 plist.add (Properties::start, _start);
174 plist.add (Properties::start_beats, _start_beats);
175 plist.add (Properties::length, _length);
176 plist.add (Properties::length_beats, _length_beats);
177 plist.add (Properties::layer, 0);
179 return boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true));
183 MidiRegion::post_set (const PropertyChange& pc)
185 Region::post_set (pc);
187 if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
188 update_length_beats ();
189 } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
190 set_start_beats_from_start_frames ();
195 MidiRegion::set_start_beats_from_start_frames ()
197 BeatsFramesConverter c (_session.tempo_map(), _position - _start);
198 _start_beats = c.from (_start);
202 MidiRegion::set_length_internal (framecnt_t len)
204 Region::set_length_internal (len);
205 update_length_beats ();
209 MidiRegion::update_after_tempo_map_change ()
211 Region::update_after_tempo_map_change ();
213 /* _position has now been updated for the new tempo map */
214 _start = _position - _session.tempo_map().framepos_minus_beats (_position, _start_beats);
216 send_change (Properties::start);
220 MidiRegion::update_length_beats ()
222 BeatsFramesConverter converter (_session.tempo_map(), _position);
223 _length_beats = converter.from (_length);
227 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute)
229 Region::set_position_internal (pos, allow_bbt_recompute);
230 /* zero length regions don't exist - so if _length_beats is zero, this object
231 is under construction.
233 if (_length_beats.val() == Evoral::MusicalTime()) {
234 /* leave _length_beats alone, and change _length to reflect the state of things
235 at the new position (tempo map may dictate a different number of frames
237 BeatsFramesConverter converter (_session.tempo_map(), _position);
238 Region::set_length_internal (converter.to (_length_beats));
243 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode, MidiStateTracker* tracker) const
245 return _read_at (_sources, out, position, dur, chan_n, mode, tracker);
249 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode) const
251 return _read_at (_master_sources, out, position, dur, chan_n, mode); /* no tracker */
255 MidiRegion::_read_at (const SourceList& /*srcs*/, Evoral::EventSink<framepos_t>& dst, framepos_t position, framecnt_t dur, uint32_t chan_n,
256 NoteMode mode, MidiStateTracker* tracker) const
258 frameoffset_t internal_offset = 0;
259 framecnt_t to_read = 0;
261 /* precondition: caller has verified that we cover the desired section */
266 return 0; /* read nothing */
269 if (position < _position) {
270 /* we are starting the read from before the start of the region */
272 dur -= _position - position;
274 /* we are starting the read from after the start of the region */
275 internal_offset = position - _position;
278 if (internal_offset >= _length) {
279 return 0; /* read nothing */
282 if ((to_read = min (dur, _length - internal_offset)) == 0) {
283 return 0; /* read nothing */
286 boost::shared_ptr<MidiSource> src = midi_source(chan_n);
288 Glib::Threads::Mutex::Lock lm(src->mutex());
290 src->set_note_mode(lm, mode);
293 cerr << "MR " << name () << " read @ " << position << " * " << to_read
294 << " _position = " << _position
295 << " _start = " << _start
296 << " intoffset = " << internal_offset
300 /* This call reads events from a source and writes them to `dst' timed in session frames */
304 dst, // destination buffer
305 _position - _start, // start position of the source in session frames
306 _start + internal_offset, // where to start reading in the source
307 to_read, // read duration in frames
311 return 0; /* "read nothing" */
320 return Region::state ();
324 MidiRegion::set_state (const XMLNode& node, int version)
326 int ret = Region::set_state (node, version);
329 update_length_beats ();
336 MidiRegion::recompute_at_end ()
338 /* our length has changed
339 * so what? stuck notes are dealt with via a note state tracker
344 MidiRegion::recompute_at_start ()
346 /* as above, but the shift was from the front
347 * maybe bump currently active note's note-ons up so they sound here?
348 * that could be undesireable in certain situations though.. maybe
349 * remove the note entirely, including it's note off? something needs to
350 * be done to keep the played MIDI sane to avoid messing up voices of
351 * polyhonic things etc........
356 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
362 boost::shared_ptr<Evoral::Control>
363 MidiRegion::control (const Evoral::Parameter& id, bool create)
365 return model()->control(id, create);
368 boost::shared_ptr<const Evoral::Control>
369 MidiRegion::control (const Evoral::Parameter& id) const
371 return model()->control(id);
374 boost::shared_ptr<MidiModel>
377 return midi_source()->model();
380 boost::shared_ptr<const MidiModel>
381 MidiRegion::model() const
383 return midi_source()->model();
386 boost::shared_ptr<MidiSource>
387 MidiRegion::midi_source (uint32_t n) const
389 // Guaranteed to succeed (use a static cast?)
390 return boost::dynamic_pointer_cast<MidiSource>(source(n));
394 MidiRegion::model_changed ()
400 /* build list of filtered Parameters, being those whose automation state is not `Play' */
402 _filtered_parameters.clear ();
404 Automatable::Controls const & c = model()->controls();
406 for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
407 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
409 if (ac->alist()->automation_state() != Play) {
410 _filtered_parameters.insert (ac->parameter ());
414 /* watch for changes to controls' AutoState */
415 midi_source()->AutomationStateChanged.connect_same_thread (
416 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
419 model()->ContentsChanged.connect_same_thread (
420 _model_contents_connection, boost::bind (&MidiRegion::model_contents_changed, this));
424 MidiRegion::model_contents_changed ()
427 /* Invalidate source iterator to force reading new contents even if the
428 calls to read progress linearly. */
429 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex());
430 midi_source(0)->invalidate (lm);
432 send_change (PropertyChange (Properties::midi_data));
436 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
438 /* Update our filtered parameters list after a change to a parameter's AutoState */
440 boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
441 if (!ac || ac->alist()->automation_state() == Play) {
442 /* It should be "impossible" for ac to be NULL, but if it is, don't
443 filter the parameter so events aren't lost. */
444 _filtered_parameters.erase (p);
446 _filtered_parameters.insert (p);
449 /* the source will have an iterator into the model, and that iterator will have been set up
450 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
453 Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex());
454 midi_source(0)->invalidate (lm);
457 /** This is called when a trim drag has resulted in a -ve _start time for this region.
458 * Fix it up by adding some empty space to the source.
461 MidiRegion::fix_negative_start ()
463 BeatsFramesConverter c (_session.tempo_map(), _position);
465 model()->insert_silence_at_start (c.from (-_start));
467 _start_beats = Evoral::MusicalTime();
470 /** Transpose the notes in this region by a given number of semitones */
472 MidiRegion::transpose (int semitones)
474 BeatsFramesConverter c (_session.tempo_map(), _start);
475 model()->transpose (c.from (_start), c.from (_start + _length), semitones);
479 MidiRegion::set_start_internal (framecnt_t s)
481 Region::set_start_internal (s);
482 set_start_beats_from_start_frames ();