fix newly introduced deadlock when cloning MIDI regions
[ardour.git] / libs / ardour / midi_region.cc
1 /*
2     Copyright (C) 2000-2006 Paul Davis
3
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.
8
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.
13
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.
17
18     $Id: midiregion.cc 746 2006-08-02 02:44:23Z drobilla $
19 */
20
21 #include <cmath>
22 #include <climits>
23 #include <cfloat>
24
25 #include <set>
26
27 #include <glibmm/threads.h>
28 #include <glibmm/fileutils.h>
29 #include <glibmm/miscutils.h>
30
31 #include "evoral/types.hpp"
32
33 #include "pbd/xml++.h"
34 #include "pbd/basename.h"
35
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"
46
47 #include "i18n.h"
48 #include <locale.h>
49
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53
54 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;
59         }
60 }
61
62 void
63 MidiRegion::make_property_quarks ()
64 {
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));
71 }
72
73 void
74 MidiRegion::register_properties ()
75 {
76         add_property (_start_beats);
77         add_property (_length_beats);
78 }
79
80 /* Basic MidiRegion constructor (many channels) */
81 MidiRegion::MidiRegion (const SourceList& srcs)
82         : Region (srcs)
83         , _start_beats (Properties::start_beats, Evoral::MusicalTime())
84         , _length_beats (Properties::length_beats, midi_source(0)->length_beats())
85 {
86         register_properties ();
87
88         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
89         model_changed ();
90         assert(_name.val().find("/") == string::npos);
91         assert(_type == DataType::MIDI);
92 }
93
94 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
95         : Region (other)
96         , _start_beats (Properties::start_beats, other->_start_beats)
97         , _length_beats (Properties::length_beats, Evoral::MusicalTime())
98 {
99         update_length_beats ();
100         register_properties ();
101
102         assert(_name.val().find("/") == string::npos);
103         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
104         model_changed ();
105 }
106
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())
112 {
113         BeatsFramesConverter bfc (_session.tempo_map(), _position);
114         Evoral::MusicalTime const offset_beats = bfc.from (offset);
115
116         _start_beats  = other->_start_beats.val() + offset_beats;
117         _length_beats = other->_length_beats.val() - offset_beats;
118
119         register_properties ();
120
121         assert(_name.val().find("/") == string::npos);
122         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
123         model_changed ();
124 }
125
126 MidiRegion::~MidiRegion ()
127 {
128 }
129
130 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
131  */
132 boost::shared_ptr<MidiRegion>
133 MidiRegion::clone (string path) const
134 {
135         boost::shared_ptr<MidiSource> newsrc;
136
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);
144 }
145
146 boost::shared_ptr<MidiRegion>
147 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
148 {
149         BeatsFramesConverter bfc (_session.tempo_map(), _position);
150         Evoral::MusicalTime const bbegin = bfc.from (_start);
151         Evoral::MusicalTime const bend = bfc.from (_start + _length);
152
153         {
154                 Source::Lock lm (midi_source(0)->mutex());
155                 /* ::write_to() will take the lock on newsrc.
156
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.
163                  */
164                 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
165                         return boost::shared_ptr<MidiRegion> ();
166                 }
167         }
168
169         PropertyList plist;
170
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);
178
179         return boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true));
180 }
181
182 void
183 MidiRegion::post_set (const PropertyChange& pc)
184 {
185         Region::post_set (pc);
186
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 ();
191         }
192 }
193
194 void
195 MidiRegion::set_start_beats_from_start_frames ()
196 {
197         BeatsFramesConverter c (_session.tempo_map(), _position - _start);
198         _start_beats = c.from (_start);
199 }
200
201 void
202 MidiRegion::set_length_internal (framecnt_t len)
203 {
204         Region::set_length_internal (len);
205         update_length_beats ();
206 }
207
208 void
209 MidiRegion::update_after_tempo_map_change ()
210 {
211         Region::update_after_tempo_map_change ();
212
213         /* _position has now been updated for the new tempo map */
214         _start = _position - _session.tempo_map().framepos_minus_beats (_position, _start_beats);
215
216         send_change (Properties::start);
217 }
218
219 void
220 MidiRegion::update_length_beats ()
221 {
222         BeatsFramesConverter converter (_session.tempo_map(), _position);
223         _length_beats = converter.from (_length);
224 }
225
226 void
227 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute)
228 {
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.
232         */
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
236                 */
237                 BeatsFramesConverter converter (_session.tempo_map(), _position);
238                 Region::set_length_internal (converter.to (_length_beats));
239         }
240 }
241
242 framecnt_t
243 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode, MidiStateTracker* tracker) const
244 {
245         return _read_at (_sources, out, position, dur, chan_n, mode, tracker);
246 }
247
248 framecnt_t
249 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out, framepos_t position, framecnt_t dur, uint32_t chan_n, NoteMode mode) const
250 {
251         return _read_at (_master_sources, out, position, dur, chan_n, mode); /* no tracker */
252 }
253
254 framecnt_t
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
257 {
258         frameoffset_t internal_offset = 0;
259         framecnt_t to_read         = 0;
260
261         /* precondition: caller has verified that we cover the desired section */
262
263         assert(chan_n == 0);
264
265         if (muted()) {
266                 return 0; /* read nothing */
267         }
268
269         if (position < _position) {
270                 /* we are starting the read from before the start of the region */
271                 internal_offset = 0;
272                 dur -= _position - position;
273         } else {
274                 /* we are starting the read from after the start of the region */
275                 internal_offset = position - _position;
276         }
277
278         if (internal_offset >= _length) {
279                 return 0; /* read nothing */
280         }
281
282         if ((to_read = min (dur, _length - internal_offset)) == 0) {
283                 return 0; /* read nothing */
284         }
285
286         boost::shared_ptr<MidiSource> src = midi_source(chan_n);
287
288         Glib::Threads::Mutex::Lock lm(src->mutex());
289
290         src->set_note_mode(lm, mode);
291
292         /*
293           cerr << "MR " << name () << " read @ " << position << " * " << to_read
294           << " _position = " << _position
295           << " _start = " << _start
296           << " intoffset = " << internal_offset
297           << endl;
298         */
299
300         /* This call reads events from a source and writes them to `dst' timed in session frames */
301
302         if (src->midi_read (
303                     lm, // source lock
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
308                         tracker,
309                         _filtered_parameters
310                     ) != to_read) {
311                 return 0; /* "read nothing" */
312         }
313
314         return to_read;
315 }
316
317 XMLNode&
318 MidiRegion::state ()
319 {
320         return Region::state ();
321 }
322
323 int
324 MidiRegion::set_state (const XMLNode& node, int version)
325 {
326         int ret = Region::set_state (node, version);
327
328         if (ret == 0) {
329                 update_length_beats ();
330         }
331
332         return ret;
333 }
334
335 void
336 MidiRegion::recompute_at_end ()
337 {
338         /* our length has changed
339          * so what? stuck notes are dealt with via a note state tracker
340          */
341 }
342
343 void
344 MidiRegion::recompute_at_start ()
345 {
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........
352          */
353 }
354
355 int
356 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
357 {
358         // TODO
359         return -1;
360 }
361
362 boost::shared_ptr<Evoral::Control>
363 MidiRegion::control (const Evoral::Parameter& id, bool create)
364 {
365         return model()->control(id, create);
366 }
367
368 boost::shared_ptr<const Evoral::Control>
369 MidiRegion::control (const Evoral::Parameter& id) const
370 {
371         return model()->control(id);
372 }
373
374 boost::shared_ptr<MidiModel>
375 MidiRegion::model()
376 {
377         return midi_source()->model();
378 }
379
380 boost::shared_ptr<const MidiModel>
381 MidiRegion::model() const
382 {
383         return midi_source()->model();
384 }
385
386 boost::shared_ptr<MidiSource>
387 MidiRegion::midi_source (uint32_t n) const
388 {
389         // Guaranteed to succeed (use a static cast?)
390         return boost::dynamic_pointer_cast<MidiSource>(source(n));
391 }
392
393 void
394 MidiRegion::model_changed ()
395 {
396         if (!model()) {
397                 return;
398         }
399
400         /* build list of filtered Parameters, being those whose automation state is not `Play' */
401
402         _filtered_parameters.clear ();
403
404         Automatable::Controls const & c = model()->controls();
405
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);
408                 assert (ac);
409                 if (ac->alist()->automation_state() != Play) {
410                         _filtered_parameters.insert (ac->parameter ());
411                 }
412         }
413
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)
417                 );
418
419         model()->ContentsChanged.connect_same_thread (
420                 _model_contents_connection, boost::bind (&MidiRegion::model_contents_changed, this));
421 }
422
423 void
424 MidiRegion::model_contents_changed ()
425 {
426         {
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);
431         }
432         send_change (PropertyChange (Properties::midi_data));
433 }
434
435 void
436 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
437 {
438         /* Update our filtered parameters list after a change to a parameter's AutoState */
439
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);
445         } else {
446                 _filtered_parameters.insert (p);
447         }
448
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
451            the iterator.
452         */
453         Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex());
454         midi_source(0)->invalidate (lm);
455 }
456
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.
459  */
460 void
461 MidiRegion::fix_negative_start ()
462 {
463         BeatsFramesConverter c (_session.tempo_map(), _position);
464
465         model()->insert_silence_at_start (c.from (-_start));
466         _start = 0;
467         _start_beats = Evoral::MusicalTime();
468 }
469
470 /** Transpose the notes in this region by a given number of semitones */
471 void
472 MidiRegion::transpose (int semitones)
473 {
474         BeatsFramesConverter c (_session.tempo_map(), _start);
475         model()->transpose (c.from (_start), c.from (_start + _length), semitones);
476 }
477
478 void
479 MidiRegion::set_start_internal (framecnt_t s)
480 {
481         Region::set_start_internal (s);
482         set_start_beats_from_start_frames ();
483 }