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