send a slightly more useful message if a control protocol's probe() method fails
[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         /* midi is in quarter note format as distinct from ardour beat */
443         _length_pulse = _length_beats.to_double() / 4.0;
444
445         const Evoral::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 frames (framepos_t) */
454 void
455 SMFSource::append_event_frames (const Glib::Threads::Mutex::Lock& lock,
456                                 const Evoral::Event<framepos_t>&  ev,
457                                 framepos_t                        position)
458 {
459         if (!_writing || ev.size() == 0)  {
460                 return;
461         }
462
463         // printf("SMFSource: %s - append_event_frames 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_frames) {
468                 warning << string_compose(_("Skipping event with unordered frame time %1 < %2"),
469                                           ev.time(), _last_ev_time_frames)
470                         << endmsg;
471                 return;
472         }
473
474         BeatsFramesConverter converter(_session.tempo_map(), position);
475         const Evoral::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<Evoral::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         /* midi is in quarter note format as distinct from ardour beat */
494         _length_pulse = _length_beats.to_double() / 4.0;
495
496         const Evoral::Beats last_time_beats  = converter.from (_last_ev_time_frames);
497         const Evoral::Beats delta_time_beats = ev_time_beats - last_time_beats;
498         const uint32_t      delta_time_ticks = delta_time_beats.to_ticks(ppqn());
499
500         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
501         _last_ev_time_frames = ev.time();
502         _flags = Source::Flag (_flags & ~Empty);
503 }
504
505 XMLNode&
506 SMFSource::get_state ()
507 {
508         XMLNode& node = MidiSource::get_state();
509         node.add_property (X_("origin"), _origin);
510         return node;
511 }
512
513 int
514 SMFSource::set_state (const XMLNode& node, int version)
515 {
516         if (Source::set_state (node, version)) {
517                 return -1;
518         }
519
520         if (MidiSource::set_state (node, version)) {
521                 return -1;
522         }
523
524         if (FileSource::set_state (node, version)) {
525                 return -1;
526         }
527
528         return 0;
529 }
530
531 void
532 SMFSource::mark_streaming_midi_write_started (const Lock& lock, NoteMode mode)
533 {
534         if (!_open && open_for_write()) {
535                 error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg;
536                 /* XXX should probably throw or return something */
537                 return;
538         }
539
540         MidiSource::mark_streaming_midi_write_started (lock, mode);
541         Evoral::SMF::begin_write ();
542         _last_ev_time_beats  = Evoral::Beats();
543         _last_ev_time_frames = 0;
544 }
545
546 void
547 SMFSource::mark_streaming_write_completed (const Lock& lock)
548 {
549         mark_midi_streaming_write_completed (lock, Evoral::Sequence<Evoral::Beats>::DeleteStuckNotes);
550 }
551
552 void
553 SMFSource::mark_midi_streaming_write_completed (const Lock& lm, Evoral::Sequence<Evoral::Beats>::StuckNoteOption stuck_notes_option, Evoral::Beats when)
554 {
555         MidiSource::mark_midi_streaming_write_completed (lm, stuck_notes_option, when);
556
557         if (!writable()) {
558                 warning << string_compose ("attempt to write to unwritable SMF file %1", _path) << endmsg;
559                 return;
560         }
561
562         if (_model) {
563                 _model->set_edited(false);
564         }
565
566         Evoral::SMF::end_write (_path);
567
568         /* data in the file now, not removable */
569
570         mark_nonremovable ();
571 }
572
573 bool
574 SMFSource::valid_midi_file (const string& file)
575 {
576         if (safe_midi_file_extension (file) ) {
577                 return (SMF::test (file) );
578         }
579         return false;
580 }
581
582 bool
583 SMFSource::safe_midi_file_extension (const string& file)
584 {
585         static regex_t compiled_pattern;
586         static bool compile = true;
587         const int nmatches = 2;
588         regmatch_t matches[nmatches];
589
590         if (Glib::file_test (file, Glib::FILE_TEST_EXISTS)) {
591                 if (!Glib::file_test (file, Glib::FILE_TEST_IS_REGULAR)) {
592                         /* exists but is not a regular file */
593                         return false;
594                 }
595         }
596
597         if (compile && regcomp (&compiled_pattern, "\\.[mM][iI][dD][iI]?$", REG_EXTENDED)) {
598                 return false;
599         } else {
600                 compile = false;
601         }
602
603         if (regexec (&compiled_pattern, file.c_str(), nmatches, matches, 0)) {
604                 return false;
605         }
606
607         return true;
608 }
609
610 static bool compare_eventlist (
611         const std::pair< Evoral::Event<Evoral::Beats>*, gint >& a,
612         const std::pair< Evoral::Event<Evoral::Beats>*, gint >& b) {
613         return ( a.first->time() < b.first->time() );
614 }
615
616 void
617 SMFSource::load_model (const Glib::Threads::Mutex::Lock& lock, bool force_reload)
618 {
619         if (_writing) {
620                 return;
621         }
622
623         if (_model && !force_reload) {
624                 return;
625         }
626
627         if (!_model) {
628                 _model = boost::shared_ptr<MidiModel> (new MidiModel (shared_from_this ()));
629         } else {
630                 _model->clear();
631         }
632
633         invalidate(lock);
634
635         if (writable() && !_open) {
636                 return;
637         }
638
639         _model->start_write();
640         Evoral::SMF::seek_to_start();
641
642         uint64_t time = 0; /* in SMF ticks */
643         Evoral::Event<Evoral::Beats> ev;
644
645         uint32_t scratch_size = 0; // keep track of scratch and minimize reallocs
646
647         uint32_t delta_t = 0;
648         uint32_t size    = 0;
649         uint8_t* buf     = NULL;
650         int ret;
651         gint event_id;
652         bool have_event_id;
653
654         // TODO simplify event allocation
655         std::list< std::pair< Evoral::Event<Evoral::Beats>*, gint > > eventlist;
656
657         for (unsigned i = 1; i <= num_tracks(); ++i) {
658                 if (seek_to_track(i)) continue;
659
660                 time = 0;
661                 have_event_id = false;
662
663                 while ((ret = read_event (&delta_t, &size, &buf, &event_id)) >= 0) {
664
665                         time += delta_t;
666
667                         if (ret == 0) {
668                                 /* meta-event : did we get an event ID ?  */
669                                 if (event_id >= 0) {
670                                         have_event_id = true;
671                                 }
672                                 continue;
673                         }
674
675                         if (ret > 0) {
676                                 /* not a meta-event */
677
678                                 if (!have_event_id) {
679                                         event_id = Evoral::next_event_id();
680                                 }
681                                 const uint32_t            event_type = midi_parameter_type(buf[0]);
682                                 const Evoral::Beats event_time = Evoral::Beats::ticks_at_rate(time, ppqn());
683 #ifndef NDEBUG
684                                 std::string ss;
685
686                                 for (uint32_t xx = 0; xx < size; ++xx) {
687                                         char b[8];
688                                         snprintf (b, sizeof (b), "0x%x ", buf[xx]);
689                                         ss += b;
690                                 }
691
692                                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF %7 load model delta %1, time %2, size %3 buf %4, type %5 id %6\n",
693                                                         delta_t, time, size, ss , event_type, event_id, name()));
694 #endif
695
696                                 eventlist.push_back(make_pair (
697                                                         new Evoral::Event<Evoral::Beats> (
698                                                                 event_type, event_time,
699                                                                 size, buf, true)
700                                                         , event_id));
701
702                                 // Set size to max capacity to minimize allocs in read_event
703                                 scratch_size = std::max(size, scratch_size);
704                                 size = scratch_size;
705
706                                 _length_beats = max(_length_beats, event_time);
707                                 /* midi is in quarter note format as distinct from ardour beat */
708                                 _length_pulse = _length_beats.to_double() / 4.0;
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<Evoral::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<Evoral::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 }