fix mistakenly-placed semi-colon
[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 XMLNode&
485 MidiRegion::state ()
486 {
487         return Region::state ();
488 }
489
490 int
491 MidiRegion::set_state (const XMLNode& node, int version)
492 {
493         int ret = Region::set_state (node, version);
494
495         return ret;
496 }
497
498 void
499 MidiRegion::recompute_at_end ()
500 {
501         /* our length has changed
502          * so what? stuck notes are dealt with via a note state tracker
503          */
504 }
505
506 void
507 MidiRegion::recompute_at_start ()
508 {
509         /* as above, but the shift was from the front
510          * maybe bump currently active note's note-ons up so they sound here?
511          * that could be undesireable in certain situations though.. maybe
512          * remove the note entirely, including it's note off?  something needs to
513          * be done to keep the played MIDI sane to avoid messing up voices of
514          * polyhonic things etc........
515          */
516 }
517
518 int
519 MidiRegion::separate_by_channel (vector< boost::shared_ptr<Region> >&) const
520 {
521         // TODO
522         return -1;
523 }
524
525 boost::shared_ptr<Evoral::Control>
526 MidiRegion::control (const Evoral::Parameter& id, bool create)
527 {
528         return model()->control(id, create);
529 }
530
531 boost::shared_ptr<const Evoral::Control>
532 MidiRegion::control (const Evoral::Parameter& id) const
533 {
534         return model()->control(id);
535 }
536
537 boost::shared_ptr<MidiModel>
538 MidiRegion::model()
539 {
540         return midi_source()->model();
541 }
542
543 boost::shared_ptr<const MidiModel>
544 MidiRegion::model() const
545 {
546         return midi_source()->model();
547 }
548
549 boost::shared_ptr<MidiSource>
550 MidiRegion::midi_source (uint32_t n) const
551 {
552         // Guaranteed to succeed (use a static cast?)
553         return boost::dynamic_pointer_cast<MidiSource>(source(n));
554 }
555
556 /* don't use this. hopefully it will go away.
557    currently used by headless-chicken session utility.
558 */
559 void
560 MidiRegion::clobber_sources (boost::shared_ptr<MidiSource> s)
561 {
562        drop_sources();
563
564        _sources.push_back (s);
565        s->inc_use_count ();
566        _master_sources.push_back (s);
567        s->inc_use_count ();
568
569        s->DropReferences.connect_same_thread (*this, boost::bind (&Region::source_deleted, this, boost::weak_ptr<Source>(s)));
570
571 }
572
573 void
574 MidiRegion::model_changed ()
575 {
576         if (!model()) {
577                 return;
578         }
579
580         /* build list of filtered Parameters, being those whose automation state is not `Play' */
581
582         _filtered_parameters.clear ();
583
584         Automatable::Controls const & c = model()->controls();
585
586         for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
587                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
588                 assert (ac);
589                 if (ac->alist()->automation_state() != Play) {
590                         _filtered_parameters.insert (ac->parameter ());
591                 }
592         }
593
594         /* watch for changes to controls' AutoState */
595         midi_source()->AutomationStateChanged.connect_same_thread (
596                 _model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
597                 );
598
599         model()->ContentsShifted.connect_same_thread (_model_shift_connection, boost::bind (&MidiRegion::model_shifted, this, _1));
600 }
601 void
602 MidiRegion::model_shifted (double qn_distance)
603 {
604         if (!model()) {
605                 return;
606         }
607
608         if (!_ignore_shift) {
609                 PropertyChange what_changed;
610                 _start_beats += qn_distance;
611                 samplepos_t const new_start = _session.tempo_map().samples_between_quarter_notes (_quarter_note - _start_beats, _quarter_note);
612                 _start = new_start;
613                 what_changed.add (Properties::start);
614                 what_changed.add (Properties::start_beats);
615                 send_change (what_changed);
616         } else {
617                 _ignore_shift = false;
618         }
619 }
620
621 void
622 MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
623 {
624         /* Update our filtered parameters list after a change to a parameter's AutoState */
625
626         boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
627         if (!ac || ac->alist()->automation_state() == Play) {
628                 /* It should be "impossible" for ac to be NULL, but if it is, don't
629                    filter the parameter so events aren't lost. */
630                 _filtered_parameters.erase (p);
631         } else {
632                 _filtered_parameters.insert (p);
633         }
634
635         /* the source will have an iterator into the model, and that iterator will have been set up
636            for a given set of filtered_parameters, so now that we've changed that list we must invalidate
637            the iterator.
638         */
639         Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
640         if (lm.locked()) {
641                 /* TODO: This is too aggressive, we need more fine-grained invalidation. */
642                 midi_source(0)->invalidate (lm);
643         }
644 }
645
646 /** This is called when a trim drag has resulted in a -ve _start time for this region.
647  *  Fix it up by adding some empty space to the source.
648  */
649 void
650 MidiRegion::fix_negative_start ()
651 {
652         BeatsSamplesConverter c (_session.tempo_map(), _position);
653
654         _ignore_shift = true;
655
656         model()->insert_silence_at_start (Temporal::Beats (- _start_beats));
657
658         _start = 0;
659         _start_beats = 0.0;
660 }
661
662 void
663 MidiRegion::set_start_internal (samplecnt_t s, const int32_t sub_num)
664 {
665         Region::set_start_internal (s, sub_num);
666
667         set_start_beats_from_start_samples ();
668 }
669
670 void
671 MidiRegion::trim_to_internal (samplepos_t position, samplecnt_t length, const int32_t sub_num)
672 {
673         if (locked()) {
674                 return;
675         }
676
677         PropertyChange what_changed;
678
679
680         /* Set position before length, otherwise for MIDI regions this bad thing happens:
681          * 1. we call set_length_internal; length in beats is computed using the region's current
682          *    (soon-to-be old) position
683          * 2. we call set_position_internal; position is set and length in samples re-computed using
684          *    length in beats from (1) but at the new position, which is wrong if the region
685          *    straddles a tempo/meter change.
686          */
687
688         if (_position != position) {
689
690                 const double pos_qn = _session.tempo_map().exact_qn_at_sample (position, sub_num);
691                 const double old_pos_qn = quarter_note();
692
693                 /* sets _pulse to new position.*/
694                 set_position_internal (position, true, sub_num);
695                 what_changed.add (Properties::position);
696
697                 double new_start_qn = start_beats() + (pos_qn - old_pos_qn);
698                 samplepos_t new_start = _session.tempo_map().samples_between_quarter_notes (pos_qn - new_start_qn, pos_qn);
699
700                 if (!verify_start_and_length (new_start, length)) {
701                         return;
702                 }
703
704                 _start_beats = new_start_qn;
705                 what_changed.add (Properties::start_beats);
706
707                 set_start_internal (new_start, sub_num);
708                 what_changed.add (Properties::start);
709         }
710
711         if (_length != length) {
712
713                 if (!verify_start_and_length (_start, length)) {
714                         return;
715                 }
716
717                 set_length_internal (length, sub_num);
718                 what_changed.add (Properties::length);
719                 what_changed.add (Properties::length_beats);
720         }
721
722         set_whole_file (false);
723
724         PropertyChange start_and_length;
725
726         start_and_length.add (Properties::start);
727         start_and_length.add (Properties::length);
728
729         if (what_changed.contains (start_and_length)) {
730                 first_edit ();
731         }
732
733         if (!what_changed.empty()) {
734                 send_change (what_changed);
735         }
736 }