Ensure midi region start trim always sets _start_beats.
[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::beat, _beat);
194         plist.add (Properties::length_beats, _length_beats);
195         plist.add (Properties::layer, 0);
196
197         boost::shared_ptr<MidiRegion> ret (boost::dynamic_pointer_cast<MidiRegion> (RegionFactory::create (newsrc, plist, true)));
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                 /* we're called by Stateful::set_values() which sends a change
210                    only if the value is different from _current.
211                    session load means we can clobber length_beats here in error (not all properties differ from current),
212                    so disallow (this has been set from XML state anyway).
213                 */
214                 if (!_session.loading()) {
215                         /* update non-musically */
216                         update_length_beats (0);
217                 }
218         } else if (pc.contains (Properties::start) && !pc.contains (Properties::start_beats)) {
219                 set_start_beats_from_start_frames ();
220         }
221 }
222
223 void
224 MidiRegion::set_start_beats_from_start_frames ()
225 {
226         _start_beats = Evoral::Beats ((pulse() * 4.0) - _session.tempo_map().quarter_note_at_frame (_position - _start));
227 }
228
229 void
230 MidiRegion::set_length_internal (framecnt_t len, const int32_t sub_num)
231 {
232         Region::set_length_internal (len, sub_num);
233         update_length_beats (sub_num);
234 }
235
236 void
237 MidiRegion::update_after_tempo_map_change (bool /* send */)
238 {
239         boost::shared_ptr<Playlist> pl (playlist());
240
241         if (!pl) {
242                 return;
243         }
244
245         const framepos_t old_pos = _position;
246         const framepos_t old_length = _length;
247         const framepos_t old_start = _start;
248
249         PropertyChange s_and_l;
250
251         if (position_lock_style() == AudioTime) {
252                 recompute_position_from_lock_style (0);
253
254                 /*
255                   set _start to new position in tempo map.
256
257                   The user probably expects the region contents to maintain audio position as the
258                   tempo changes, but AFAICT this requires modifying the src file to use
259                   SMPTE timestamps with the current disk read model (?).
260
261                   We could arguably use _start to set _start_beats here,
262                   resulting in viewport-like behaviour (the contents maintain
263                   their musical position while the region is stationary).
264
265                   For now, the musical position at the region start is retained, but subsequent events
266                   will maintain their beat distance according to the map.
267                 */
268                 _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
269
270                 /* _length doesn't change for audio-locked regions. update length_beats to match. */
271                 _length_beats = Evoral::Beats (_session.tempo_map().quarter_note_at_frame (_position + _length) - _session.tempo_map().quarter_note_at_frame (_position));
272
273                 s_and_l.add (Properties::start);
274                 s_and_l.add (Properties::length_beats);
275
276                 send_change  (s_and_l);
277                 return;
278         }
279
280         Region::update_after_tempo_map_change (false);
281
282         /* _start has now been updated. */
283         _length = _session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position;
284
285         if (old_start != _start) {
286                 s_and_l.add (Properties::start);
287         }
288         if (old_length != _length) {
289                 s_and_l.add (Properties::length);
290         }
291         if (old_pos != _position) {
292                 s_and_l.add (Properties::position);
293         }
294
295         send_change (s_and_l);
296 }
297
298 void
299 MidiRegion::update_length_beats (const int32_t sub_num)
300 {
301         _length_beats = Evoral::Beats (_session.tempo_map().exact_qn_at_frame (_position + _length, sub_num) - (pulse() * 4.0));
302 }
303
304 void
305 MidiRegion::set_position_internal (framepos_t pos, bool allow_bbt_recompute, const int32_t sub_num)
306 {
307         Region::set_position_internal (pos, allow_bbt_recompute, sub_num);
308
309         /* don't clobber _start _length and _length_beats if session loading.*/
310         if (_session.loading()) {
311                 return;
312         }
313
314         /* set _start to new position in tempo map */
315         _start = _position - _session.tempo_map().frame_at_pulse (pulse() - (_start_beats.val().to_double() / 4.0));
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         } else {
325                 /* leave _length_beats alone, and change _length to reflect the state of things
326                    at the new position (tempo map may dictate a different number of frames).
327                 */
328                 Region::set_length_internal (_session.tempo_map().frame_at_pulse (pulse() + (_length_beats.val().to_double() / 4.0)) - _position, sub_num);
329         }
330 }
331
332 framecnt_t
333 MidiRegion::read_at (Evoral::EventSink<framepos_t>& out,
334                      framepos_t                     position,
335                      framecnt_t                     dur,
336                      Evoral::Range<framepos_t>*     loop_range,
337                      uint32_t                       chan_n,
338                      NoteMode                       mode,
339                      MidiStateTracker*              tracker,
340                      MidiChannelFilter*             filter) const
341 {
342         return _read_at (_sources, out, position, dur, loop_range, chan_n, mode, tracker, filter);
343 }
344
345 framecnt_t
346 MidiRegion::master_read_at (MidiRingBuffer<framepos_t>& out,
347                             framepos_t                  position,
348                             framecnt_t                  dur,
349                             Evoral::Range<framepos_t>*  loop_range,
350                             uint32_t                    chan_n,
351                             NoteMode                    mode) const
352 {
353         return _read_at (_master_sources, out, position, dur, loop_range, chan_n, mode); /* no tracker */
354 }
355
356 framecnt_t
357 MidiRegion::_read_at (const SourceList&              /*srcs*/,
358                       Evoral::EventSink<framepos_t>& dst,
359                       framepos_t                     position,
360                       framecnt_t                     dur,
361                       Evoral::Range<framepos_t>*     loop_range,
362                       uint32_t                       chan_n,
363                       NoteMode                       mode,
364                       MidiStateTracker*              tracker,
365                       MidiChannelFilter*             filter) const
366 {
367         frameoffset_t internal_offset = 0;
368         framecnt_t    to_read         = 0;
369
370         /* precondition: caller has verified that we cover the desired section */
371
372         assert(chan_n == 0);
373
374         if (muted()) {
375                 return 0; /* read nothing */
376         }
377
378         if (position < _position) {
379                 /* we are starting the read from before the start of the region */
380                 internal_offset = 0;
381                 dur -= _position - position;
382         } else {
383                 /* we are starting the read from after the start of the region */
384                 internal_offset = position - _position;
385         }
386
387         if (internal_offset >= _length) {
388                 return 0; /* read nothing */
389         }
390
391         if ((to_read = min (dur, _length - internal_offset)) == 0) {
392                 return 0; /* read nothing */
393         }
394
395         boost::shared_ptr<MidiSource> src = midi_source(chan_n);
396
397         Glib::Threads::Mutex::Lock lm(src->mutex());
398
399         src->set_note_mode(lm, mode);
400
401 #if 0
402         cerr << "MR " << name () << " read @ " << position << " + " << to_read
403              << " dur was " << dur
404              << " len " << _length
405              << " l-io " << (_length - internal_offset)
406              << " _position = " << _position
407              << " _start = " << _start
408              << " intoffset = " << internal_offset
409              << " pulse = " << pulse()
410              << " start_pulse = " << start_pulse()
411              << " start_beat = " << _start_beats
412              << endl;
413 #endif
414
415         /* This call reads events from a source and writes them to `dst' timed in session frames */
416
417         if (src->midi_read (
418                     lm, // source lock
419                     dst, // destination buffer
420                     _position - _start, // start position of the source in session frames
421                     _start + internal_offset, // where to start reading in the source
422                     to_read, // read duration in frames
423                     loop_range,
424                     tracker,
425                     filter,
426                     _filtered_parameters,
427                     pulse(),
428                     _start_beats.val().to_double()
429                     ) != to_read) {
430                 return 0; /* "read nothing" */
431         }
432
433         return to_read;
434 }
435
436 XMLNode&
437 MidiRegion::state ()
438 {
439         return Region::state ();
440 }
441
442 int
443 MidiRegion::set_state (const XMLNode& node, int version)
444 {
445         int ret = Region::set_state (node, version);
446
447         if (ret == 0) {
448                 /* set length beats to the frame (non-musical) */
449                 if (position_lock_style() == AudioTime) {
450                         update_length_beats (0);
451                 }
452         }
453
454         return ret;
455 }
456
457 void
458 MidiRegion::recompute_at_end ()
459 {
460         /* our length has changed
461          * so what? stuck notes are dealt with via a note state tracker
462          */
463 }
464
465 void
466 MidiRegion::recompute_at_start ()
467 {
468         /* as above, but the shift was from the front
469          * maybe bump currently active note's note-ons up so they sound here?
470          * that could be undesireable in certain situations though.. maybe
471          * remove the note entirely, including it's note off?  something needs to
472          * be done to keep the played MIDI sane to avoid messing up voices of
473          * polyhonic things etc........
474          */
475 }
476
477 int
478 MidiRegion::separate_by_channel (ARDOUR::Session&, vector< boost::shared_ptr<Region> >&) const
479 {
480         // TODO
481         return -1;
482 }
483
484 boost::shared_ptr<Evoral::Control>
485 MidiRegion::control (const Evoral::Parameter& id, bool create)
486 {
487         return model()->control(id, create);
488 }
489
490 boost::shared_ptr<const Evoral::Control>
491 MidiRegion::control (const Evoral::Parameter& id) const
492 {
493         return model()->control(id);
494 }
495
496 boost::shared_ptr<MidiModel>
497 MidiRegion::model()
498 {
499         return midi_source()->model();
500 }
501
502 boost::shared_ptr<const MidiModel>
503 MidiRegion::model() const
504 {
505         return midi_source()->model();
506 }
507
508 boost::shared_ptr<MidiSource>
509 MidiRegion::midi_source (uint32_t n) const
510 {
511         // Guaranteed to succeed (use a static cast?)
512         return boost::dynamic_pointer_cast<MidiSource>(source(n));
513 }
514
515 /* don't use this. hopefully it will go away.
516    currently used by headless-chicken session utility.
517 */
518 void
519 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
520 {
521        drop_sources();
522
523        _sources.push_back (s);
524        s->inc_use_count ();
525        _master_sources.push_back (s);
526        s->inc_use_count ();
527
528        s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
529
530 }
531
532 void
533 MidiRegion::model_changed ()
534 {
535         if (!model()) {
536                 return;
537         }
538
539         /* build list of filtered Parameters, being those whose automation state is not `Play' */
540
541         _filtered_parameters.clear ();
542
543         Automatable::Controls const & c = model()->controls();
544
545         for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
546                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
547                 assert (ac);
548                 if (ac->alist()->automation_state() != Play) {
549                         _filtered_parameters.insert (ac->parameter ());
550                 }
551         }
552
553         /* watch for changes to controls' AutoState */
554         midi_source()->AutomationStateChanged.connect_same_thread (
555                 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
556                 );
557 }
558
559 void
560 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
561 {
562         /* Update our filtered parameters list after a change to a parameter's AutoState */
563
564         boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
565         if (!ac || ac->alist()->automation_state() == Play) {
566                 /* It should be "impossible" for ac to be NULL, but if it is, don't
567                    filter the parameter so events aren't lost. */
568                 _filtered_parameters.erase (p);
569         } else {
570                 _filtered_parameters.insert (p);
571         }
572
573         /* the source will have an iterator into the model, and that iterator will have been set up
574            for a given set of filtered_parameters, so now that we've changed that list we must invalidate
575            the iterator.
576         */
577         Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
578         if (lm.locked()) {
579                 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
580                 midi_source(0)->invalidate (lm);
581         }
582 }
583
584 /** This is called when a trim drag has resulted in a -ve _start time for this region.
585  *  Fix it up by adding some empty space to the source.
586  */
587 void
588 MidiRegion::fix_negative_start ()
589 {
590         BeatsFramesConverter c (_session.tempo_map(), _position);
591
592         model()->insert_silence_at_start (c.from (-_start));
593         _start = 0;
594         _start_beats = Evoral::Beats();
595 }
596
597 void
598 MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
599 {
600         Region::set_start_internal (s, sub_num);
601         if (position_lock_style() == AudioTime) {
602                 set_start_beats_from_start_frames ();
603         }
604 }
605
606 void
607 MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
608 {
609         if (locked()) {
610                 return;
611         }
612
613         PropertyChange what_changed;
614
615
616         /* Set position before length, otherwise for MIDI regions this bad thing happens:
617          * 1. we call set_length_internal; length in beats is computed using the region's current
618          *    (soon-to-be old) position
619          * 2. we call set_position_internal; position is set and length in frames re-computed using
620          *    length in beats from (1) but at the new position, which is wrong if the region
621          *    straddles a tempo/meter change.
622          */
623
624         if (_position != position) {
625
626                 const double pos_qn = _session.tempo_map().exact_qn_at_frame (position, sub_num);
627                 const double old_pos_qn = pulse() * 4.0;
628
629                 /* sets _pulse to new position.*/
630                 set_position_internal (position, true, sub_num);
631                 what_changed.add (Properties::position);
632
633                 double new_start_qn = start_beats().to_double() + (pos_qn - old_pos_qn);
634                 const framepos_t new_start = _position - _session.tempo_map().frame_at_quarter_note (pos_qn - new_start_qn);
635
636                 if (!verify_start_and_length (new_start, length)) {
637                         return;
638                 }
639
640                 /* at small deltas, (high zooms) the property will not change without this (tick resolution of Beats::operator!=)*/
641                 _start_beats += Evoral::Beats (new_start_qn) + start_beats().tick() * 2.0;
642                 _start_beats = Evoral::Beats (new_start_qn);
643                 what_changed.add (Properties::start_beats);
644
645                 set_start_internal (new_start, sub_num);
646                 what_changed.add (Properties::start);
647         }
648
649         if (_length != length) {
650
651                 if (!verify_start_and_length (_start, length)) {
652                         return;
653                 }
654
655                 set_length_internal (length, sub_num);
656                 what_changed.add (Properties::length);
657                 what_changed.add (Properties::length_beats);
658         }
659
660         set_whole_file (false);
661
662         PropertyChange start_and_length;
663
664         start_and_length.add (Properties::start);
665         start_and_length.add (Properties::length);
666
667         if (what_changed.contains (start_and_length)) {
668                 first_edit ();
669         }
670
671         if (!what_changed.empty()) {
672                 send_change (what_changed);
673         }
674 }