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