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