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