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