ff5ec02433c617d3f6c1a56e8143c4b731703b5d
[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/Beats.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 "pbd/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<Evoral::Beats> start_beats;
57                 PBD::PropertyDescriptor<Evoral::Beats> length_beats;
58         }
59 }
60
61 void
62 MidiRegion::make_property_quarks ()
63 {
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));
68 }
69
70 void
71 MidiRegion::register_properties ()
72 {
73         add_property (_start_beats);
74         add_property (_length_beats);
75 }
76
77 /* Basic MidiRegion constructor (many channels) */
78 MidiRegion::MidiRegion (const SourceList& srcs)
79         : Region (srcs)
80         , _start_beats (Properties::start_beats, Evoral::Beats())
81         , _length_beats (Properties::length_beats, midi_source(0)->length_beats())
82 {
83         register_properties ();
84         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
85         model_changed ();
86         assert(_name.val().find("/") == string::npos);
87         assert(_type == DataType::MIDI);
88 }
89
90 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
91         : Region (other)
92         , _start_beats (Properties::start_beats, other->_start_beats)
93         , _length_beats (Properties::length_beats, other->_length_beats)
94 {
95         //update_length_beats ();
96         register_properties ();
97
98         assert(_name.val().find("/") == string::npos);
99         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
100         model_changed ();
101 }
102
103 /** Create a new MidiRegion that is part of an existing one */
104 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
105         : Region (other, offset, sub_num)
106         , _start_beats (Properties::start_beats, Evoral::Beats())
107         , _length_beats (Properties::length_beats, other->_length_beats)
108 {
109         _start_beats = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) - (other->pulse() * 4.0)) + other->_start_beats;
110
111         update_length_beats (sub_num);
112         register_properties ();
113
114         assert(_name.val().find("/") == string::npos);
115         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
116         model_changed ();
117 }
118
119 MidiRegion::~MidiRegion ()
120 {
121 }
122
123 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
124  */
125 bool
126 MidiRegion::do_export (string path) const
127 {
128         boost::shared_ptr<MidiSource> newsrc;
129
130         /* caller must check for pre-existing file */
131         assert (!path.empty());
132         assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
133         newsrc = boost::dynamic_pointer_cast<MidiSource>(
134                 SourceFactory::createWritable(DataType::MIDI, _session,
135                                               path, false, _session.frame_rate()));
136
137         BeatsFramesConverter bfc (_session.tempo_map(), _position);
138         Evoral::Beats const bbegin = bfc.from (_start);
139         Evoral::Beats const bend = bfc.from (_start + _length);
140
141         {
142                 /* Lock our source since we'll be reading from it.  write_to() will
143                    take a lock on newsrc. */
144                 Source::Lock lm (midi_source(0)->mutex());
145                 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
146                         return false;
147                 }
148         }
149
150         return true;
151 }
152
153
154 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
155  */
156 boost::shared_ptr<MidiRegion>
157 MidiRegion::clone (string path) const
158 {
159         boost::shared_ptr<MidiSource> newsrc;
160
161         /* caller must check for pre-existing file */
162         assert (!path.empty());
163         assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
164         newsrc = boost::dynamic_pointer_cast<MidiSource>(
165                 SourceFactory::createWritable(DataType::MIDI, _session,
166                                               path, false, _session.frame_rate()));
167         return clone (newsrc);
168 }
169
170 boost::shared_ptr<MidiRegion>
171 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
172 {
173         BeatsFramesConverter bfc (_session.tempo_map(), _position);
174         Evoral::Beats const bbegin = bfc.from (_start);
175         Evoral::Beats const bend = bfc.from (_start + _length);
176
177         {
178                 /* Lock our source since we'll be reading from it.  write_to() will
179                    take a lock on newsrc. */
180                 Source::Lock lm (midi_source(0)->mutex());
181                 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
182                         return boost::shared_ptr<MidiRegion> ();
183                 }
184         }
185
186         PropertyList plist;
187
188         plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
189         plist.add (Properties::whole_file, true);
190         plist.add (Properties::start, _start);
191         plist.add (Properties::start_beats, _start_beats);
192         plist.add (Properties::length, _length);
193         plist.add (Properties::length_beats, _length_beats);
194         plist.add (Properties::layer, 0);
195
196         boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
197         ret->set_beat (beat());
198         ret->set_pulse (pulse());
199
200         return ret;
201 }
202
203 void
204 MidiRegion::post_set (const PropertyChange& pc)
205 {
206         Region::post_set (pc);
207
208         if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
209                 /* update non-musically */
210                 update_length_beats (0);
211         } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
212                 set_start_beats_from_start_frames ();
213         }
214 }
215
216 void
217 MidiRegion::set_start_beats_from_start_frames ()
218 {
219         _start_beats = Evoral::Beats ((pulse() * 4.0) - _session.tempo_map().quarter_note_at_frame (_position - _start));
220 }
221
222 void
223 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
224 {
225         Region::set_length_internal (len, sub_num);
226         update_length_beats (sub_num);
227 }
228
229 void
230 MidiRegion::update_after_tempo_map_change (bool /* send */)
231 {
232         boost::shared_ptr<Playlist> pl (playlist());
233
234         if (!pl) {
235                 return;
236         }
237
238         const framepos_t old_pos = _position;
239         const framepos_t old_length = _length;
240         const framepos_t old_start = _start;
241
242         PropertyChange s_and_l;
243
244         if (position_lock_style() == AudioTime) {
245                 recompute_position_from_lock_style (0);
246
247                 /*
248                   set _start to new position in tempo map.
249
250                   The user probably expects the region contents to maintain audio position as the
251                   tempo changes, but AFAICT this requires modifying the src file to use
252                   SMPTE timestamps with the current disk read model (?).
253
254                   We could arguably use _start to set _start_beats here,
255                   resulting in viewport-like behaviour (the contents maintain
256                   their musical position while the region is stationary).
257
258                   For now, the musical position at the region start is retained, but subsequent events
259                   will maintain their beat distance according to the map.
260                 */
261                 _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
262
263                 /* _length doesn't change for audio-locked regions. update length_beats to match. */
264                 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
265
266                 s_and_l.add (Properties::start);
267                 s_and_l.add (Properties::length_beats);
268
269                 send_change  (s_and_l);
270                 return;
271         }
272
273         Region::update_after_tempo_map_change (false);
274
275         /* _start has now been updated. */
276         _length = _session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position;
277
278         if (old_start != _start) {
279                 s_and_l.add (Properties::start);
280         }
281         if (old_length != _length) {
282                 s_and_l.add (Properties::length);
283         }
284         if (old_pos != _position) {
285                 s_and_l.add (Properties::position);
286         }
287
288         send_change (s_and_l);
289 }
290
291 void
292 MidiRegion::update_length_beats (const int32_t sub_num)
293 {
294         _length_beats = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - (pulse() * 4.0));
295 }
296
297 void
298 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
299 {
300         Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
301
302         /* set _start to new position in tempo map */
303         _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
304
305         /* in construction from src */
306         if (_length_beats == Evoral::Beats()) {
307                 update_length_beats (sub_num);
308         }
309
310         if (position_lock_style() == AudioTime) {
311                 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
312         } else {
313                 /* leave _length_beats alone, and change _length to reflect the state of things
314                    at the new position (tempo map may dictate a different number of frames).
315                 */
316                 Region::set_length_internal (_session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position, sub_num);
317         }
318 }
319
320 framecnt_t
321 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
322                      framepos_t                     position,
323                      framecnt_t                     dur,
324                      Evoral::Range<framepos_t>*     loop_range,
325                      uint32_t                       chan_n,
326                      NoteMode                       mode,
327                      MidiStateTracker*              tracker,
328                      MidiChannelFilter*             filter) const
329 {
330         return _read_at (_sources, out, position, dur, loop_range, chan_n, mode, tracker, filter);
331 }
332
333 framecnt_t
334 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
335                             framepos_t                  position,
336                             framecnt_t                  dur,
337                             Evoral::Range<framepos_t>*  loop_range,
338                             uint32_t                    chan_n,
339                             NoteMode                    mode) const
340 {
341         return _read_at (_master_sources, out, position, dur, loop_range, chan_n, mode); /* no tracker */
342 }
343
344 framecnt_t
345 MidiRegion::_read_at (const SourceList&              /*srcs*/,
346                       Evoral::EventSink<framepos_t>& dst,
347                       framepos_t                     position,
348                       framecnt_t                     dur,
349                       Evoral::Range<framepos_t>*     loop_range,
350                       uint32_t                       chan_n,
351                       NoteMode                       mode,
352                       MidiStateTracker*              tracker,
353                       MidiChannelFilter*             filter) const
354 {
355         frameoffset_t internal_offset = 0;
356         framecnt_t    to_read         = 0;
357
358         /* precondition: caller has verified that we cover the desired section */
359
360         assert(chan_n == 0);
361
362         if (muted()) {
363                 return 0; /* read nothing */
364         }
365
366         if (position < _position) {
367                 /* we are starting the read from before the start of the region */
368                 internal_offset = 0;
369                 dur -= _position - position;
370         } else {
371                 /* we are starting the read from after the start of the region */
372                 internal_offset = position - _position;
373         }
374
375         if (internal_offset >= _length) {
376                 return 0; /* read nothing */
377         }
378
379         if ((to_read = min (dur, _length - internal_offset)) == 0) {
380                 return 0; /* read nothing */
381         }
382
383         boost::shared_ptr<MidiSource> src = midi_source(chan_n);
384
385         Glib::Threads::Mutex::Lock lm(src->mutex());
386
387         src->set_note_mode(lm, mode);
388
389 #if 0
390         cerr << "MR " << name () << " read @ " << position << " + " << to_read
391              << " dur was " << dur
392              << " len " << _length
393              << " l-io " << (_length - internal_offset)
394              << " _position = " << _position
395              << " _start = " << _start
396              << " intoffset = " << internal_offset
397              << " pulse = " << pulse()
398              << " start_pulse = " << start_pulse()
399              << " start_beat = " << _start_beats
400              << endl;
401 #endif
402
403         /* This call reads events from a source and writes them to `dst' timed in session frames */
404
405         if (src->midi_read (
406                     lm, // source lock
407                     dst, // destination buffer
408                     _position - _start, // start position of the source in session frames
409                     _start + internal_offset, // where to start reading in the source
410                     to_read, // read duration in frames
411                     loop_range,
412                     tracker,
413                     filter,
414                     _filtered_parameters,
415                     pulse(),
416                     _start_beats.val().to_double()
417                     ) != to_read) {
418                 return 0; /* "read nothing" */
419         }
420
421         return to_read;
422 }
423
424 XMLNode&
425 MidiRegion::state ()
426 {
427         return Region::state ();
428 }
429
430 int
431 MidiRegion::set_state (const XMLNode& node, int version)
432 {
433         int ret = Region::set_state (node, version);
434
435         if (ret == 0) {
436                 /* set length beats to the frame (non-musical) */
437                 if (position_lock_style() == AudioTime) {
438                         update_length_beats (0);
439                 }
440         }
441
442         return ret;
443 }
444
445 void
446 MidiRegion::recompute_at_end ()
447 {
448         /* our length has changed
449          * so what? stuck notes are dealt with via a note state tracker
450          */
451 }
452
453 void
454 MidiRegion::recompute_at_start ()
455 {
456         /* as above, but the shift was from the front
457          * maybe bump currently active note's note-ons up so they sound here?
458          * that could be undesireable in certain situations though.. maybe
459          * remove the note entirely, including it's note off?  something needs to
460          * be done to keep the played MIDI sane to avoid messing up voices of
461          * polyhonic things etc........
462          */
463 }
464
465 int
466 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
467 {
468         // TODO
469         return -1;
470 }
471
472 boost::shared_ptr<Evoral::Control>
473 MidiRegion::control (const Evoral::Parameter& id, bool create)
474 {
475         return model()->control(id, create);
476 }
477
478 boost::shared_ptr<const Evoral::Control>
479 MidiRegion::control (const Evoral::Parameter& id) const
480 {
481         return model()->control(id);
482 }
483
484 boost::shared_ptr<MidiModel>
485 MidiRegion::model()
486 {
487         return midi_source()->model();
488 }
489
490 boost::shared_ptr<const MidiModel>
491 MidiRegion::model() const
492 {
493         return midi_source()->model();
494 }
495
496 boost::shared_ptr<MidiSource>
497 MidiRegion::midi_source (uint32_t n) const
498 {
499         // Guaranteed to succeed (use a static cast?)
500         return boost::dynamic_pointer_cast<MidiSource>(source(n));
501 }
502
503 void
504 MidiRegion::model_changed ()
505 {
506         if (!model()) {
507                 return;
508         }
509
510         /* build list of filtered Parameters, being those whose automation state is not `Play' */
511
512         _filtered_parameters.clear ();
513
514         Automatable::Controls const & c = model()->controls();
515
516         for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
517                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
518                 assert (ac);
519                 if (ac->alist()->automation_state() != Play) {
520                         _filtered_parameters.insert (ac->parameter ());
521                 }
522         }
523
524         /* watch for changes to controls' AutoState */
525         midi_source()->AutomationStateChanged.connect_same_thread (
526                 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
527                 );
528 }
529
530 void
531 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
532 {
533         /* Update our filtered parameters list after a change to a parameter's AutoState */
534
535         boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
536         if (!ac || ac->alist()->automation_state() == Play) {
537                 /* It should be "impossible" for ac to be NULL, but if it is, don't
538                    filter the parameter so events aren't lost. */
539                 _filtered_parameters.erase (p);
540         } else {
541                 _filtered_parameters.insert (p);
542         }
543
544         /* the source will have an iterator into the model, and that iterator will have been set up
545            for a given set of filtered_parameters, so now that we've changed that list we must invalidate
546            the iterator.
547         */
548         Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
549         if (lm.locked()) {
550                 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
551                 midi_source(0)->invalidate (lm);
552         }
553 }
554
555 /** This is called when a trim drag has resulted in a -ve _start time for this region.
556  *  Fix it up by adding some empty space to the source.
557  */
558 void
559 MidiRegion::fix_negative_start ()
560 {
561         BeatsFramesConverter c (_session.tempo_map(), _position);
562
563         model()->insert_silence_at_start (c.from (-_start));
564         _start = 0;
565         _start_beats = Evoral::Beats();
566 }
567
568 void
569 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
570 {
571         Region::set_start_internal (s, sub_num);
572
573         if (position_lock_style() == AudioTime) {
574                 set_start_beats_from_start_frames ();
575                 }
576 }
577
578 void
579 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
580 {
581         if (locked()) {
582                 return;
583         }
584
585         PropertyChange what_changed;
586
587         /* beat has been set exactly by set_position_internal, but the source starts on a frame.
588            working in beats seems the correct thing to do, but reports of a missing first note
589            on playback suggest otherwise. for now, we work in exact beats.
590         */
591         const double pos_pulse = _session.tempo_map().exact_qn_at_frame (position, sub_num) / 4.0;
592         const double pulse_delta = pos_pulse - pulse();
593
594         /* Set position before length, otherwise for MIDI regions this bad thing happens:
595          * 1. we call set_length_internal; length in beats is computed using the region's current
596          *    (soon-to-be old) position
597          * 2. we call set_position_internal; position is set and length in frames re-computed using
598          *    length in beats from (1) but at the new position, which is wrong if the region
599          *    straddles a tempo/meter change.
600          */
601
602         if (_position != position) {
603                 /* sets _beat to new position.*/
604                 set_position_internal (position, true, sub_num);
605                 what_changed.add (Properties::position);
606
607                 const double new_start_pulse = (_start_beats.val().to_double() / 4.0) + pulse_delta;
608                 const framepos_t new_start = _position - _session.tempo_map().frame_at_pulse (pulse() - new_start_pulse);
609
610                 if (!verify_start_and_length (new_start, length)) {
611                         return;
612                 }
613
614                 _start_beats = Evoral::Beats (new_start_pulse * 4.0);
615                 what_changed.add (Properties::start_beats);
616
617                 set_start_internal (new_start, sub_num);
618                 what_changed.add (Properties::start);
619         }
620
621         if (_length != length) {
622
623                 if (!verify_start_and_length (_start, length)) {
624                         return;
625                 }
626
627                 set_length_internal (length, sub_num);
628                 what_changed.add (Properties::length);
629                 what_changed.add (Properties::length_beats);
630         }
631
632         set_whole_file (false);
633
634         PropertyChange start_and_length;
635
636         start_and_length.add (Properties::start);
637         start_and_length.add (Properties::length);
638
639         if (what_changed.contains (start_and_length)) {
640                 first_edit ();
641         }
642
643         if (!what_changed.empty()) {
644                 send_change (what_changed);
645         }
646 }