Fixup prev commit (LV2 X11 UI) -- #7837
[ardour.git] / libs / ardour / smf_source.cc
1 /*
2  * Copyright (C) 2006-2016 David Robillard <d@drobilla.net>
3  * Copyright (C) 2007-2018 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2008-2009 Hans Baier <hansfbaier@googlemail.com>
5  * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
6  * Copyright (C) 2012-2016 Tim Mayberry <mojofunk@gmail.com>
7  * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
8  * Copyright (C) 2014-2016 John Emmas <john@creativepost.co.uk>
9  * Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
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 <vector>
27
28 #include <sys/time.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <regex.h>
33
34 #include "pbd/file_utils.h"
35 #include "pbd/stl_delete.h"
36 #include "pbd/strsplit.h"
37
38 #include "pbd/gstdio_compat.h"
39 #include <glibmm/miscutils.h>
40 #include <glibmm/fileutils.h>
41
42 #include "evoral/Control.hpp"
43 #include "evoral/SMF.hpp"
44
45 #include "ardour/debug.h"
46 #include "ardour/midi_channel_filter.h"
47 #include "ardour/midi_model.h"
48 #include "ardour/midi_ring_buffer.h"
49 #include "ardour/midi_state_tracker.h"
50 #include "ardour/parameter_types.h"
51 #include "ardour/session.h"
52 #include "ardour/smf_source.h"
53
54 #include "pbd/i18n.h"
55
56 using namespace ARDOUR;
57 using namespace Glib;
58 using namespace PBD;
59 using namespace Evoral;
60 using namespace std;
61
62 /** Constructor used for new internal-to-session files.  File cannot exist. */
63 SMFSource::SMFSource (Session& s, const string& path, Source::Flag flags)
64         : Source(s, DataType::MIDI, path, flags)
65         , MidiSource(s, path, flags)
66         , FileSource(s, DataType::MIDI, path, string(), flags)
67         , Evoral::SMF()
68         , _open (false)
69         , _last_ev_time_beats(0.0)
70         , _last_ev_time_samples(0)
71         , _smf_last_read_end (0)
72         , _smf_last_read_time (0)
73 {
74         /* note that origin remains empty */
75
76         if (init (_path, false)) {
77                 throw failed_constructor ();
78         }
79
80         assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
81         existence_check ();
82
83         _flags = Source::Flag (_flags | Empty);
84
85         /* file is not opened until write */
86
87         if (flags & Writable) {
88                 return;
89         }
90
91         if (open (_path)) {
92                 throw failed_constructor ();
93         }
94
95         _open = true;
96 }
97
98 /** Constructor used for external-to-session files.  File must exist. */
99 SMFSource::SMFSource (Session& s, const string& path)
100         : Source(s, DataType::MIDI, path, Source::Flag (0))
101         , MidiSource(s, path, Source::Flag (0))
102         , FileSource(s, DataType::MIDI, path, string(), Source::Flag (0))
103         , Evoral::SMF()
104         , _open (false)
105         , _last_ev_time_beats(0.0)
106         , _last_ev_time_samples(0)
107         , _smf_last_read_end (0)
108         , _smf_last_read_time (0)
109 {
110         /* note that origin remains empty */
111
112         if (init (_path, true)) {
113                 throw failed_constructor ();
114         }
115
116         assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
117         existence_check ();
118
119         if (_flags & Writable) {
120                 /* file is not opened until write */
121                 return;
122         }
123
124         if (open (_path)) {
125                 throw failed_constructor ();
126         }
127
128         _open = true;
129 }
130
131 /** Constructor used for existing internal-to-session files. */
132 SMFSource::SMFSource (Session& s, const XMLNode& node, bool must_exist)
133         : Source(s, node)
134         , MidiSource(s, node)
135         , FileSource(s, node, must_exist)
136         , _open (false)
137         , _last_ev_time_beats(0.0)
138         , _last_ev_time_samples(0)
139         , _smf_last_read_end (0)
140         , _smf_last_read_time (0)
141 {
142         if (set_state(node, Stateful::loading_state_version)) {
143                 throw failed_constructor ();
144         }
145
146         /* we expect the file to exist, but if no MIDI data was ever added
147            it will have been removed at last session close. so, we don't
148            require it to exist if it was marked Empty.
149         */
150
151         try {
152
153                 if (init (_path, true)) {
154                         throw failed_constructor ();
155                 }
156
157         } catch (MissingSource& err) {
158
159                 if (_flags & Source::Empty) {
160                         /* we don't care that the file was not found, because
161                            it was empty. But FileSource::init() will have
162                            failed to set our _path correctly, so we have to do
163                            this ourselves. Use the first entry in the search
164                            path for MIDI files, which is assumed to be the
165                            correct "main" location.
166                         */
167                         std::vector<string> sdirs = s.source_search_path (DataType::MIDI);
168                         _path = Glib::build_filename (sdirs.front(), _path);
169                         /* This might be important, too */
170                         _file_is_new = true;
171                 } else {
172                         /* pass it on */
173                         throw;
174                 }
175         }
176
177         if (!(_flags & Source::Empty)) {
178                 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
179                 existence_check ();
180         } else {
181                 assert (_flags & Source::Writable);
182                 /* file will be opened on write */
183                 return;
184         }
185
186         if (open (_path)) {
187                 throw failed_constructor ();
188         }
189
190         _open = true;
191 }
192
193 SMFSource::~SMFSource ()
194 {
195         if (removable()) {
196                 ::g_unlink (_path.c_str());
197         }
198 }
199
200 int
201 SMFSource::open_for_write ()
202 {
203         if (create (_path)) {
204                 return -1;
205         }
206         _open = true;
207         return 0;
208 }
209
210 void
211 SMFSource::close ()
212 {
213         /* nothing to do: file descriptor is never kept open */
214 }
215
216 /** All stamps in audio samples */
217 samplecnt_t
218 SMFSource::read_unlocked (const Lock&                    lock,
219                           Evoral::EventSink<samplepos_t>& destination,
220                           samplepos_t const               source_start,
221                           samplepos_t                     start,
222                           samplecnt_t                     duration,
223                           Evoral::Range<samplepos_t>*     loop_range,
224                           MidiStateTracker*              tracker,
225                           MidiChannelFilter*             filter) const
226 {
227         int      ret  = 0;
228         uint64_t time = 0; // in SMF ticks, 1 tick per _ppqn
229
230         if (writable() && !_open) {
231                 /* nothing to read since nothing has ben written */
232                 return duration;
233         }
234
235         DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start %1 duration %2\n", start, duration));
236
237         // Output parameters for read_event (which will allocate scratch in buffer as needed)
238         uint32_t ev_delta_t = 0;
239         uint32_t ev_size    = 0;
240         uint8_t* ev_buffer  = 0;
241
242         size_t scratch_size = 0; // keep track of scratch to minimize reallocs
243
244         BeatsSamplesConverter converter(_session.tempo_map(), source_start);
245
246         const uint64_t start_ticks = converter.from(start).to_ticks();
247         DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start in ticks %1\n", start_ticks));
248
249         if (_smf_last_read_end == 0 || start != _smf_last_read_end) {
250                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: seek to %1\n", start));
251                 Evoral::SMF::seek_to_start();
252                 while (time < start_ticks) {
253                         gint ignored;
254
255                         ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
256                         if (ret == -1) { // EOF
257                                 _smf_last_read_end = start + duration;
258                                 return duration;
259                         }
260                         time += ev_delta_t; // accumulate delta time
261                 }
262         } else {
263                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: set time to %1\n", _smf_last_read_time));
264                 time = _smf_last_read_time;
265         }
266
267         _smf_last_read_end = start + duration;
268
269         while (true) {
270                 gint ignored; /* XXX don't ignore note id's ??*/
271
272                 ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
273                 if (ret == -1) { // EOF
274                         break;
275                 }
276
277                 time += ev_delta_t; // accumulate delta time
278                 _smf_last_read_time = time;
279
280                 if (ret == 0) { // meta-event (skipped, just accumulate time)
281                         continue;
282                 }
283
284                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked delta %1, time %2, buf[0] %3\n",
285                                                                   ev_delta_t, time, ev_buffer[0]));
286
287                 assert(time >= start_ticks);
288
289                 /* Note that we add on the source start time (in session samples) here so that ev_sample_time
290                    is in session samples.
291                 */
292                 const samplepos_t ev_sample_time = converter.to(Temporal::Beats::ticks_at_rate(time, ppqn())) + source_start;
293
294                 if (loop_range) {
295                         loop_range->squish (ev_sample_time);
296                 }
297
298                 if (ev_sample_time < start + duration) {
299                         if (!filter || !filter->filter(ev_buffer, ev_size)) {
300                                 destination.write (ev_sample_time, Evoral::MIDI_EVENT, ev_size, ev_buffer);
301                                 if (tracker) {
302                                         tracker->track(ev_buffer);
303                                 }
304                         }
305                 } else {
306                         break;
307                 }
308
309                 if (ev_size > scratch_size) {
310                         scratch_size = ev_size;
311                 }
312                 ev_size = scratch_size; // ensure read_event only allocates if necessary
313         }
314
315         return duration;
316 }
317
318 samplecnt_t
319 SMFSource::write_unlocked (const Lock&                 lock,
320                            MidiRingBuffer<samplepos_t>& source,
321                            samplepos_t                  position,
322                            samplecnt_t                  cnt)
323 {
324         if (!_writing) {
325                 mark_streaming_write_started (lock);
326         }
327
328         samplepos_t        time;
329         Evoral::EventType type;
330         uint32_t          size;
331
332         size_t   buf_capacity = 4;
333         uint8_t* buf          = (uint8_t*)malloc(buf_capacity);
334
335         if (_model && !_model->writing()) {
336                 _model->start_write();
337         }
338
339         Evoral::Event<samplepos_t> ev;
340         while (true) {
341                 /* Get the event time, in samples since session start but ignoring looping. */
342                 bool ret;
343                 if (!(ret = source.peek ((uint8_t*)&time, sizeof (time)))) {
344                         /* Ring is empty, no more events. */
345                         break;
346                 }
347
348                 if ((cnt != max_samplecnt) &&
349                     (time > position + _capture_length + cnt)) {
350                         /* The diskstream doesn't want us to write everything, and this
351                            event is past the end of this block, so we're done for now. */
352                         break;
353                 }
354
355                 /* Read the time, type, and size of the event. */
356                 if (!(ret = source.read_prefix (&time, &type, &size))) {
357                         error << _("Unable to read event prefix, corrupt MIDI ring") << endmsg;
358                         break;
359                 }
360
361                 /* Enlarge body buffer if necessary now that we know the size. */
362                 if (size > buf_capacity) {
363                         buf_capacity = size;
364                         buf = (uint8_t*)realloc(buf, size);
365                 }
366
367                 /* Read the event body into buffer. */
368                 ret = source.read_contents(size, buf);
369                 if (!ret) {
370                         error << _("Event has time and size but no body, corrupt MIDI ring") << endmsg;
371                         break;
372                 }
373
374                 /* Convert event time from absolute to source relative. */
375                 if (time < position) {
376                         error << _("Event time is before MIDI source position") << endmsg;
377                         break;
378                 }
379                 time -= position;
380
381                 ev.set(buf, size, time);
382                 ev.set_event_type(Evoral::MIDI_EVENT);
383                 ev.set_id(Evoral::next_event_id());
384
385                 if (!(ev.is_channel_event() || ev.is_smf_meta_event() || ev.is_sysex())) {
386                         continue;
387                 }
388
389                 append_event_samples(lock, ev, position);
390         }
391
392         Evoral::SMF::flush ();
393         free (buf);
394
395         return cnt;
396 }
397
398 /** Append an event with a timestamp in beats */
399 void
400 SMFSource::append_event_beats (const Glib::Threads::Mutex::Lock&   lock,
401                                const Evoral::Event<Temporal::Beats>& ev)
402 {
403         if (!_writing || ev.size() == 0)  {
404                 return;
405         }
406
407 #if 0
408         printf("SMFSource: %s - append_event_beats ID = %d time = %lf, size = %u, data = ",
409                name().c_str(), ev.id(), ev.time(), ev.size());
410                for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
411 #endif
412
413         Temporal::Beats time = ev.time();
414         if (time < _last_ev_time_beats) {
415                 const Temporal::Beats difference = _last_ev_time_beats - time;
416                 if (difference.to_double() / (double)ppqn() < 1.0) {
417                         /* Close enough.  This problem occurs because Sequence is not
418                            actually ordered due to fuzzy time comparison.  I'm pretty sure
419                            this is inherently a bad idea which causes problems all over the
420                            place, but tolerate it here for now anyway. */
421                         time = _last_ev_time_beats;
422                 } else {
423                         /* Out of order by more than a tick. */
424                         warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"),
425                                                   ev.time(), _last_ev_time_beats, difference, difference.to_double() / (double)ppqn())
426                                 << endmsg;
427                         return;
428                 }
429         }
430
431         Evoral::event_id_t event_id;
432
433         if (ev.id() < 0) {
434                 event_id  = Evoral::next_event_id();
435         } else {
436                 event_id = ev.id();
437         }
438
439         if (_model) {
440                 _model->append (ev, event_id);
441         }
442
443         _length_beats = max(_length_beats, time);
444
445         const Temporal::Beats delta_time_beats = time - _last_ev_time_beats;
446         const uint32_t      delta_time_ticks = delta_time_beats.to_ticks(ppqn());
447
448         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
449         _last_ev_time_beats = time;
450         _flags = Source::Flag (_flags & ~Empty);
451 }
452
453 /** Append an event with a timestamp in samples (samplepos_t) */
454 void
455 SMFSource::append_event_samples (const Glib::Threads::Mutex::Lock& lock,
456                                 const Evoral::Event<samplepos_t>&  ev,
457                                 samplepos_t                        position)
458 {
459         if (!_writing || ev.size() == 0)  {
460                 return;
461         }
462
463         // printf("SMFSource: %s - append_event_samples ID = %d time = %u, size = %u, data = ",
464         // name().c_str(), ev.id(), ev.time(), ev.size());
465         // for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
466
467         if (ev.time() < _last_ev_time_samples) {
468                 warning << string_compose(_("Skipping event with unordered sample time %1 < %2"),
469                                           ev.time(), _last_ev_time_samples)
470                         << endmsg;
471                 return;
472         }
473
474         BeatsSamplesConverter converter(_session.tempo_map(), position);
475         const Temporal::Beats  ev_time_beats = converter.from(ev.time());
476         Evoral::event_id_t   event_id;
477
478         if (ev.id() < 0) {
479                 event_id  = Evoral::next_event_id();
480         } else {
481                 event_id = ev.id();
482         }
483
484         if (_model) {
485                 const Evoral::Event<Temporal::Beats> beat_ev (ev.event_type(),
486                                                             ev_time_beats,
487                                                             ev.size(),
488                                                             const_cast<uint8_t*>(ev.buffer()));
489                 _model->append (beat_ev, event_id);
490         }
491
492         _length_beats = max(_length_beats, ev_time_beats);
493
494         const Temporal::Beats last_time_beats  = converter.from (_last_ev_time_samples);
495         const Temporal::Beats delta_time_beats = ev_time_beats - last_time_beats;
496         const uint32_t      delta_time_ticks = delta_time_beats.to_ticks(ppqn());
497
498         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
499         _last_ev_time_samples = ev.time();
500         _flags = Source::Flag (_flags & ~Empty);
501 }
502
503 XMLNode&
504 SMFSource::get_state ()
505 {
506         XMLNode& node = MidiSource::get_state();
507         node.set_property (X_("origin"), _origin);
508         return node;
509 }
510
511 int
512 SMFSource::set_state (const XMLNode& node, int version)
513 {
514         if (Source::set_state (node, version)) {
515                 return -1;
516         }
517
518         if (MidiSource::set_state (node, version)) {
519                 return -1;
520         }
521
522         if (FileSource::set_state (node, version)) {
523                 return -1;
524         }
525
526         return 0;
527 }
528
529 void
530 SMFSource::mark_streaming_midi_write_started (const Lock& lock, NoteMode mode)
531 {
532         if (!_open && open_for_write()) {
533                 error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg;
534                 /* XXX should probably throw or return something */
535                 return;
536         }
537
538         MidiSource::mark_streaming_midi_write_started (lock, mode);
539         Evoral::SMF::begin_write ();
540         _last_ev_time_beats  = Temporal::Beats();
541         _last_ev_time_samples = 0;
542 }
543
544 void
545 SMFSource::mark_streaming_write_completed (const Lock& lock)
546 {
547         mark_midi_streaming_write_completed (lock, Evoral::Sequence<Temporal::Beats>::DeleteStuckNotes);
548 }
549
550 void
551 SMFSource::mark_midi_streaming_write_completed (const Lock& lm, Evoral::Sequence<Temporal::Beats>::StuckNoteOption stuck_notes_option, Temporal::Beats when)
552 {
553         MidiSource::mark_midi_streaming_write_completed (lm, stuck_notes_option, when);
554
555         if (!writable()) {
556                 warning << string_compose ("attempt to write to unwritable SMF file %1", _path) << endmsg;
557                 return;
558         }
559
560         if (_model) {
561                 _model->set_edited(false);
562         }
563
564         try {
565                 Evoral::SMF::end_write (_path);
566         } catch (std::exception & e) {
567                 error << string_compose (_("Exception while writing %1, file may be corrupt/unusable"), _path) << endmsg;
568         }
569
570         /* data in the file now, not removable */
571
572         mark_nonremovable ();
573 }
574
575 bool
576 SMFSource::valid_midi_file (const string& file)
577 {
578         if (safe_midi_file_extension (file) ) {
579                 return (SMF::test (file) );
580         }
581         return false;
582 }
583
584 bool
585 SMFSource::safe_midi_file_extension (const string& file)
586 {
587         static regex_t compiled_pattern;
588         static bool compile = true;
589         const int nmatches = 2;
590         regmatch_t matches[nmatches];
591
592         if (Glib::file_test (file, Glib::FILE_TEST_EXISTS)) {
593                 if (!Glib::file_test (file, Glib::FILE_TEST_IS_REGULAR)) {
594                         /* exists but is not a regular file */
595                         return false;
596                 }
597         }
598
599         if (compile && regcomp (&compiled_pattern, "\\.[mM][iI][dD][iI]?$", REG_EXTENDED)) {
600                 return false;
601         } else {
602                 compile = false;
603         }
604
605         if (regexec (&compiled_pattern, file.c_str(), nmatches, matches, 0)) {
606                 return false;
607         }
608
609         return true;
610 }
611
612 static bool compare_eventlist (
613         const std::pair< const Evoral::Event<Temporal::Beats>*, gint >& a,
614         const std::pair< const Evoral::Event<Temporal::Beats>*, gint >& b) {
615         return ( a.first->time() < b.first->time() );
616 }
617
618 void
619 SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload)
620 {
621         if (_writing) {
622                 return;
623         }
624
625         if (_model && !force_reload) {
626                 return;
627         }
628
629         if (!_model) {
630                 boost::shared_ptr<SMFSource> smf = boost::dynamic_pointer_cast<SMFSource> ( shared_from_this () );
631                 _model = boost::shared_ptr<MidiModel> (new MidiModel (smf));
632         } else {
633                 _model->clear();
634         }
635
636         invalidate(lock);
637
638         if (writable() && !_open) {
639                 return;
640         }
641
642         _model->start_write();
643         Evoral::SMF::seek_to_start();
644
645         uint64_t time = 0; /* in SMF ticks */
646         Evoral::Event<Temporal::Beats> ev;
647
648         uint32_t scratch_size = 0; // keep track of scratch and minimize reallocs
649
650         uint32_t delta_t = 0;
651         uint32_t size    = 0;
652         uint8_t* buf     = NULL;
653         int ret;
654         gint event_id;
655         bool have_event_id;
656
657         // TODO simplify event allocation
658         std::list< std::pair< Evoral::Event<Temporal::Beats>*, gint > > eventlist;
659
660         for (unsigned i = 1; i <= num_tracks(); ++i) {
661                 if (seek_to_track(i)) continue;
662
663                 time = 0;
664                 have_event_id = false;
665
666                 while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
667
668                         time += delta_t;
669
670                         if (ret == 0) {
671                                 /* meta-event : did we get an event ID ?  */
672                                 if (event_id >= 0) {
673                                         have_event_id = true;
674                                 }
675                                 continue;
676                         }
677
678                         if (ret > 0) {
679                                 /* not a meta-event */
680
681                                 if (!have_event_id) {
682                                         event_id = Evoral::next_event_id();
683                                 }
684                                 const Temporal::Beats event_time = Temporal::Beats::ticks_at_rate(time, ppqn());
685 #ifndef NDEBUG
686                                 std::string ss;
687
688                                 for (uint32_t xx = 0; xx < size; ++xx) {
689                                         char b[8];
690                                         snprintf (b, sizeof (b), "0x%x ", buf[xx]);
691                                         ss += b;
692                                 }
693
694                                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF %7 load model delta %1, time %2, size %3 buf %4, id %6\n",
695                                                         delta_t, time, size, ss, event_id, name()));
696 #endif
697
698                                 eventlist.push_back(make_pair (
699                                                         new Evoral::Event<Temporal::Beats> (
700                                                                 Evoral::MIDI_EVENT, event_time,
701                                                                 size, buf, true)
702                                                         , event_id));
703
704                                 // Set size to max capacity to minimize allocs in read_event
705                                 scratch_size = std::max(size, scratch_size);
706                                 size = scratch_size;
707
708                                 _length_beats = max(_length_beats, event_time);
709                         }
710
711                         /* event ID's must immediately precede the event they are for */
712                         have_event_id = false;
713                 }
714         }
715
716         eventlist.sort(compare_eventlist);
717
718         std::list< std::pair< Evoral::Event<Temporal::Beats>*, gint > >::iterator it;
719         for (it=eventlist.begin(); it!=eventlist.end(); ++it) {
720                 _model->append (*it->first, it->second);
721                 delete it->first;
722         }
723
724         // cerr << "----SMF-SRC-----\n";
725         // _playback_buf->dump (cerr);
726         // cerr << "----------------\n";
727
728         _model->end_write (Evoral::Sequence<Temporal::Beats>::ResolveStuckNotes, _length_beats);
729         _model->set_edited (false);
730         invalidate(lock);
731
732         free(buf);
733 }
734
735 void
736 SMFSource::destroy_model (const Glib::Threads::Mutex::Lock& lock)
737 {
738         //cerr << _name << " destroying model " << _model.get() << endl;
739         _model.reset();
740         invalidate(lock);
741 }
742
743 void
744 SMFSource::flush_midi (const Lock& lock)
745 {
746         if (!writable() || _length_beats == 0.0) {
747                 return;
748         }
749
750         ensure_disk_file (lock);
751
752         Evoral::SMF::end_write (_path);
753         /* data in the file means its no longer removable */
754         mark_nonremovable ();
755
756         invalidate(lock);
757 }
758
759 void
760 SMFSource::set_path (const string& p)
761 {
762         FileSource::set_path (p);
763 }
764
765 /** Ensure that this source has some file on disk, even if it's just a SMF header */
766 void
767 SMFSource::ensure_disk_file (const Lock& lock)
768 {
769         if (!writable()) {
770                 return;
771         }
772
773         if (_model) {
774                 /* We have a model, so write it to disk; see MidiSource::session_saved
775                    for an explanation of what we are doing here.
776                 */
777                 boost::shared_ptr<MidiModel> mm = _model;
778                 _model.reset ();
779                 mm->sync_to_source (lock);
780                 _model = mm;
781                 invalidate(lock);
782         } else {
783                 /* No model; if it's not already open, it's an empty source, so create
784                    and open it for writing.
785                 */
786                 if (!_open) {
787                         open_for_write ();
788                 }
789         }
790 }
791
792 void
793 SMFSource::prevent_deletion ()
794 {
795         /* Unlike the audio case, the MIDI file remains mutable (because we can
796            edit MIDI data)
797         */
798
799         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
800 }