ensure non-zero midi region length when tempo map changes.
[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_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
48 #include "pbd/i18n.h"
49 #include <locale.h>
50
51 using namespace std;
52 using namespace ARDOUR;
53 using namespace PBD;
54
55 namespace ARDOUR {
56         namespace Properties {
57                 PBD::PropertyDescriptor<double> start_beats;
58                 PBD::PropertyDescriptor<double> length_beats;
59         }
60 }
61
62 void
63 MidiRegion::make_property_quarks ()
64 {
65         Properties::start_beats.property_id = g_quark_from_static_string (X_("start-beats"));
66         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for start-beats = %1\n", Properties::start_beats.property_id));
67         Properties::length_beats.property_id = g_quark_from_static_string (X_("length-beats"));
68         DEBUG_TRACE (DEBUG::Properties, string_compose ("quark for length-beats = %1\n", Properties::length_beats.property_id));
69 }
70
71 void
72 MidiRegion::register_properties ()
73 {
74         add_property (_start_beats);
75         add_property (_length_beats);
76 }
77
78 /* Basic MidiRegion constructor (many channels) */
79 MidiRegion::MidiRegion (const SourceList& srcs)
80         : Region (srcs)
81         , _start_beats (Properties::start_beats, 0.0)
82         , _length_beats (Properties::length_beats, midi_source(0)->length_beats().to_double())
83 {
84         register_properties ();
85         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
86         model_changed ();
87         assert(_name.val().find("/") == string::npos);
88         assert(_type == DataType::MIDI);
89 }
90
91 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other)
92         : Region (other)
93         , _start_beats (Properties::start_beats, other->_start_beats)
94         , _length_beats (Properties::length_beats, other->_length_beats)
95 {
96         //update_length_beats ();
97         register_properties ();
98
99         assert(_name.val().find("/") == string::npos);
100         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
101         model_changed ();
102 }
103
104 /** Create a new MidiRegion that is part of an existing one */
105 MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t offset, const int32_t sub_num)
106         : Region (other, offset, sub_num)
107         , _start_beats (Properties::start_beats, 0.0)
108         , _length_beats (Properties::length_beats, other->_length_beats)
109 {
110         _start_beats = (_session.tempo_map().exact_qn_at_frame (other->_position + offset, sub_num) - other->pos_beats()) + other->_start_beats;
111
112         update_length_beats (sub_num);
113         register_properties ();
114
115         assert(_name.val().find("/") == string::npos);
116         midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
117         model_changed ();
118 }
119
120 MidiRegion::~MidiRegion ()
121 {
122 }
123
124 /** Export the MIDI data of the MidiRegion to a new MIDI file (SMF).
125  */
126 bool
127 MidiRegion::do_export (string path) const
128 {
129         boost::shared_ptr<MidiSource> newsrc;
130
131         /* caller must check for pre-existing file */
132         assert (!path.empty());
133         assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
134         newsrc = boost::dynamic_pointer_cast<MidiSource>(
135                 SourceFactory::createWritable(DataType::MIDI, _session,
136                                               path, false, _session.frame_rate()));
137
138         BeatsFramesConverter bfc (_session.tempo_map(), _position);
139         Evoral::Beats const bbegin = bfc.from (_start);
140         Evoral::Beats const bend = bfc.from (_start + _length);
141
142         {
143                 /* Lock our source since we'll be reading from it.  write_to() will
144                    take a lock on newsrc. */
145                 Source::Lock lm (midi_source(0)->mutex());
146                 if (midi_source(0)->export_write_to (lm, newsrc, bbegin, bend)) {
147                         return false;
148                 }
149         }
150
151         return true;
152 }
153
154
155 /** Create a new MidiRegion that has its own version of some/all of the Source used by another.
156  */
157 boost::shared_ptr<MidiRegion>
158 MidiRegion::clone (string path) const
159 {
160         boost::shared_ptr<MidiSource> newsrc;
161
162         /* caller must check for pre-existing file */
163         assert (!path.empty());
164         assert (!Glib::file_test (path, Glib::FILE_TEST_EXISTS));
165         newsrc = boost::dynamic_pointer_cast<MidiSource>(
166                 SourceFactory::createWritable(DataType::MIDI, _session,
167                                               path, false, _session.frame_rate()));
168         return clone (newsrc);
169 }
170
171 boost::shared_ptr<MidiRegion>
172 MidiRegion::clone (boost::shared_ptr<MidiSource> newsrc) const
173 {
174         BeatsFramesConverter bfc (_session.tempo_map(), _position);
175         Evoral::Beats const bbegin = bfc.from (_start);
176         Evoral::Beats const bend = bfc.from (_start + _length);
177
178         {
179                 /* Lock our source since we'll be reading from it.  write_to() will
180                    take a lock on newsrc. */
181                 Source::Lock lm (midi_source(0)->mutex());
182                 if (midi_source(0)->write_to (lm, newsrc, bbegin, bend)) {
183                         return boost::shared_ptr<MidiRegion> ();
184                 }
185         }
186
187         PropertyList plist;
188
189         plist.add (Properties::name, PBD::basename_nosuffix (newsrc->name()));
190         plist.add (Properties::whole_file, true);
191         plist.add (Properties::start, _start);
192         plist.add (Properties::start_beats, _start_beats);
193         plist.add (Properties::length, _length);
194         plist.add (Properties::beat, _beat);
195         plist.add (Properties::length_beats, _length_beats);
196         plist.add (Properties::layer, 0);
197
198         boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
199         ret->set_pos_beats (pos_beats());
200
201         return ret;
202 }
203
204 void
205 MidiRegion::post_set (const PropertyChange& pc)
206 {
207         Region::post_set (pc);
208
209         if (pc.contains (Properties::length) && !pc.contains (Properties::length_beats)) {
210                 /* we're called by Stateful::set_values() which sends a change
211                    only if the value is different from _current.
212                    session load means we can clobber length_beats here in error (not all properties differ from current),
213                    so disallow (this has been set from XML state anyway).
214                 */
215                 if (!_session.loading()) {
216                         /* ensure this only updates non-musical regions */
217                         if (position_lock_style() == AudioTime) {
218                                 update_length_beats (0);
219                         }
220                 }
221         } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
222                 set_start_beats_from_start_frames ();
223         }
224 }
225
226 void
227 MidiRegion::set_start_beats_from_start_frames ()
228 {
229         if (position_lock_style() == AudioTime) {
230                 _start_beats = pos_beats() - _session.tempo_map().quarter_note_at_frame (_position - _start);
231         }
232 }
233
234 void
235 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
236 {
237         Region::set_length_internal (len, sub_num);
238         update_length_beats (sub_num);
239 }
240
241 void
242 MidiRegion::update_after_tempo_map_change (bool /* send */)
243 {
244         boost::shared_ptr<Playlist> pl (playlist());
245
246         if (!pl) {
247                 return;
248         }
249
250         const framepos_t old_pos = _position;
251         const framepos_t old_length = _length;
252         const framepos_t old_start = _start;
253
254         PropertyChange s_and_l;
255
256         if (position_lock_style() == AudioTime) {
257                 recompute_position_from_lock_style (0);
258
259                 /*
260                   set _start to new position in tempo map.
261
262                   The user probably expects the region contents to maintain audio position as the
263                   tempo changes, but AFAICT this requires modifying the src file to use
264                   SMPTE timestamps with the current disk read model (?).
265
266                   We could arguably use _start to set _start_beats here,
267                   resulting in viewport-like behaviour (the contents maintain
268                   their musical position while the region is stationary).
269
270                   For now, the musical position at the region start is retained, but subsequent events
271                   will maintain their beat distance according to the map.
272                 */
273                 _start = _session.tempo_map().frames_between_quarter_notes (pos_beats() - start_beats(), pos_beats());
274
275                 /* _length doesn't change for audio-locked regions. update length_beats to match. */
276                 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - pos_beats();
277
278                 s_and_l.add (Properties::start);
279                 s_and_l.add (Properties::length_beats);
280
281                 send_change  (s_and_l);
282                 return;
283         }
284
285         Region::update_after_tempo_map_change (false);
286
287         /* _start has now been updated. */
288         _length = max ((framecnt_t) 1, _session.tempo_map().frames_between_quarter_notes (pos_beats(), pos_beats() + _length_beats));
289
290         if (old_start != _start) {
291                 s_and_l.add (Properties::start);
292         }
293         if (old_length != _length) {
294                 s_and_l.add (Properties::length);
295         }
296         if (old_pos != _position) {
297                 s_and_l.add (Properties::position);
298         }
299
300         send_change (s_and_l);
301 }
302
303 void
304 MidiRegion::update_length_beats (const int32_t sub_num)
305 {
306         _length_beats = _session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - pos_beats();
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         /* don't clobber _start _length and _length_beats if session loading.*/
315         if (_session.loading()) {
316                 return;
317         }
318
319         /* set _start to new position in tempo map */
320         _start = _session.tempo_map().frames_between_quarter_notes (pos_beats() - start_beats(), pos_beats());
321
322         /* in construction from src */
323         if (_length_beats == 0.0) {
324                 update_length_beats (sub_num);
325         }
326
327         if (position_lock_style() == AudioTime) {
328                 _length_beats = _session.tempo_map().quarter_note_at_frame (_position + _length) - pos_beats();
329         } else {
330                 /* leave _length_beats alone, and change _length to reflect the state of things
331                    at the new position (tempo map may dictate a different number of frames).
332                 */
333                 Region::set_length_internal (_session.tempo_map().frames_between_quarter_notes (pos_beats(), pos_beats() + length_beats()), sub_num);
334         }
335 }
336
337 framecnt_t
338 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
339                      framepos_t                     position,
340                      framecnt_t                     dur,
341                      Evoral::Range<framepos_t>*     loop_range,
342                      MidiCursor&                    cursor,
343                      uint32_t                       chan_n,
344                      NoteMode                       mode,
345                      MidiStateTracker*              tracker,
346                      MidiChannelFilter*             filter) const
347 {
348         return _read_at (_sources, out, position, dur, loop_range, cursor, chan_n, mode, tracker, filter);
349 }
350
351 framecnt_t
352 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
353                             framepos_t                  position,
354                             framecnt_t                  dur,
355                             Evoral::Range<framepos_t>*  loop_range,
356                             MidiCursor&                 cursor,
357                             uint32_t                    chan_n,
358                             NoteMode                    mode) const
359 {
360         return _read_at (_master_sources, out, position, dur, loop_range, cursor, chan_n, mode); /* no tracker */
361 }
362
363 framecnt_t
364 MidiRegion::_read_at (const SourceList&              /*srcs*/,
365                       Evoral::EventSink<framepos_t>& dst,
366                       framepos_t                     position,
367                       framecnt_t                     dur,
368                       Evoral::Range<framepos_t>*     loop_range,
369                       MidiCursor&                    cursor,
370                       uint32_t                       chan_n,
371                       NoteMode                       mode,
372                       MidiStateTracker*              tracker,
373                       MidiChannelFilter*             filter) const
374 {
375         frameoffset_t internal_offset = 0;
376         framecnt_t    to_read         = 0;
377
378         /* precondition: caller has verified that we cover the desired section */
379
380         assert(chan_n == 0);
381
382         if (muted()) {
383                 return 0; /* read nothing */
384         }
385
386         if (position < _position) {
387                 /* we are starting the read from before the start of the region */
388                 internal_offset = 0;
389                 dur -= _position - position;
390         } else {
391                 /* we are starting the read from after the start of the region */
392                 internal_offset = position - _position;
393         }
394
395         if (internal_offset >= _length) {
396                 return 0; /* read nothing */
397         }
398
399         if ((to_read = min (dur, _length - internal_offset)) == 0) {
400                 return 0; /* read nothing */
401         }
402
403         boost::shared_ptr<MidiSource> src = midi_source(chan_n);
404
405         Glib::Threads::Mutex::Lock lm(src->mutex());
406
407         src->set_note_mode(lm, mode);
408
409 #if 0
410         cerr << "MR " << name () << " read @ " << position << " + " << to_read
411              << " dur was " << dur
412              << " len " << _length
413              << " l-io " << (_length - internal_offset)
414              << " _position = " << _position
415              << " _start = " << _start
416              << " intoffset = " << internal_offset
417              << " pos_beats = " << pos_beats()
418              << " start_beat = " << _start_beats
419              << endl;
420 #endif
421
422         /* This call reads events from a source and writes them to `dst' timed in session frames */
423
424         if (src->midi_read (
425                     lm, // source lock
426                     dst, // destination buffer
427                     _position - _start, // start position of the source in session frames
428                     _start + internal_offset, // where to start reading in the source
429                     to_read, // read duration in frames
430                     loop_range,
431                     cursor,
432                     tracker,
433                     filter,
434                     _filtered_parameters,
435                     pos_beats(),
436                     _start_beats
437                     ) != to_read) {
438                 return 0; /* "read nothing" */
439         }
440
441         return to_read;
442 }
443
444 XMLNode&
445 MidiRegion::state ()
446 {
447         return Region::state ();
448 }
449
450 int
451 MidiRegion::set_state (const XMLNode& node, int version)
452 {
453         int ret = Region::set_state (node, version);
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 /* don't use this. hopefully it will go away.
517    currently used by headless-chicken session utility.
518 */
519 void
520 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
521 {
522        drop_sources();
523
524        _sources.push_back (s);
525        s->inc_use_count ();
526        _master_sources.push_back (s);
527        s->inc_use_count ();
528
529        s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
530
531 }
532
533 void
534 MidiRegion::model_changed ()
535 {
536         if (!model()) {
537                 return;
538         }
539
540         /* build list of filtered Parameters, being those whose automation state is not `Play' */
541
542         _filtered_parameters.clear ();
543
544         Automatable::Controls const & c = model()->controls();
545
546         for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
547                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
548                 assert (ac);
549                 if (ac->alist()->automation_state() != Play) {
550                         _filtered_parameters.insert (ac->parameter ());
551                 }
552         }
553
554         /* watch for changes to controls' AutoState */
555         midi_source()->AutomationStateChanged.connect_same_thread (
556                 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
557                 );
558 }
559
560 void
561 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
562 {
563         /* Update our filtered parameters list after a change to a parameter's AutoState */
564
565         boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
566         if (!ac || ac->alist()->automation_state() == Play) {
567                 /* It should be "impossible" for ac to be NULL, but if it is, don't
568                    filter the parameter so events aren't lost. */
569                 _filtered_parameters.erase (p);
570         } else {
571                 _filtered_parameters.insert (p);
572         }
573
574         /* the source will have an iterator into the model, and that iterator will have been set up
575            for a given set of filtered_parameters, so now that we've changed that list we must invalidate
576            the iterator.
577         */
578         Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
579         if (lm.locked()) {
580                 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
581                 midi_source(0)->invalidate (lm);
582         }
583 }
584
585 /** This is called when a trim drag has resulted in a -ve _start time for this region.
586  *  Fix it up by adding some empty space to the source.
587  */
588 void
589 MidiRegion::fix_negative_start ()
590 {
591         BeatsFramesConverter c (_session.tempo_map(), _position);
592
593         model()->insert_silence_at_start (c.from (-_start));
594         _start = 0;
595         _start_beats = 0.0;
596 }
597
598 void
599 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
600 {
601         Region::set_start_internal (s, sub_num);
602         set_start_beats_from_start_frames ();
603 }
604
605 void
606 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
607 {
608         if (locked()) {
609                 return;
610         }
611
612         PropertyChange what_changed;
613
614
615         /* Set position before length, otherwise for MIDI regions this bad thing happens:
616          * 1. we call set_length_internal; length in beats is computed using the region's current
617          *    (soon-to-be old) position
618          * 2. we call set_position_internal; position is set and length in frames re-computed using
619          *    length in beats from (1) but at the new position, which is wrong if the region
620          *    straddles a tempo/meter change.
621          */
622
623         if (_position != position) {
624
625                 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
626                 const double old_pos_qn = pos_beats();
627
628                 /* sets _pulse to new position.*/
629                 set_position_internal (position, true, sub_num);
630                 what_changed.add (Properties::position);
631
632                 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
633                 framepos_t new_start = _session.tempo_map().frames_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
634
635                 if (!verify_start_and_length (new_start, length)) {
636                         return;
637                 }
638
639                 _start_beats = new_start_qn;
640                 what_changed.add (Properties::start_beats);
641
642                 set_start_internal (new_start, sub_num);
643                 what_changed.add (Properties::start);
644         }
645
646         if (_length != length) {
647
648                 if (!verify_start_and_length (_start, length)) {
649                         return;
650                 }
651
652                 set_length_internal (length, sub_num);
653                 what_changed.add (Properties::length);
654                 what_changed.add (Properties::length_beats);
655         }
656
657         set_whole_file (false);
658
659         PropertyChange start_and_length;
660
661         start_and_length.add (Properties::start);
662         start_and_length.add (Properties::length);
663
664         if (what_changed.contains (start_and_length)) {
665                 first_edit ();
666         }
667
668         if (!what_changed.empty()) {
669                 send_change (what_changed);
670         }
671 }