Remove _length_pulse from MidiSource.
[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_frames(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_frames(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_frames(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 frames */
212 framecnt_t
213 SMFSource::read_unlocked (const Lock&                    lock,
214                           Evoral::EventSink<framepos_t>& destination,
215                           framepos_t const               source_start,
216                           framepos_t                     start,
217                           framecnt_t                     duration,
218                           Evoral::Range<framepos_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_type    = 0;
235         uint32_t ev_size    = 0;
236         uint8_t* ev_buffer  = 0;
237
238         size_t scratch_size = 0; // keep track of scratch to minimize reallocs
239
240         BeatsFramesConverter converter(_session.tempo_map(), source_start);
241
242         const uint64_t start_ticks = converter.from(start).to_ticks();
243         DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start in ticks %1\n", start_ticks));
244
245         if (_smf_last_read_end == 0 || start != _smf_last_read_end) {
246                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: seek to %1\n", start));
247                 Evoral::SMF::seek_to_start();
248                 while (time < start_ticks) {
249                         gint ignored;
250
251                         ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
252                         if (ret == -1) { // EOF
253                                 _smf_last_read_end = start + duration;
254                                 return duration;
255                         }
256                         time += ev_delta_t; // accumulate delta time
257                 }
258         } else {
259                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: set time to %1\n", _smf_last_read_time));
260                 time = _smf_last_read_time;
261         }
262
263         _smf_last_read_end = start + duration;
264
265         while (true) {
266                 gint ignored; /* XXX don't ignore note id's ??*/
267
268                 ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
269                 if (ret == -1) { // EOF
270                         break;
271                 }
272
273                 time += ev_delta_t; // accumulate delta time
274                 _smf_last_read_time = time;
275
276                 if (ret == 0) { // meta-event (skipped, just accumulate time)
277                         continue;
278                 }
279
280                 ev_type = midi_parameter_type(ev_buffer[0]);
281
282                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked delta %1, time %2, buf[0] %3, type %4\n",
283                                                                   ev_delta_t, time, ev_buffer[0], ev_type));
284
285                 assert(time >= start_ticks);
286
287                 /* Note that we add on the source start time (in session frames) here so that ev_frame_time
288                    is in session frames.
289                 */
290                 const framepos_t ev_frame_time = converter.to(Evoral::Beats::ticks_at_rate(time, ppqn())) + source_start;
291
292                 if (loop_range) {
293                         loop_range->squish (ev_frame_time);
294                 }
295
296                 if (ev_frame_time < start + duration) {
297                         if (!filter || !filter->filter(ev_buffer, ev_size)) {
298                                 destination.write (ev_frame_time, ev_type, ev_size, ev_buffer);
299                                 if (tracker) {
300                                         tracker->track(ev_buffer);
301                                 }
302                         }
303                 } else {
304                         break;
305                 }
306
307                 if (ev_size > scratch_size) {
308                         scratch_size = ev_size;
309                 }
310                 ev_size = scratch_size; // ensure read_event only allocates if necessary
311         }
312
313         return duration;
314 }
315
316 framecnt_t
317 SMFSource::write_unlocked (const Lock&                 lock,
318                            MidiRingBuffer<framepos_t>& source,
319                            framepos_t                  position,
320                            framecnt_t                  cnt)
321 {
322         if (!_writing) {
323                 mark_streaming_write_started (lock);
324         }
325
326         framepos_t        time;
327         Evoral::EventType type;
328         uint32_t          size;
329
330         size_t   buf_capacity = 4;
331         uint8_t* buf          = (uint8_t*)malloc(buf_capacity);
332
333         if (_model && !_model->writing()) {
334                 _model->start_write();
335         }
336
337         Evoral::MIDIEvent<framepos_t> ev;
338         while (true) {
339                 /* Get the event time, in frames since session start but ignoring looping. */
340                 bool ret;
341                 if (!(ret = source.peek ((uint8_t*)&time, sizeof (time)))) {
342                         /* Ring is empty, no more events. */
343                         break;
344                 }
345
346                 if ((cnt != max_framecnt) &&
347                     (time > position + _capture_length + cnt)) {
348                         /* The diskstream doesn't want us to write everything, and this
349                            event is past the end of this block, so we're done for now. */
350                         break;
351                 }
352
353                 /* Read the time, type, and size of the event. */
354                 if (!(ret = source.read_prefix (&time, &type, &size))) {
355                         error << _("Unable to read event prefix, corrupt MIDI ring") << endmsg;
356                         break;
357                 }
358
359                 /* Enlarge body buffer if necessary now that we know the size. */
360                 if (size > buf_capacity) {
361                         buf_capacity = size;
362                         buf = (uint8_t*)realloc(buf, size);
363                 }
364
365                 /* Read the event body into buffer. */
366                 ret = source.read_contents(size, buf);
367                 if (!ret) {
368                         error << _("Event has time and size but no body, corrupt MIDI ring") << endmsg;
369                         break;
370                 }
371
372                 /* Convert event time from absolute to source relative. */
373                 if (time < position) {
374                         error << _("Event time is before MIDI source position") << endmsg;
375                         break;
376                 }
377                 time -= position;
378
379                 ev.set(buf, size, time);
380                 ev.set_event_type(midi_parameter_type(ev.buffer()[0]));
381                 ev.set_id(Evoral::next_event_id());
382
383                 if (!(ev.is_channel_event() || ev.is_smf_meta_event() || ev.is_sysex())) {
384                         continue;
385                 }
386
387                 append_event_frames(lock, ev, position);
388         }
389
390         Evoral::SMF::flush ();
391         free (buf);
392
393         return cnt;
394 }
395
396 /** Append an event with a timestamp in beats */
397 void
398 SMFSource::append_event_beats (const Glib::Threads::Mutex::Lock&   lock,
399                                const Evoral::Event<Evoral::Beats>& ev)
400 {
401         if (!_writing || ev.size() == 0)  {
402                 return;
403         }
404
405 #if 0
406         printf("SMFSource: %s - append_event_beats ID = %d time = %lf, size = %u, data = ",
407                name().c_str(), ev.id(), ev.time(), ev.size());
408                for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
409 #endif
410
411         Evoral::Beats time = ev.time();
412         if (time < _last_ev_time_beats) {
413                 const Evoral::Beats difference = _last_ev_time_beats - time;
414                 if (difference.to_double() / (double)ppqn() < 1.0) {
415                         /* Close enough.  This problem occurs because Sequence is not
416                            actually ordered due to fuzzy time comparison.  I'm pretty sure
417                            this is inherently a bad idea which causes problems all over the
418                            place, but tolerate it here for now anyway. */
419                         time = _last_ev_time_beats;
420                 } else {
421                         /* Out of order by more than a tick. */
422                         warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"),
423                                                   ev.time(), _last_ev_time_beats, difference, difference.to_double() / (double)ppqn())
424                                 << endmsg;
425                         return;
426                 }
427         }
428
429         Evoral::event_id_t event_id;
430
431         if (ev.id() < 0) {
432                 event_id  = Evoral::next_event_id();
433         } else {
434                 event_id = ev.id();
435         }
436
437         if (_model) {
438                 _model->append (ev, event_id);
439         }
440
441         _length_beats = max(_length_beats, time);
442
443         const Evoral::Beats delta_time_beats = time - _last_ev_time_beats;
444         const uint32_t      delta_time_ticks = delta_time_beats.to_ticks(ppqn());
445
446         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
447         _last_ev_time_beats = time;
448         _flags = Source::Flag (_flags & ~Empty);
449 }
450
451 /** Append an event with a timestamp in frames (framepos_t) */
452 void
453 SMFSource::append_event_frames (const Glib::Threads::Mutex::Lock& lock,
454                                 const Evoral::Event<framepos_t>&  ev,
455                                 framepos_t                        position)
456 {
457         if (!_writing || ev.size() == 0)  {
458                 return;
459         }
460
461         // printf("SMFSource: %s - append_event_frames ID = %d time = %u, size = %u, data = ",
462         // name().c_str(), ev.id(), ev.time(), ev.size());
463         // for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
464
465         if (ev.time() < _last_ev_time_frames) {
466                 warning << string_compose(_("Skipping event with unordered frame time %1 < %2"),
467                                           ev.time(), _last_ev_time_frames)
468                         << endmsg;
469                 return;
470         }
471
472         BeatsFramesConverter converter(_session.tempo_map(), position);
473         const Evoral::Beats  ev_time_beats = converter.from(ev.time());
474         Evoral::event_id_t   event_id;
475
476         if (ev.id() < 0) {
477                 event_id  = Evoral::next_event_id();
478         } else {
479                 event_id = ev.id();
480         }
481
482         if (_model) {
483                 const Evoral::Event<Evoral::Beats> beat_ev (ev.event_type(),
484                                                             ev_time_beats,
485                                                             ev.size(),
486                                                             const_cast<uint8_t*>(ev.buffer()));
487                 _model->append (beat_ev, event_id);
488         }
489
490         _length_beats = max(_length_beats, ev_time_beats);
491
492         const Evoral::Beats last_time_beats  = converter.from (_last_ev_time_frames);
493         const Evoral::Beats delta_time_beats = ev_time_beats - last_time_beats;
494         const uint32_t      delta_time_ticks = delta_time_beats.to_ticks(ppqn());
495
496         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
497         _last_ev_time_frames = ev.time();
498         _flags = Source::Flag (_flags & ~Empty);
499 }
500
501 XMLNode&
502 SMFSource::get_state ()
503 {
504         XMLNode& node = MidiSource::get_state();
505         node.add_property (X_("origin"), _origin);
506         return node;
507 }
508
509 int
510 SMFSource::set_state (const XMLNode& node, int version)
511 {
512         if (Source::set_state (node, version)) {
513                 return -1;
514         }
515
516         if (MidiSource::set_state (node, version)) {
517                 return -1;
518         }
519
520         if (FileSource::set_state (node, version)) {
521                 return -1;
522         }
523
524         return 0;
525 }
526
527 void
528 SMFSource::mark_streaming_midi_write_started (const Lock& lock, NoteMode mode)
529 {
530         if (!_open && open_for_write()) {
531                 error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg;
532                 /* XXX should probably throw or return something */
533                 return;
534         }
535
536         MidiSource::mark_streaming_midi_write_started (lock, mode);
537         Evoral::SMF::begin_write ();
538         _last_ev_time_beats  = Evoral::Beats();
539         _last_ev_time_frames = 0;
540 }
541
542 void
543 SMFSource::mark_streaming_write_completed (const Lock& lock)
544 {
545         mark_midi_streaming_write_completed (lock, Evoral::Sequence<Evoral::Beats>::DeleteStuckNotes);
546 }
547
548 void
549 SMFSource::mark_midi_streaming_write_completed (const Lock& lm, Evoral::Sequence<Evoral::Beats>::StuckNoteOption stuck_notes_option, Evoral::Beats when)
550 {
551         MidiSource::mark_midi_streaming_write_completed (lm, stuck_notes_option, when);
552
553         if (!writable()) {
554                 warning << string_compose ("attempt to write to unwritable SMF file %1", _path) << endmsg;
555                 return;
556         }
557
558         if (_model) {
559                 _model->set_edited(false);
560         }
561
562         Evoral::SMF::end_write (_path);
563
564         /* data in the file now, not removable */
565
566         mark_nonremovable ();
567 }
568
569 bool
570 SMFSource::valid_midi_file (const string& file)
571 {
572         if (safe_midi_file_extension (file) ) {
573                 return (SMF::test (file) );
574         }
575         return false;
576 }
577
578 bool
579 SMFSource::safe_midi_file_extension (const string& file)
580 {
581         static regex_t compiled_pattern;
582         static bool compile = true;
583         const int nmatches = 2;
584         regmatch_t matches[nmatches];
585
586         if (Glib::file_test (file, Glib::FILE_TEST_EXISTS)) {
587                 if (!Glib::file_test (file, Glib::FILE_TEST_IS_REGULAR)) {
588                         /* exists but is not a regular file */
589                         return false;
590                 }
591         }
592
593         if (compile && regcomp (&compiled_pattern, "\\.[mM][iI][dD][iI]?$", REG_EXTENDED)) {
594                 return false;
595         } else {
596                 compile = false;
597         }
598
599         if (regexec (&compiled_pattern, file.c_str(), nmatches, matches, 0)) {
600                 return false;
601         }
602
603         return true;
604 }
605
606 static bool compare_eventlist (
607         const std::pair< Evoral::Event<Evoral::Beats>*, gint >& a,
608         const std::pair< Evoral::Event<Evoral::Beats>*, gint >& b) {
609         return ( a.first->time() < b.first->time() );
610 }
611
612 void
613 SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload)
614 {
615         if (_writing) {
616                 return;
617         }
618
619         if (_model && !force_reload) {
620                 return;
621         }
622
623         if (!_model) {
624                 _model = boost::shared_ptr<MidiModel> (new MidiModel (shared_from_this ()));
625         } else {
626                 _model->clear();
627         }
628
629         invalidate(lock);
630
631         if (writable() && !_open) {
632                 return;
633         }
634
635         _model->start_write();
636         Evoral::SMF::seek_to_start();
637
638         uint64_t time = 0; /* in SMF ticks */
639         Evoral::Event<Evoral::Beats> ev;
640
641         uint32_t scratch_size = 0; // keep track of scratch and minimize reallocs
642
643         uint32_t delta_t = 0;
644         uint32_t size    = 0;
645         uint8_t* buf     = NULL;
646         int ret;
647         gint event_id;
648         bool have_event_id;
649
650         // TODO simplify event allocation
651         std::list< std::pair< Evoral::Event<Evoral::Beats>*, gint > > eventlist;
652
653         for (unsigned i = 1; i <= num_tracks(); ++i) {
654                 if (seek_to_track(i)) continue;
655
656                 time = 0;
657                 have_event_id = false;
658
659                 while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
660
661                         time += delta_t;
662
663                         if (ret == 0) {
664                                 /* meta-event : did we get an event ID ?  */
665                                 if (event_id >= 0) {
666                                         have_event_id = true;
667                                 }
668                                 continue;
669                         }
670
671                         if (ret > 0) {
672                                 /* not a meta-event */
673
674                                 if (!have_event_id) {
675                                         event_id = Evoral::next_event_id();
676                                 }
677                                 const uint32_t            event_type = midi_parameter_type(buf[0]);
678                                 const Evoral::Beats event_time = Evoral::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, type %5 id %6\n",
689                                                         delta_t, time, size, ss , event_type, event_id, name()));
690 #endif
691
692                                 eventlist.push_back(make_pair (
693                                                         new Evoral::Event<Evoral::Beats> (
694                                                                 event_type, 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<Evoral::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<Evoral::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 }