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