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