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