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