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