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