Invalidate iterator whenever model changes.
[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         , _last_ev_time_beats(0.0)
63         , _last_ev_time_frames(0)
64         , _smf_last_read_end (0)
65         , _smf_last_read_time (0)
66 {
67         /* note that origin remains empty */
68
69         if (init (_path, false)) {
70                 throw failed_constructor ();
71         }
72  
73         assert (!Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
74         existence_check ();
75
76         _flags = Source::Flag (_flags | Empty);
77
78         /* file is not opened until write */
79
80         if (flags & Writable) {
81                 return;
82         }
83
84         if (open (_path)) {
85                 throw failed_constructor ();
86         }
87
88         _open = true;
89 }
90
91 /** Constructor used for external-to-session files.  File must exist. */
92 SMFSource::SMFSource (Session& s, const string& path)
93         : Source(s, DataType::MIDI, path, Source::Flag (0))
94         , MidiSource(s, path, Source::Flag (0))
95         , FileSource(s, DataType::MIDI, path, string(), Source::Flag (0))
96         , Evoral::SMF()
97         , _last_ev_time_beats(0.0)
98         , _last_ev_time_frames(0)
99         , _smf_last_read_end (0)
100         , _smf_last_read_time (0)
101 {
102         /* note that origin remains empty */
103
104         if (init (_path, true)) {
105                 throw failed_constructor ();
106         }
107  
108         assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
109         existence_check ();
110
111         if (_flags & Writable) {
112                 /* file is not opened until write */
113                 return;
114         }
115
116         if (open (_path)) {
117                 throw failed_constructor ();
118         }
119
120         _open = true;
121 }
122
123 /** Constructor used for existing internal-to-session files. */
124 SMFSource::SMFSource (Session& s, const XMLNode& node, bool must_exist)
125         : Source(s, node)
126         , MidiSource(s, node)
127         , FileSource(s, node, must_exist)
128         , _last_ev_time_beats(0.0)
129         , _last_ev_time_frames(0)
130         , _smf_last_read_end (0)
131         , _smf_last_read_time (0)
132 {
133         if (set_state(node, Stateful::loading_state_version)) {
134                 throw failed_constructor ();
135         }
136
137         /* we expect the file to exist, but if no MIDI data was ever added
138            it will have been removed at last session close. so, we don't
139            require it to exist if it was marked Empty.
140         */
141
142         try {
143
144                 if (init (_path, true)) {
145                         throw failed_constructor ();
146                 }
147
148         } catch (MissingSource& err) {
149
150                 if (_flags & Source::Empty) {
151                         /* we don't care that the file was not found, because
152                            it was empty. But FileSource::init() will have
153                            failed to set our _path correctly, so we have to do
154                            this ourselves. Use the first entry in the search
155                            path for MIDI files, which is assumed to be the
156                            correct "main" location.
157                         */
158                         std::vector<string> sdirs = s.source_search_path (DataType::MIDI);
159                         _path = Glib::build_filename (sdirs.front(), _path);
160                         /* This might be important, too */
161                         _file_is_new = true;
162                 } else {
163                         /* pass it on */
164                         throw;
165                 }
166         }
167
168         if (!(_flags & Source::Empty)) {
169                 assert (Glib::file_test (_path, Glib::FILE_TEST_EXISTS));
170                 existence_check ();
171         } else {
172                 assert (_flags & Source::Writable);
173                 /* file will be opened on write */
174                 return;
175         }
176
177         if (open(_path)) {
178                 throw failed_constructor ();
179         }
180
181         _open = true;
182 }
183
184 SMFSource::~SMFSource ()
185 {
186         if (removable()) {
187                 ::g_unlink (_path.c_str());
188         }
189 }
190
191 int
192 SMFSource::open_for_write ()
193 {
194         if (create (_path)) {
195                 return -1;
196         }
197         _open = true;
198         return 0;
199 }
200
201 /** All stamps in audio frames */
202 framecnt_t
203 SMFSource::read_unlocked (Evoral::EventSink<framepos_t>& destination,
204                           framepos_t const               source_start,
205                           framepos_t                     start,
206                           framecnt_t                     duration,
207                           MidiStateTracker*              tracker) const
208 {
209         int      ret  = 0;
210         uint64_t time = 0; // in SMF ticks, 1 tick per _ppqn
211
212         if (writable() && !_open) {
213                 /* nothing to read since nothing has ben written */
214                 return duration;
215         }
216
217         DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start %1 duration %2\n", start, duration));
218
219         // Output parameters for read_event (which will allocate scratch in buffer as needed)
220         uint32_t ev_delta_t = 0;
221         uint32_t ev_type    = 0;
222         uint32_t ev_size    = 0;
223         uint8_t* ev_buffer  = 0;
224
225         size_t scratch_size = 0; // keep track of scratch to minimize reallocs
226
227         BeatsFramesConverter converter(_session.tempo_map(), source_start);
228
229         const uint64_t start_ticks = converter.from(start).to_ticks();
230         DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: start in ticks %1\n", start_ticks));
231
232         if (_smf_last_read_end == 0 || start != _smf_last_read_end) {
233                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: seek to %1\n", start));
234                 Evoral::SMF::seek_to_start();
235                 while (time < start_ticks) {
236                         gint ignored;
237
238                         ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
239                         if (ret == -1) { // EOF
240                                 _smf_last_read_end = start + duration;
241                                 return duration;
242                         }
243                         time += ev_delta_t; // accumulate delta time
244                 }
245         } else {
246                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked: set time to %1\n", _smf_last_read_time));
247                 time = _smf_last_read_time;
248         }
249
250         _smf_last_read_end = start + duration;
251
252         while (true) {
253                 gint ignored; /* XXX don't ignore note id's ??*/
254
255                 ret = read_event(&ev_delta_t, &ev_size, &ev_buffer, &ignored);
256                 if (ret == -1) { // EOF
257                         break;
258                 }
259
260                 time += ev_delta_t; // accumulate delta time
261                 _smf_last_read_time = time;
262
263                 if (ret == 0) { // meta-event (skipped, just accumulate time)
264                         continue;
265                 }
266
267                 ev_type = midi_parameter_type(ev_buffer[0]);
268
269                 DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("SMF read_unlocked delta %1, time %2, buf[0] %3, type %4\n",
270                                                                   ev_delta_t, time, ev_buffer[0], ev_type));
271
272                 assert(time >= start_ticks);
273
274                 /* Note that we add on the source start time (in session frames) here so that ev_frame_time
275                    is in session frames.
276                 */
277                 const framepos_t ev_frame_time = converter.to(Evoral::MusicalTime::ticks_at_rate(time, ppqn())) + source_start;
278
279                 if (ev_frame_time < start + duration) {
280                         destination.write (ev_frame_time, ev_type, ev_size, ev_buffer);
281
282                         if (tracker) {
283                                 if (ev_buffer[0] & MIDI_CMD_NOTE_ON) {
284                                         tracker->add (ev_buffer[1], ev_buffer[0] & 0xf);
285                                 } else if (ev_buffer[0] & MIDI_CMD_NOTE_OFF) {
286                                         tracker->remove (ev_buffer[1], ev_buffer[0] & 0xf);
287                                 }
288                         }
289                 } else {
290                         break;
291                 }
292
293                 if (ev_size > scratch_size) {
294                         scratch_size = ev_size;
295                 }
296                 ev_size = scratch_size; // ensure read_event only allocates if necessary
297         }
298
299         return duration;
300 }
301
302 framecnt_t
303 SMFSource::write_unlocked (MidiRingBuffer<framepos_t>& source,
304                            framepos_t                  position,
305                            framecnt_t                  cnt)
306 {
307         if (!_writing) {
308                 mark_streaming_write_started ();
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_unlocked_frames(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_unlocked_beats (const Evoral::Event<Evoral::MusicalTime>& ev)
384 {
385         if (!_writing || ev.size() == 0)  {
386                 return;
387         }
388
389         /*printf("SMFSource: %s - append_event_unlocked_beats ID = %d time = %lf, size = %u, data = ",
390                name().c_str(), ev.id(), ev.time(), ev.size());
391                for (size_t i = 0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");*/
392
393         Evoral::MusicalTime time = ev.time();
394         if (time < _last_ev_time_beats) {
395                 const Evoral::MusicalTime difference = _last_ev_time_beats - time;
396                 if (difference.to_double() / (double)ppqn() < 1.0) {
397                         /* Close enough.  This problem occurs because Sequence is not
398                            actually ordered due to fuzzy time comparison.  I'm pretty sure
399                            this is inherently a bad idea which causes problems all over the
400                            place, but tolerate it here for now anyway. */
401                         time = _last_ev_time_beats;
402                 } else {
403                         /* Out of order by more than a tick. */
404                         warning << string_compose(_("Skipping event with unordered beat time %1 < %2 (off by %3 beats, %4 ticks)"),
405                                                   ev.time(), _last_ev_time_beats, difference, difference.to_double() / (double)ppqn())
406                                 << endmsg;
407                         return;
408                 }
409         }
410
411         Evoral::event_id_t event_id;
412
413         if (ev.id() < 0) {
414                 event_id  = Evoral::next_event_id();
415         } else {
416                 event_id = ev.id();
417         }
418
419         if (_model) {
420                 _model->append (ev, event_id);
421         }
422
423         _length_beats = max(_length_beats, time);
424
425         const Evoral::MusicalTime delta_time_beats = time - _last_ev_time_beats;
426         const uint32_t            delta_time_ticks = delta_time_beats.to_ticks(ppqn());
427
428         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
429         _last_ev_time_beats = time;
430         _flags = Source::Flag (_flags & ~Empty);
431 }
432
433 /** Append an event with a timestamp in frames (framepos_t) */
434 void
435 SMFSource::append_event_unlocked_frames (const Evoral::Event<framepos_t>& ev, framepos_t position)
436 {
437         if (!_writing || ev.size() == 0)  {
438                 return;
439         }
440
441         // printf("SMFSource: %s - append_event_unlocked_frames ID = %d time = %u, size = %u, data = ",
442         // name().c_str(), ev.id(), ev.time(), ev.size());
443         // for (size_t i=0; i < ev.size(); ++i) printf("%X ", ev.buffer()[i]); printf("\n");
444
445         if (ev.time() < _last_ev_time_frames) {
446                 warning << string_compose(_("Skipping event with unordered frame time %1 < %2"),
447                                           ev.time(), _last_ev_time_frames)
448                         << endmsg;
449                 return;
450         }
451
452         BeatsFramesConverter      converter(_session.tempo_map(), position);
453         const Evoral::MusicalTime ev_time_beats = converter.from(ev.time());
454         Evoral::event_id_t        event_id;
455
456         if (ev.id() < 0) {
457                 event_id  = Evoral::next_event_id();
458         } else {
459                 event_id = ev.id();
460         }
461
462         if (_model) {
463                 const Evoral::Event<Evoral::MusicalTime> beat_ev (ev.event_type(),
464                                                                   ev_time_beats,
465                                                                   ev.size(),
466                                                                   const_cast<uint8_t*>(ev.buffer()));
467                 _model->append (beat_ev, event_id);
468         }
469
470         _length_beats = max(_length_beats, ev_time_beats);
471
472         const Evoral::MusicalTime last_time_beats  = converter.from (_last_ev_time_frames);
473         const Evoral::MusicalTime delta_time_beats = ev_time_beats - last_time_beats;
474         const uint32_t            delta_time_ticks = delta_time_beats.to_ticks(ppqn());
475
476         Evoral::SMF::append_event_delta(delta_time_ticks, ev.size(), ev.buffer(), event_id);
477         _last_ev_time_frames = ev.time();
478         _flags = Source::Flag (_flags & ~Empty);
479 }
480
481 XMLNode&
482 SMFSource::get_state ()
483 {
484         XMLNode& node = MidiSource::get_state();
485         node.add_property (X_("origin"), _origin);
486         return node;
487 }
488
489 int
490 SMFSource::set_state (const XMLNode& node, int version)
491 {
492         if (Source::set_state (node, version)) {
493                 return -1;
494         }
495
496         if (MidiSource::set_state (node, version)) {
497                 return -1;
498         }
499
500         if (FileSource::set_state (node, version)) {
501                 return -1;
502         }
503
504         return 0;
505 }
506
507 void
508 SMFSource::mark_streaming_midi_write_started (NoteMode mode)
509 {
510         /* CALLER MUST HOLD LOCK */
511
512         if (!_open && open_for_write()) {
513                 error << string_compose (_("cannot open MIDI file %1 for write"), _path) << endmsg;
514                 /* XXX should probably throw or return something */
515                 return;
516         }
517
518         MidiSource::mark_streaming_midi_write_started (mode);
519         Evoral::SMF::begin_write ();
520         _last_ev_time_beats  = Evoral::MusicalTime();
521         _last_ev_time_frames = 0;
522 }
523
524 void
525 SMFSource::mark_streaming_write_completed ()
526 {
527         mark_midi_streaming_write_completed (Evoral::Sequence<Evoral::MusicalTime>::DeleteStuckNotes);
528 }
529
530 void
531 SMFSource::mark_midi_streaming_write_completed (Evoral::Sequence<Evoral::MusicalTime>::StuckNoteOption stuck_notes_option, Evoral::MusicalTime when)
532 {
533         Glib::Threads::Mutex::Lock lm (_lock);
534         MidiSource::mark_midi_streaming_write_completed (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 (bool lock, bool force_reload)
597 {
598         if (_writing) {
599                 return;
600         }
601
602         boost::shared_ptr<Glib::Threads::Mutex::Lock> lm;
603         if (lock)
604                 lm = boost::shared_ptr<Glib::Threads::Mutex::Lock>(new Glib::Threads::Mutex::Lock(_lock));
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();
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::MusicalTime> 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::MusicalTime>*, 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::MusicalTime event_time = Evoral::MusicalTime::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::MusicalTime> (
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::MusicalTime>*, 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::MusicalTime>::ResolveStuckNotes, _length_beats);
706         _model->set_edited (false);
707         invalidate();
708
709         free(buf);
710 }
711
712 void
713 SMFSource::destroy_model ()
714 {
715         //cerr << _name << " destroying model " << _model.get() << endl;
716         _model.reset();
717         invalidate();
718 }
719
720 void
721 SMFSource::flush_midi ()
722 {
723         if (!writable() || _length_beats == 0.0) {
724                 return;
725         }
726
727         ensure_disk_file ();
728
729         Evoral::SMF::end_write ();
730         /* data in the file means its no longer removable */
731         mark_nonremovable ();
732
733         invalidate();
734 }
735
736 void
737 SMFSource::set_path (const string& p)
738 {
739         FileSource::set_path (p);
740         SMF::set_path (_path);
741 }
742
743 /** Ensure that this source has some file on disk, even if it's just a SMF header */
744 void
745 SMFSource::ensure_disk_file ()
746 {
747         if (!writable()) {
748                 return;
749         }
750
751         if (_model) {
752                 /* We have a model, so write it to disk; see MidiSource::session_saved
753                    for an explanation of what we are doing here.
754                 */
755                 boost::shared_ptr<MidiModel> mm = _model;
756                 _model.reset ();
757                 mm->sync_to_source ();
758                 _model = mm;
759                 invalidate();
760         } else {
761                 /* No model; if it's not already open, it's an empty source, so create
762                    and open it for writing.
763                 */
764                 if (!_open) {
765                         open_for_write ();
766                 }
767         }
768 }
769
770 void
771 SMFSource::prevent_deletion ()
772 {
773         /* Unlike the audio case, the MIDI file remains mutable (because we can
774            edit MIDI data)
775         */
776   
777         _flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
778 }
779                 
780