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