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