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