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/thread.h>
29 #include "pbd/basename.h"
30 #include "pbd/xml++.h"
31 #include "pbd/enumwriter.h"
33 #include "ardour/midi_region.h"
34 #include "ardour/session.h"
35 #include "ardour/gain.h"
36 #include "ardour/dB.h"
37 #include "ardour/playlist.h"
38 #include "ardour/midi_source.h"
39 #include "ardour/region_factory.h"
40 #include "ardour/types.h"
41 #include "ardour/midi_ring_buffer.h"
47 using namespace ARDOUR
;
51 namespace Properties
{
52 PBD::PropertyDescriptor
<void*> midi_data
;
53 PBD::PropertyDescriptor
<Evoral::MusicalTime
> length_beats
;
58 MidiRegion::make_property_quarks ()
60 Properties::midi_data
.property_id
= g_quark_from_static_string (X_("midi-data"));
61 DEBUG_TRACE (DEBUG::Properties
, string_compose ("quark for midi-data = %1\n", Properties::midi_data
.property_id
));
62 Properties::length_beats
.property_id
= g_quark_from_static_string (X_("length-beats"));
63 DEBUG_TRACE (DEBUG::Properties
, string_compose ("quark for length-beats = %1\n", Properties::length_beats
.property_id
));
67 MidiRegion::register_properties ()
69 add_property (_length_beats
);
72 /* Basic MidiRegion constructor (many channels) */
73 MidiRegion::MidiRegion (const SourceList
& srcs
)
75 , _length_beats (Properties::length_beats
, midi_source(0)->length_beats())
77 register_properties ();
79 midi_source(0)->ModelChanged
.connect_same_thread (_source_connection
, boost::bind (&MidiRegion::model_changed
, this));
81 assert(_name
.val().find("/") == string::npos
);
82 assert(_type
== DataType::MIDI
);
85 MidiRegion::MidiRegion (boost::shared_ptr
<const MidiRegion
> other
)
87 , _length_beats (Properties::length_beats
, (Evoral::MusicalTime
) 0)
89 update_length_beats ();
90 register_properties ();
92 assert(_name
.val().find("/") == string::npos
);
93 midi_source(0)->ModelChanged
.connect_same_thread (_source_connection
, boost::bind (&MidiRegion::model_changed
, this));
97 /** Create a new MidiRegion, that is part of an existing one */
98 MidiRegion::MidiRegion (boost::shared_ptr
<const MidiRegion
> other
, frameoffset_t offset
)
99 : Region (other
, offset
)
100 , _length_beats (Properties::length_beats
, (Evoral::MusicalTime
) 0)
102 BeatsFramesConverter
bfc (_session
.tempo_map(), _position
);
103 Evoral::MusicalTime
const offset_beats
= bfc
.from (offset
);
105 _length_beats
= other
->_length_beats
- offset_beats
;
107 register_properties ();
109 assert(_name
.val().find("/") == string::npos
);
110 midi_source(0)->ModelChanged
.connect_same_thread (_source_connection
, boost::bind (&MidiRegion::model_changed
, this));
114 MidiRegion::~MidiRegion ()
118 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
120 boost::shared_ptr
<MidiRegion
>
123 BeatsFramesConverter
bfc (_session
.tempo_map(), _position
);
124 Evoral::MusicalTime
const bbegin
= bfc
.from (_start
);
125 Evoral::MusicalTime
const bend
= bfc
.from (_start
+ _length
);
127 boost::shared_ptr
<MidiSource
> ms
= midi_source(0)->clone (bbegin
, bend
);
131 plist
.add (Properties::name
, ms
->name());
132 plist
.add (Properties::whole_file
, true);
133 plist
.add (Properties::start
, _start
);
134 plist
.add (Properties::length
, _length
);
135 plist
.add (Properties::length_beats
, _length_beats
);
136 plist
.add (Properties::layer
, 0);
138 return boost::dynamic_pointer_cast
<MidiRegion
> (RegionFactory::create (ms
, plist
, true));
142 MidiRegion::post_set (const PropertyChange
& pc
)
144 if (pc
.contains (Properties::length
) && !pc
.contains (Properties::length_beats
)) {
145 update_length_beats ();
150 MidiRegion::set_length_internal (framecnt_t len
)
152 Region::set_length_internal (len
);
153 update_length_beats ();
157 MidiRegion::update_length_beats ()
159 BeatsFramesConverter
converter (_session
.tempo_map(), _position
);
160 _length_beats
= converter
.from (_length
);
164 MidiRegion::set_position_internal (framepos_t pos
, bool allow_bbt_recompute
)
166 Region::set_position_internal (pos
, allow_bbt_recompute
);
167 /* zero length regions don't exist - so if _length_beats is zero, this object
168 is under construction.
171 /* leave _length_beats alone, and change _length to reflect the state of things
172 at the new position (tempo map may dictate a different number of frames
174 BeatsFramesConverter
converter (_session
.tempo_map(), _position
);
175 Region::set_length_internal (converter
.to (_length_beats
));
180 MidiRegion::read_at (Evoral::EventSink
<framepos_t
>& out
, framepos_t position
, framecnt_t dur
, uint32_t chan_n
, NoteMode mode
, MidiStateTracker
* tracker
) const
182 return _read_at (_sources
, out
, position
, dur
, chan_n
, mode
, tracker
);
186 MidiRegion::master_read_at (MidiRingBuffer
<framepos_t
>& out
, framepos_t position
, framecnt_t dur
, uint32_t chan_n
, NoteMode mode
) const
188 return _read_at (_master_sources
, out
, position
, dur
, chan_n
, mode
); /* no tracker */
192 MidiRegion::_read_at (const SourceList
& /*srcs*/, Evoral::EventSink
<framepos_t
>& dst
, framepos_t position
, framecnt_t dur
, uint32_t chan_n
,
193 NoteMode mode
, MidiStateTracker
* tracker
) const
195 frameoffset_t internal_offset
= 0;
196 framecnt_t to_read
= 0;
198 /* precondition: caller has verified that we cover the desired section */
203 return 0; /* read nothing */
206 if (position
< _position
) {
207 /* we are starting the read from before the start of the region */
209 dur
-= _position
- position
;
211 /* we are starting the read from after the start of the region */
212 internal_offset
= position
- _position
;
215 if (internal_offset
>= _length
) {
216 return 0; /* read nothing */
219 if ((to_read
= min (dur
, _length
- internal_offset
)) == 0) {
220 return 0; /* read nothing */
223 _read_data_count
= 0;
225 boost::shared_ptr
<MidiSource
> src
= midi_source(chan_n
);
226 src
->set_note_mode(mode
);
229 cerr << "MR read @ " << position << " * " << to_read
230 << " _position = " << _position
231 << " _start = " << _start
232 << " intoffset = " << internal_offset
236 /* This call reads events from a source and writes them to `dst' timed in session frames */
239 dst
, // destination buffer
240 _position
- _start
, // start position of the source in session frames
241 _start
+ internal_offset
, // where to start reading in the source
242 to_read
, // read duration in frames
246 return 0; /* "read nothing" */
249 _read_data_count
+= src
->read_data_count();
257 return Region::state ();
261 MidiRegion::set_state (const XMLNode
& node
, int version
)
263 int ret
= Region::set_state (node
, version
);
266 update_length_beats ();
273 MidiRegion::recompute_at_end ()
275 /* our length has changed
276 * so what? stuck notes are dealt with via a note state tracker
281 MidiRegion::recompute_at_start ()
283 /* as above, but the shift was from the front
284 * maybe bump currently active note's note-ons up so they sound here?
285 * that could be undesireable in certain situations though.. maybe
286 * remove the note entirely, including it's note off? something needs to
287 * be done to keep the played MIDI sane to avoid messing up voices of
288 * polyhonic things etc........
293 MidiRegion::separate_by_channel (ARDOUR::Session
&, vector
< boost::shared_ptr
<Region
> >&) const
300 MidiRegion::exportme (ARDOUR::Session
&, ARDOUR::ExportSpecification
&)
305 boost::shared_ptr
<MidiSource
>
306 MidiRegion::midi_source (uint32_t n
) const
308 // Guaranteed to succeed (use a static cast?)
309 return boost::dynamic_pointer_cast
<MidiSource
>(source(n
));
313 MidiRegion::model_changed ()
319 /* build list of filtered Parameters, being those whose automation state is not `Play' */
321 _filtered_parameters
.clear ();
323 Automatable::Controls
const & c
= model()->controls();
325 for (Automatable::Controls::const_iterator i
= c
.begin(); i
!= c
.end(); ++i
) {
326 boost::shared_ptr
<AutomationControl
> ac
= boost::dynamic_pointer_cast
<AutomationControl
> (i
->second
);
328 if (ac
->alist()->automation_state() != Play
) {
329 _filtered_parameters
.insert (ac
->parameter ());
333 /* watch for changes to controls' AutoState */
334 midi_source()->AutomationStateChanged
.connect_same_thread (
335 _model_connection
, boost::bind (&MidiRegion::model_automation_state_changed
, this, _1
)
338 model()->ContentsChanged
.connect_same_thread (
339 _model_contents_connection
, boost::bind (&MidiRegion::model_contents_changed
, this));
343 MidiRegion::model_contents_changed ()
345 send_change (PropertyChange (Properties::midi_data
));
349 MidiRegion::model_automation_state_changed (Evoral::Parameter
const & p
)
351 /* Update our filtered parameters list after a change to a parameter's AutoState */
353 boost::shared_ptr
<AutomationControl
> ac
= model()->automation_control (p
);
356 if (ac
->alist()->automation_state() == Play
) {
357 _filtered_parameters
.erase (p
);
359 _filtered_parameters
.insert (p
);
362 /* the source will have an iterator into the model, and that iterator will have been set up
363 for a given set of filtered_parameters, so now that we've changed that list we must invalidate
366 Glib::Mutex::Lock
lm (midi_source(0)->mutex());
367 midi_source(0)->invalidate ();
370 /** This is called when a trim drag has resulted in a -ve _start time for this region.
371 * Fix it up by adding some empty space to the source.
374 MidiRegion::fix_negative_start ()
376 BeatsFramesConverter
c (_session
.tempo_map(), _position
);
378 model()->insert_silence_at_start (c
.from (-_start
));
382 /** Transpose the notes in this region by a given number of semitones */
384 MidiRegion::transpose (int semitones
)
386 BeatsFramesConverter
c (_session
.tempo_map(), _start
);
387 model()->transpose (c
.from (_start
), c
.from (_start
+ _length
), semitones
);