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