Add length_pulse to MidiSource, usr quarter-notes in midi_read().
[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                           MidiStateTracker*              tracker,
219                           MidiChannelFilter*             filter) const
220 {
221         int      ret  = 0;
222         uint64_t time = 0; // in SMF ticks, 1 tick per _ppqn
223
224         if (writable() && !_open) {
225                 /* nothing to read since nothing has ben written */
226                 return duration;
227         }
228
229         DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start %1 duration %2\n", start, duration));
230
231         // Output parameters for read_event (which will allocate scratch in buffer as needed)
232         uint32_t ev_delta_t = 0;
233         uint32_t ev_type    = 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         BeatsFramesConverter 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                 ev_type = midi_parameter_type(ev_buffer[0]);
280
281                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked delta %1, time %2, buf[0] %3, type %4\n",
282                                                                   ev_delta_t, time, ev_buffer[0], ev_type));
283
284                 assert(time >= start_ticks);
285
286                 /* Note that we add on the source start time (in session frames) here so that ev_frame_time
287                    is in session frames.
288                 */
289                 const framepos_t ev_frame_time = converter.to(Evoral::Beats::ticks_at_rate(time, ppqn())) + source_start;
290
291                 if (ev_frame_time < start + duration) {
292                         if (!filter || !filter->filter(ev_buffer, ev_size)) {
293                                 destination.write (ev_frame_time, ev_type, ev_size, ev_buffer);
294                                 if (tracker) {
295                                         tracker->track(ev_buffer);
296                                 }
297                         }
298                 } else {
299                         break;
300                 }
301
302                 if (ev_size > scratch_size) {
303                         scratch_size = ev_size;
304                 }
305                 ev_size = scratch_size; // ensure read_event only allocates if necessary
306         }
307
308         return duration;
309 }
310
311 framecnt_t
312 SMFSource::write_unlocked (const Lock&                 lock,
313                            MidiRingBuffer<framepos_t>& source,
314                            framepos_t                  position,
315                            framecnt_t                  cnt)
316 {
317         if (!_writing) {
318                 mark_streaming_write_started (lock);
319         }
320
321         framepos_t        time;
322         Evoral::EventType type;
323         uint32_t          size;
324
325         size_t   buf_capacity = 4;
326         uint8_t* buf          = (uint8_t*)malloc(buf_capacity);
327
328         if (_model && !_model->writing()) {
329                 _model->start_write();
330         }
331
332         Evoral::MIDIEvent<framepos_t> ev;
333         while (true) {
334                 /* Get the event time, in frames since session start but ignoring looping. */
335                 bool ret;
336                 if (!(ret = source.peek ((uint8_t*)&time, sizeof (time)))) {
337                         /* Ring is empty, no more events. */
338                         break;
339                 }
340
341                 if ((cnt != max_framecnt) &&
342                     (time > position + _capture_length + cnt)) {
343                         /* The diskstream doesn't want us to write everything, and this
344                            event is past the end of this block, so we're done for now. */
345                         break;
346                 }
347
348                 /* Read the time, type, and size of the event. */
349                 if (!(ret = source.read_prefix (&time, &type, &size))) {
350                         error << _("Unable to read event prefix, corrupt MIDI ring") << endmsg;
351                         break;
352                 }
353
354                 /* Enlarge body buffer if necessary now that we know the size. */
355                 if (size > buf_capacity) {
356                         buf_capacity = size;
357                         buf = (uint8_t*)realloc(buf, size);
358                 }
359
360                 /* Read the event body into buffer. */
361                 ret = source.read_contents(size, buf);
362                 if (!ret) {
363                         error << _("Event has time and size but no body, corrupt MIDI ring") << endmsg;
364                         break;
365                 }
366
367                 /* Convert event time from absolute to source relative. */
368                 if (time < position) {
369                         error << _("Event time is before MIDI source position") << endmsg;
370                         break;
371                 }
372                 time -= position;
373
374                 ev.set(buf, size, time);
375                 ev.set_event_type(midi_parameter_type(ev.buffer()[0]));
376                 ev.set_id(Evoral::next_event_id());
377
378                 if (!(ev.is_channel_event() || ev.is_smf_meta_event() || ev.is_sysex())) {
379                         continue;
380                 }
381
382                 append_event_frames(lock, ev, position);
383         }
384
385         Evoral::SMF::flush ();
386         free (buf);
387
388         return cnt;
389 }
390
391 /** Append an event with a timestamp in beats */
392 void
393 SMFSource::append_event_beats (const Glib::Threads::Mutex::Lock&   lock,
394                                const Evoral::Event<Evoral::Beats>& ev)
395 {
396         if (!_writing || ev.size() == 0)  {
397                 return;
398         }
399
400 #if 0
401         printf("SMFSource: %s - append_event_beats ID = %d time = %lf, size = %u, data = ",
402                name().c_str(), ev.id(), ev.time(), ev.size());
403                for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
404 #endif
405
406         Evoral::Beats time = ev.time();
407         if (time < _last_ev_time_beats) {
408                 const Evoral::Beats difference = _last_ev_time_beats - time;
409                 if (difference.to_double() / (double)ppqn() < 1.0) {
410                         /* Close enough.  This problem occurs because Sequence is not
411                            actually ordered due to fuzzy time comparison.  I'm pretty sure
412                            this is inherently a bad idea which causes problems all over the
413                            place, but tolerate it here for now anyway. */
414                         time = _last_ev_time_beats;
415                 } else {
416                         /* Out of order by more than a tick. */
417                         warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"),
418                                                   ev.time(), _last_ev_time_beats, difference, difference.to_double() / (double)ppqn())
419                                 << endmsg;
420                         return;
421                 }
422         }
423
424         Evoral::event_id_t event_id;
425
426         if (ev.id() < 0) {
427                 event_id  = Evoral::next_event_id();
428         } else {
429                 event_id = ev.id();
430         }
431
432         if (_model) {
433                 _model->append (ev, event_id);
434         }
435
436         _length_beats = max(_length_beats, time);
437         /* midi is in quarter note format as distinct from ardour beat */
438         _length_pulse = _length_beats.to_double() / 4.0;
439
440         const Evoral::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 frames (framepos_t) */
449 void
450 SMFSource::append_event_frames (const Glib::Threads::Mutex::Lock& lock,
451                                 const Evoral::Event<framepos_t>&  ev,
452                                 framepos_t                        position)
453 {
454         if (!_writing || ev.size() == 0)  {
455                 return;
456         }
457
458         // printf("SMFSource: %s - append_event_frames 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_frames) {
463                 warning << string_compose(_("Skipping event with unordered frame time %1 < %2"),
464                                           ev.time(), _last_ev_time_frames)
465                         << endmsg;
466                 return;
467         }
468
469         BeatsFramesConverter converter(_session.tempo_map(), position);
470         const Evoral::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<Evoral::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         /* midi is in quarter note format as distinct from ardour beat */
489         _length_pulse = _length_beats.to_double() / 4.0;
490
491         const Evoral::Beats last_time_beats  = converter.from (_last_ev_time_frames);
492         const Evoral::Beats delta_time_beats = ev_time_beats - last_time_beats;
493         const uint32_t      delta_time_ticks = delta_time_beats.to_ticks(ppqn());
494
495         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
496         _last_ev_time_frames = ev.time();
497         _flags = Source::Flag (_flags & ~Empty);
498 }
499
500 XMLNode&
501 SMFSource::get_state ()
502 {
503         XMLNode& node = MidiSource::get_state();
504         node.add_property (X_("origin"), _origin);
505         return node;
506 }
507
508 int
509 SMFSource::set_state (const XMLNode& node, int version)
510 {
511         if (Source::set_state (node, version)) {
512                 return -1;
513         }
514
515         if (MidiSource::set_state (node, version)) {
516                 return -1;
517         }
518
519         if (FileSource::set_state (node, version)) {
520                 return -1;
521         }
522
523         return 0;
524 }
525
526 void
527 SMFSource::mark_streaming_midi_write_started (const Lock& lock, NoteMode mode)
528 {
529         if (!_open && open_for_write()) {
530                 error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg;
531                 /* XXX should probably throw or return something */
532                 return;
533         }
534
535         MidiSource::mark_streaming_midi_write_started (lock, mode);
536         Evoral::SMF::begin_write ();
537         _last_ev_time_beats  = Evoral::Beats();
538         _last_ev_time_frames = 0;
539 }
540
541 void
542 SMFSource::mark_streaming_write_completed (const Lock& lock)
543 {
544         mark_midi_streaming_write_completed (lock, Evoral::Sequence<Evoral::Beats>::DeleteStuckNotes);
545 }
546
547 void
548 SMFSource::mark_midi_streaming_write_completed (const Lock& lm, Evoral::Sequence<Evoral::Beats>::StuckNoteOption stuck_notes_option, Evoral::Beats when)
549 {
550         MidiSource::mark_midi_streaming_write_completed (lm, stuck_notes_option, when);
551
552         if (!writable()) {
553                 warning << string_compose ("attempt to write to unwritable SMF file %1", _path) << endmsg;
554                 return;
555         }
556
557         if (_model) {
558                 _model->set_edited(false);
559         }
560
561         Evoral::SMF::end_write (_path);
562
563         /* data in the file now, not removable */
564
565         mark_nonremovable ();
566 }
567
568 bool
569 SMFSource::valid_midi_file (const string& file)
570 {
571         if (safe_midi_file_extension (file) ) {
572                 return (SMF::test (file) );
573         }
574         return false;
575 }
576
577 bool
578 SMFSource::safe_midi_file_extension (const string& file)
579 {
580         static regex_t compiled_pattern;
581         static bool compile = true;
582         const int nmatches = 2;
583         regmatch_t matches[nmatches];
584
585         if (Glib::file_test (file, Glib::FILE_TEST_EXISTS)) {
586                 if (!Glib::file_test (file, Glib::FILE_TEST_IS_REGULAR)) {
587                         /* exists but is not a regular file */
588                         return false;
589                 }
590         }
591
592         if (compile && regcomp (&compiled_pattern, "\\.[mM][iI][dD][iI]?$", REG_EXTENDED)) {
593                 return false;
594         } else {
595                 compile = false;
596         }
597
598         if (regexec (&compiled_pattern, file.c_str(), nmatches, matches, 0)) {
599                 return false;
600         }
601
602         return true;
603 }
604
605 static bool compare_eventlist (
606         const std::pair< Evoral::Event<Evoral::Beats>*, gint >& a,
607         const std::pair< Evoral::Event<Evoral::Beats>*, gint >& b) {
608         return ( a.first->time() < b.first->time() );
609 }
610
611 void
612 SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload)
613 {
614         if (_writing) {
615                 return;
616         }
617
618         if (_model && !force_reload) {
619                 return;
620         }
621
622         if (!_model) {
623                 _model = boost::shared_ptr<MidiModel> (new MidiModel (shared_from_this ()));
624         } else {
625                 _model->clear();
626         }
627
628         invalidate(lock);
629
630         if (writable() && !_open) {
631                 return;
632         }
633
634         _model->start_write();
635         Evoral::SMF::seek_to_start();
636
637         uint64_t time = 0; /* in SMF ticks */
638         Evoral::Event<Evoral::Beats> ev;
639
640         uint32_t scratch_size = 0; // keep track of scratch and minimize reallocs
641
642         uint32_t delta_t = 0;
643         uint32_t size    = 0;
644         uint8_t* buf     = NULL;
645         int ret;
646         gint event_id;
647         bool have_event_id;
648
649         // TODO simplify event allocation
650         std::list< std::pair< Evoral::Event<Evoral::Beats>*, gint > > eventlist;
651
652         for (unsigned i = 1; i <= num_tracks(); ++i) {
653                 if (seek_to_track(i)) continue;
654
655                 time = 0;
656                 have_event_id = false;
657
658                 while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
659
660                         time += delta_t;
661
662                         if (ret == 0) {
663                                 /* meta-event : did we get an event ID ?  */
664                                 if (event_id >= 0) {
665                                         have_event_id = true;
666                                 }
667                                 continue;
668                         }
669
670                         if (ret > 0) {
671                                 /* not a meta-event */
672
673                                 if (!have_event_id) {
674                                         event_id = Evoral::next_event_id();
675                                 }
676                                 const uint32_t            event_type = midi_parameter_type(buf[0]);
677                                 const Evoral::Beats event_time = Evoral::Beats::ticks_at_rate(time, ppqn());
678 #ifndef NDEBUG
679                                 std::string ss;
680
681                                 for (uint32_t xx = 0; xx < size; ++xx) {
682                                         char b[8];
683                                         snprintf (b, sizeof (b), "0x%x ", buf[xx]);
684                                         ss += b;
685                                 }
686
687                                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF %7 load model delta %1, time %2, size %3 buf %4, type %5 id %6\n",
688                                                         delta_t, time, size, ss , event_type, event_id, name()));
689 #endif
690
691                                 eventlist.push_back(make_pair (
692                                                         new Evoral::Event<Evoral::Beats> (
693                                                                 event_type, event_time,
694                                                                 size, buf, true)
695                                                         , event_id));
696
697                                 // Set size to max capacity to minimize allocs in read_event
698                                 scratch_size = std::max(size, scratch_size);
699                                 size = scratch_size;
700
701                                 _length_beats = max(_length_beats, event_time);
702                                 /* midi is in quarter note format as distinct from ardour beat */
703                                 _length_pulse = _length_beats.to_double() / 4.0;
704                         }
705
706                         /* event ID's must immediately precede the event they are for */
707                         have_event_id = false;
708                 }
709         }
710
711         eventlist.sort(compare_eventlist);
712
713         std::list< std::pair< Evoral::Event<Evoral::Beats>*, gint > >::iterator it;
714         for (it=eventlist.begin(); it!=eventlist.end(); ++it) {
715                 _model->append (*it->first, it->second);
716                 delete it->first;
717         }
718
719         // cerr << "----SMF-SRC-----\n";
720         // _playback_buf->dump (cerr);
721         // cerr << "----------------\n";
722
723         _model->end_write (Evoral::Sequence<Evoral::Beats>::ResolveStuckNotes, _length_beats);
724         _model->set_edited (false);
725         invalidate(lock);
726
727         free(buf);
728 }
729
730 void
731 SMFSource::destroy_model (const Glib::Threads::Mutex::Lock& lock)
732 {
733         //cerr << _name << " destroying model " << _model.get() << endl;
734         _model.reset();
735         invalidate(lock);
736 }
737
738 void
739 SMFSource::flush_midi (const Lock& lock)
740 {
741         if (!writable() || _length_beats == 0.0) {
742                 return;
743         }
744
745         ensure_disk_file (lock);
746
747         Evoral::SMF::end_write (_path);
748         /* data in the file means its no longer removable */
749         mark_nonremovable ();
750
751         invalidate(lock);
752 }
753
754 void
755 SMFSource::set_path (const string& p)
756 {
757         FileSource::set_path (p);
758 }
759
760 /** Ensure that this source has some file on disk, even if it's just a SMF header */
761 void
762 SMFSource::ensure_disk_file (const Lock& lock)
763 {
764         if (!writable()) {
765                 return;
766         }
767
768         if (_model) {
769                 /* We have a model, so write it to disk; see MidiSource::session_saved
770                    for an explanation of what we are doing here.
771                 */
772                 boost::shared_ptr<MidiModel> mm = _model;
773                 _model.reset ();
774                 mm->sync_to_source (lock);
775                 _model = mm;
776                 invalidate(lock);
777         } else {
778                 /* No model; if it's not already open, it's an empty source, so create
779                    and open it for writing.
780                 */
781                 if (!_open) {
782                         open_for_write ();
783                 }
784         }
785 }
786
787 void
788 SMFSource::prevent_deletion ()
789 {
790         /* Unlike the audio case, the MIDI file remains mutable (because we can
791            edit MIDI data)
792         */
793
794         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
795 }
796
797