From fbfb26b45c075da880861cf2303b851fe1acc0e8 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Mon, 18 Feb 2008 19:45:52 +0000 Subject: Preliminary (read: kludgey) MIDI import support. Only really works when tracks contain only channel 1 data for now. git-svn-id: svn://localhost/ardour2/branches/3.0@3083 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/SConscript | 1 + libs/ardour/ardour/midi_event.h | 7 +- libs/ardour/ardour/midi_util.h | 2 +- libs/ardour/ardour/smf_reader.h | 89 ++++++++++++ libs/ardour/ardour/smf_source.h | 6 +- libs/ardour/ardour/source.h | 7 +- libs/ardour/import.cc | 202 +++++++++++++++++++++------ libs/ardour/midi_diskstream.cc | 2 +- libs/ardour/midi_model.cc | 2 +- libs/ardour/playlist.cc | 2 + libs/ardour/region_factory.cc | 2 + libs/ardour/smf_reader.cc | 294 ++++++++++++++++++++++++++++++++++++++++ libs/ardour/smf_source.cc | 18 ++- 13 files changed, 574 insertions(+), 60 deletions(-) create mode 100644 libs/ardour/ardour/smf_reader.h create mode 100644 libs/ardour/smf_reader.cc (limited to 'libs') diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index 7521bd728a..942905c341 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -129,6 +129,7 @@ session_time.cc session_transport.cc session_utils.cc silentfilesource.cc +smf_reader.cc smf_source.cc sndfile_helpers.cc sndfilesource.cc diff --git a/libs/ardour/ardour/midi_event.h b/libs/ardour/ardour/midi_event.h index ee6ea7b749..7dafb0fbcf 100644 --- a/libs/ardour/ardour/midi_event.h +++ b/libs/ardour/ardour/midi_event.h @@ -86,7 +86,7 @@ struct MidiEvent { if (_owns_buffer) { if (copy._buffer) { if (!_buffer || _size < copy._size) - _buffer = (Byte*)realloc(_buffer, copy._size); + _buffer = (Byte*)::realloc(_buffer, copy._size); memcpy(_buffer, copy._buffer, copy._size); } else { free(_buffer); @@ -130,6 +130,11 @@ struct MidiEvent { _owns_buffer = own; } + inline void realloc(size_t size) { + assert(_owns_buffer); + _buffer = (Byte*) ::realloc(_buffer, size); + } + #else inline void set_buffer(Byte* buf) { _buffer = buf; } diff --git a/libs/ardour/ardour/midi_util.h b/libs/ardour/ardour/midi_util.h index d85ab128fe..1f121fbd68 100644 --- a/libs/ardour/ardour/midi_util.h +++ b/libs/ardour/ardour/midi_util.h @@ -28,7 +28,7 @@ namespace ARDOUR { /** Return the size of the given event NOT including the status byte, * or -1 if unknown (eg sysex) */ -int +static inline int midi_event_size(unsigned char status) { if (status >= 0x80 && status <= 0xE0) { diff --git a/libs/ardour/ardour/smf_reader.h b/libs/ardour/ardour/smf_reader.h new file mode 100644 index 0000000000..806844004e --- /dev/null +++ b/libs/ardour/ardour/smf_reader.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2008 Paul Davis + Written by Dave Robillard + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __ardour_smf_reader_h__ +#define __ardour_smf_reader_h__ + +#include +#include +#include +#include + +namespace ARDOUR { + + +/** Standard MIDI File (Type 0) Reader + * + * Currently this only reads SMF files with tempo-based timing. + */ +class SMFReader { +public: + class PrematureEOF : public std::exception { + const char* what() const throw() { return "Unexpected end of file"; } + }; + class CorruptFile : public std::exception { + const char* what() const throw() { return "Corrupted file"; } + }; + class UnsupportedTime : public std::exception { + const char* what() const throw() { return "Unsupported time stamp type (SMPTE)"; } + }; + + SMFReader(const std::string filename=""); + ~SMFReader(); + + bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime); + + bool seek_to_track(unsigned track) throw (std::logic_error); + + const std::string& filename() const { return _filename; }; + + //TimeUnit unit() const { return _unit; } + uint16_t type() const { return _type; } + uint16_t ppqn() const { return _ppqn; } + uint16_t num_tracks() const { return _num_tracks; } + + int read_event(size_t buf_len, + uint8_t* buf, + uint32_t* ev_size, + uint32_t* ev_delta_time) + throw (std::logic_error, PrematureEOF, CorruptFile); + + void close(); + +protected: + /** size of SMF header, including MTrk chunk header */ + static const uint32_t HEADER_SIZE = 22; + + uint32_t read_var_len() const throw(PrematureEOF); + + std::string _filename; + FILE* _fd; + //TimeUnit _unit; + uint16_t _type; + uint16_t _ppqn; + uint16_t _num_tracks; + uint32_t _track; + uint32_t _track_size; +}; + + +} // namespace ARDOUR + +#endif /* __ardour_smf_reader_h__ */ + diff --git a/libs/ardour/ardour/smf_source.h b/libs/ardour/ardour/smf_source.h index bb3950f2ee..062e812643 100644 --- a/libs/ardour/ardour/smf_source.h +++ b/libs/ardour/ardour/smf_source.h @@ -63,8 +63,10 @@ class SMFSource : public MidiSource { bool set_name (const std::string& newname) { return (set_source_name(newname, false) == 0); } int set_source_name (string newname, bool destructive); + + static bool safe_file_extension (const Glib::ustring& path); - string path() const { return _path; } + Glib::ustring path() const { return _path; } void set_allow_remove_if_empty (bool yn); void mark_for_remove(); @@ -120,7 +122,7 @@ class SMFSource : public MidiSource { static const uint16_t _ppqn = 19200; uint16_t _channel; - string _path; + Glib::ustring _path; Flag _flags; string _take_id; bool _allow_remove_if_empty; diff --git a/libs/ardour/ardour/source.h b/libs/ardour/ardour/source.h index 3109cb00ff..a2ee96bf63 100644 --- a/libs/ardour/ardour/source.h +++ b/libs/ardour/ardour/source.h @@ -50,8 +50,9 @@ class Source : public SessionObject, public ARDOUR::Readable time_t timestamp() const { return _timestamp; } void stamp (time_t when) { _timestamp = when; } - /** @return the number of items in this source */ nframes_t length() const { return _length; } + + virtual Glib::ustring path() const = 0; virtual nframes_t natural_position() const { return 0; } @@ -88,10 +89,10 @@ class Source : public SessionObject, public ARDOUR::Readable AnalysisFeatureList transients; std::string get_transients_path() const; int load_transients (const std::string&); + + void update_length (nframes_t pos, nframes_t cnt); protected: - void update_length (nframes_t pos, nframes_t cnt); - DataType _type; time_t _timestamp; nframes_t _length; diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc index 2bc3a00a7a..d4a1bc72e7 100644 --- a/libs/ardour/import.cc +++ b/libs/ardour/import.cc @@ -39,7 +39,9 @@ #include #include +#include #include +#include #include #include #include @@ -47,6 +49,9 @@ #include #include #include +#include +#include +#include #include "i18n.h" @@ -66,23 +71,25 @@ open_importable_source (const string& path, nframes_t samplerate, ARDOUR::SrcQua } static std::string -get_non_existent_filename (const bool allow_replacing, const std::string destdir, const std::string& basename, uint channel, uint channels) +get_non_existent_filename (DataType type, const bool allow_replacing, const std::string destdir, const std::string& basename, uint channel, uint channels) { char buf[PATH_MAX+1]; bool goodfile = false; string base(basename); + const char* ext = (type == DataType::AUDIO) ? "wav" : "mid"; do { - if (channels == 2) { + + if (type == DataType::AUDIO && channels == 2) { if (channel == 0) { snprintf (buf, sizeof(buf), "%s-L.wav", base.c_str()); } else { snprintf (buf, sizeof(buf), "%s-R.wav", base.c_str()); } } else if (channels > 1) { - snprintf (buf, sizeof(buf), "%s-c%d.wav", base.c_str(), channel+1); + snprintf (buf, sizeof(buf), "%s-c%d.%s", base.c_str(), channel, ext); } else { - snprintf (buf, sizeof(buf), "%s.wav", base.c_str()); + snprintf (buf, sizeof(buf), "%s.%s", base.c_str(), ext); } @@ -112,13 +119,20 @@ get_paths_for_new_sources (const bool allow_replacing, const string& import_file vector new_paths; const string basename = basename_nosuffix (import_file_path); + SessionDirectory sdir(session_dir); + for (uint n = 0; n < channels; ++n) { - std::string filepath; + const DataType type = (import_file_path.rfind(".mid") != string::npos) + ? DataType::MIDI : DataType::AUDIO; + + std::string filepath = (type == DataType::MIDI) + ? sdir.midi_path().to_string() : sdir.sound_path().to_string(); - filepath = session_dir; filepath += '/'; - filepath += get_non_existent_filename (allow_replacing, session_dir, basename, n, channels); + filepath += get_non_existent_filename (type, allow_replacing, filepath, basename, n, channels); + + cout << "NEW SOURCE PATH: " << filepath << endl; new_paths.push_back (filepath); } @@ -128,7 +142,7 @@ get_paths_for_new_sources (const bool allow_replacing, const string& import_file static bool map_existing_mono_sources (const vector& new_paths, Session& sess, - uint samplerate, vector >& newfiles, Session *session) + uint samplerate, vector >& newfiles, Session *session) { for (vector::const_iterator i = new_paths.begin(); i != new_paths.end(); ++i) @@ -140,14 +154,14 @@ map_existing_mono_sources (const vector& new_paths, Session& sess, return false; } - newfiles.push_back(boost::dynamic_pointer_cast(source)); + newfiles.push_back(boost::dynamic_pointer_cast(source)); } return true; } static bool create_mono_sources_for_writing (const vector& new_paths, Session& sess, - uint samplerate, vector >& newfiles) + uint samplerate, vector >& newfiles) { for (vector::const_iterator i = new_paths.begin(); i != new_paths.end(); ++i) @@ -156,13 +170,16 @@ create_mono_sources_for_writing (const vector& new_paths, Session& sess, try { + const DataType type = ((*i).rfind(".mid") != string::npos) + ? DataType::MIDI : DataType::AUDIO; + source = SourceFactory::createWritable ( - DataType::AUDIO, - sess, - i->c_str(), - false, // destructive - samplerate - ); + type, + sess, + i->c_str(), + false, // destructive + samplerate + ); } catch (const failed_constructor& err) { @@ -170,7 +187,7 @@ create_mono_sources_for_writing (const vector& new_paths, Session& sess, return false; } - newfiles.push_back(boost::dynamic_pointer_cast(source)); + newfiles.push_back(boost::dynamic_pointer_cast(source)); } return true; } @@ -197,9 +214,10 @@ compose_status_message (const string& path, static void write_audio_data_to_new_files (ImportableSource* source, Session::import_status& status, - vector >& newfiles) + vector >& newfiles) { const nframes_t nframes = ResampledImportableSource::blocksize; + boost::shared_ptr afs; uint channels = source->channels(); boost::scoped_array data(new float[nframes * channels]); @@ -236,7 +254,9 @@ write_audio_data_to_new_files (ImportableSource* source, Session::import_status& /* flush to disk */ for (chn = 0; chn < channels; ++chn) { - newfiles[chn]->write (channel_data[chn].get(), nfread); + if ((afs = boost::dynamic_pointer_cast(newfiles[chn])) != 0) { + afs->write (channel_data[chn].get(), nfread); + } } read_count += nread; @@ -245,9 +265,71 @@ write_audio_data_to_new_files (ImportableSource* source, Session::import_status& } static void -remove_file_source (boost::shared_ptr file_source) +write_midi_data_to_new_files (SMFReader* source, Session::import_status& status, + vector >& newfiles) { - ::unlink (file_source->path().c_str()); + MidiEvent ev(0.0, 4, NULL, true); + + uint64_t t = 0; + uint32_t delta_t = 0; + uint32_t size = 0; + + status.progress = 0.0f; + + try { + + for (unsigned i = 1; i <= source->num_tracks(); ++i) { + + boost::shared_ptr smfs = boost::dynamic_pointer_cast(newfiles[i-1]); + + source->seek_to_track(i); + + while (!status.cancel) { + + if (source->read_event(4, ev.buffer(), &size, &delta_t) < 0) + break; + + // FIXME: kluuudge + if (ev.channel() != 0) { + cout << "Skipping event with channel " << ev.channel() << endl; + continue; + } + + t += delta_t; + ev.time() = t * (double)source->ppqn(); + ev.size() = size; + + smfs->append_event_unlocked(ev); + if (status.progress < 0.99) + status.progress += 0.01; + } + + nframes_t timeline_position = 0; // FIXME: ? + + // FIXME: kluuuuudge: assumes tempo never changes after start + const double frames_per_beat = smfs->session().tempo_map().tempo_at( + timeline_position).frames_per_beat( + smfs->session().engine().frame_rate(), + smfs->session().tempo_map().meter_at(timeline_position)); + + smfs->update_length(0, (t * source->ppqn()) * frames_per_beat); + + smfs->flush_header(); + smfs->flush_footer(); + + if (status.cancel) + break; + } + + } catch (...) { + error << "Corrupt MIDI file " << source->filename() << endl; + } +} + +static void +remove_file_source (boost::shared_ptr source) +{ + ::unlink (source->path().c_str()); } // This function is still unable to cleanly update an existing source, even though @@ -258,8 +340,10 @@ void Session::import_audiofiles (import_status& status) { uint32_t cnt = 1; - typedef vector > AudioSources; - AudioSources all_new_sources; + typedef vector > Sources; + Sources all_new_sources; + boost::shared_ptr afs; + uint channels = 0; status.sources.clear (); @@ -268,24 +352,44 @@ Session::import_audiofiles (import_status& status) ++p, ++cnt) { std::auto_ptr source; - - try - { - source = open_importable_source (*p, frame_rate(), status.quality); - } - catch (const failed_constructor& err) - { - error << string_compose(_("Import: cannot open input sound file \"%1\""), (*p)) << endmsg; - status.done = status.cancel = true; - return; + std::auto_ptr smf_reader; + + const DataType type = ((*p).rfind(".mid") != string::npos) + ? DataType::MIDI : DataType::AUDIO; + + if (type == DataType::AUDIO) { + try + { + source = open_importable_source (*p, frame_rate(), status.quality); + channels = source->channels(); + } catch (const failed_constructor& err) + { + error << string_compose(_("Import: cannot open input sound file \"%1\""), (*p)) << endmsg; + status.done = status.cancel = true; + return; + } + } else { + try + { + smf_reader = std::auto_ptr(new SMFReader(*p)); + channels = smf_reader->num_tracks(); + } catch (const SMFReader::UnsupportedTime& err) { + error << _("Import: unsupported MIDI time stamp format") << endmsg; + status.done = status.cancel = true; + return; + } catch (...) { + error << _("Import: error reading MIDI file") << endmsg; + status.done = status.cancel = true; + return; + } } vector new_paths = get_paths_for_new_sources (status.replace_existing_source, *p, get_best_session_directory_for_new_source (), - source->channels()); + channels); - AudioSources newfiles; + Sources newfiles; if (status.replace_existing_source) { fatal << "THIS IS NOT IMPLEMENTED YET, IT SHOULD NEVER GET CALLED!!! DYING!" << endl; @@ -299,14 +403,20 @@ Session::import_audiofiles (import_status& status) if (status.cancel) break; - for (AudioSources::iterator i = newfiles.begin(); i != newfiles.end(); ++i) { - (*i)->prepare_for_peakfile_writes (); + for (Sources::iterator i = newfiles.begin(); i != newfiles.end(); ++i) { + if ((afs = boost::dynamic_pointer_cast(*i)) != 0) { + afs->prepare_for_peakfile_writes (); + } } - status.doing_what = compose_status_message (*p, source->samplerate(), - frame_rate(), cnt, status.paths.size()); - - write_audio_data_to_new_files (source.get(), status, newfiles); + if (source.get()) { // audio + status.doing_what = compose_status_message (*p, source->samplerate(), + frame_rate(), cnt, status.paths.size()); + write_audio_data_to_new_files (source.get(), status, newfiles); + } else if (smf_reader.get()) { // midi + status.doing_what = string_compose(_("loading MIDI file %1"), *p); + write_midi_data_to_new_files (smf_reader.get(), status, newfiles); + } } if (!status.cancel) { @@ -318,10 +428,14 @@ Session::import_audiofiles (import_status& status) /* flush the final length(s) to the header(s) */ - for (AudioSources::iterator x = all_new_sources.begin(); x != all_new_sources.end(); ++x) + for (Sources::iterator x = all_new_sources.begin(); x != all_new_sources.end(); ++x) { - (*x)->update_header(0, *now, xnow); - (*x)->done_with_peakfile_writes (); + cout << "NEW SOURCE: " << (*x)->path() << endl; + + if ((afs = boost::dynamic_pointer_cast(*x)) != 0) { + afs->update_header(0, *now, xnow); + afs->done_with_peakfile_writes (); + } /* now that there is data there, requeue the file for analysis */ diff --git a/libs/ardour/midi_diskstream.cc b/libs/ardour/midi_diskstream.cc index ea340598ac..921824c615 100644 --- a/libs/ardour/midi_diskstream.cc +++ b/libs/ardour/midi_diskstream.cc @@ -670,7 +670,7 @@ MidiDiskstream::set_pending_overwrite (bool yn) int MidiDiskstream::overwrite_existing_buffers () { - cerr << "MDS: overwrite_existing_buffers() (does nothing)" << endl; + //cerr << "MDS: overwrite_existing_buffers() (does nothing)" << endl; return 0; } diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index 9a87e89ab8..51793a4189 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -433,7 +433,7 @@ MidiModel::append_note_off_unlocked(double time, uint8_t note_num) Note& note = *_notes[*n].get(); //cerr << (unsigned)(uint8_t)note.note() << " ? " << (unsigned)note_num << endl; if (note.note() == note_num) { - assert(time > note.time()); + assert(time >= note.time()); note.set_duration(time - note.time()); _write_notes.erase(n); //cerr << "MM resolved note, duration: " << note.duration() << endl; diff --git a/libs/ardour/playlist.cc b/libs/ardour/playlist.cc index 65279499ec..7bc4287a58 100644 --- a/libs/ardour/playlist.cc +++ b/libs/ardour/playlist.cc @@ -522,6 +522,8 @@ Playlist::set_region_ownership () void Playlist::add_region_internal (boost::shared_ptr region, nframes_t position) { + assert(region->data_type() == _type); + RegionSortByPosition cmp; nframes_t old_length = 0; diff --git a/libs/ardour/region_factory.cc b/libs/ardour/region_factory.cc index bd4b0873a7..bb40fb16cf 100644 --- a/libs/ardour/region_factory.cc +++ b/libs/ardour/region_factory.cc @@ -108,6 +108,8 @@ RegionFactory::create (Session& session, XMLNode& node, bool yn) boost::shared_ptr RegionFactory::create (const SourceList& srcs, nframes_t start, nframes_t length, const string& name, layer_t layer, Region::Flag flags, bool announce) { + cerr << "CREATE REGION " << name << " " << start << " * " << length << endl; + if (srcs.empty()) { return boost::shared_ptr(); } diff --git a/libs/ardour/smf_reader.cc b/libs/ardour/smf_reader.cc new file mode 100644 index 0000000000..21ca370e6c --- /dev/null +++ b/libs/ardour/smf_reader.cc @@ -0,0 +1,294 @@ +/* + Copyright (C) 2008 Paul Davis + Written by Dave Robillard + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace ARDOUR { + + +SMFReader::SMFReader(const std::string filename) + : _fd(NULL) + //, _unit(TimeUnit::BEATS, 192) + , _ppqn(0) + , _track(0) + , _track_size(0) +{ + if (filename.length() > 0) { + open(filename); + } +} + + +SMFReader::~SMFReader() +{ + if (_fd) + close(); +} + + +bool +SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime) +{ + if (_fd) + throw logic_error("Attempt to start new read while write in progress."); + + cout << "Opening SMF file " << filename << " for reading." << endl; + + _fd = fopen(filename.c_str(), "r+"); + + if (_fd) { + // Read type (bytes 8..9) + fseek(_fd, 0, SEEK_SET); + char mthd[5]; + mthd[4] = '\0'; + fread(mthd, 1, 4, _fd); + if (strcmp(mthd, "MThd")) { + cerr << filename << " is not an SMF file, aborting." << endl; + fclose(_fd); + _fd = NULL; + return false; + } + + // Read type (bytes 8..9) + fseek(_fd, 8, SEEK_SET); + uint16_t type_be = 0; + fread(&type_be, 2, 1, _fd); + _type = GUINT16_FROM_BE(type_be); + + // Read number of tracks (bytes 10..11) + uint16_t num_tracks_be = 0; + fread(&num_tracks_be, 2, 1, _fd); + _num_tracks = GUINT16_FROM_BE(num_tracks_be); + + // Read PPQN (bytes 12..13) + + uint16_t ppqn_be = 0; + fread(&ppqn_be, 2, 1, _fd); + _ppqn = GUINT16_FROM_BE(ppqn_be); + + // TODO: Absolute (SMPTE seconds) time support + if ((_ppqn & 0x8000) != 0) + throw UnsupportedTime(); + + //_unit = TimeUnit::beats(_ppqn); + + seek_to_track(1); + + return true; + } else { + return false; + } +} + + +/** Seek to the start of a given track, starting from 1. + * Returns true if specified track was found. + */ +bool +SMFReader::seek_to_track(unsigned track) throw (std::logic_error) +{ + if (track == 0) + throw logic_error("Seek to track 0 out of range (must be >= 1)"); + + if (!_fd) + throw logic_error("Attempt to seek to track on unopened SMF file."); + + unsigned track_pos = 0; + + fseek(_fd, 14, SEEK_SET); + char id[5]; + id[4] = '\0'; + uint32_t chunk_size = 0; + + while (!feof(_fd)) { + fread(id, 1, 4, _fd); + + if (!strcmp(id, "MTrk")) { + ++track_pos; + //std::cerr << "Found track " << track_pos << endl; + } else { + std::cerr << "Unknown chunk ID " << id << endl; + } + + uint32_t chunk_size_be; + fread(&chunk_size_be, 4, 1, _fd); + chunk_size = GUINT32_FROM_BE(chunk_size_be); + + if (track_pos == track) + break; + + fseek(_fd, chunk_size, SEEK_CUR); + } + + if (!feof(_fd) && track_pos == track) { + _track = track; + _track_size = chunk_size; + return true; + } else { + return false; + } +} + + +/** Read an event from the current position in file. + * + * File position MUST be at the beginning of a delta time, or this will die very messily. + * ev.buffer must be of size ev.size, and large enough for the event. The returned event + * will have it's time field set to it's delta time (so it's the caller's responsibility + * to keep track of delta time, even for ignored events). + * + * Returns event length (including status byte) on success, 0 if event was + * skipped (eg a meta event), or -1 on EOF (or end of track). + * + * If @a buf is not large enough to hold the event, 0 will be returned, but ev_size + * set to the actual size of the event. + */ +int +SMFReader::read_event(size_t buf_len, + uint8_t* buf, + uint32_t* ev_size, + uint32_t* delta_time) + throw (std::logic_error, PrematureEOF, CorruptFile) +{ + if (_track == 0) + throw logic_error("Attempt to read from unopened SMF file"); + + if (!_fd || feof(_fd)) { + return -1; + } + + assert(buf_len > 0); + assert(buf); + assert(ev_size); + assert(delta_time); + + //cerr.flags(ios::hex); + //cerr << "SMF - Reading event at offset 0x" << ftell(_fd) << endl; + //cerr.flags(ios::dec); + + // Running status state + static uint8_t last_status = 0; + static uint32_t last_size = 0; + + *delta_time = read_var_len(); + int status = fgetc(_fd); + if (status == EOF) + throw PrematureEOF(); + else if (status > 0xFF) + throw CorruptFile(); + + if (status < 0x80) { + if (last_status == 0) + throw CorruptFile(); + status = last_status; + *ev_size = last_size; + fseek(_fd, -1, SEEK_CUR); + //cerr << "RUNNING STATUS, size = " << *ev_size << endl; + } else { + last_status = status; + *ev_size = midi_event_size(status) + 1; + last_size = *ev_size; + //cerr << "NORMAL STATUS, size = " << *ev_size << endl; + } + + buf[0] = (uint8_t)status; + + if (status == 0xFF) { + *ev_size = 0; + if (feof(_fd)) + throw PrematureEOF(); + uint8_t type = fgetc(_fd); + const uint32_t size = read_var_len(); + /*cerr.flags(ios::hex); + cerr << "SMF - meta 0x" << (int)type << ", size = "; + cerr.flags(ios::dec); + cerr << size << endl;*/ + + if ((uint8_t)type == 0x2F) { + //cerr << "SMF - hit EOT" << endl; + return -1; // we hit the logical EOF anyway... + } else { + fseek(_fd, size, SEEK_CUR); + return 0; + } + } + + if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) { + //cerr << "Skipping event" << endl; + // Skip event, return 0 + fseek(_fd, *ev_size - 1, SEEK_CUR); + return 0; + } else { + // Read event, return size + if (ferror(_fd)) + throw CorruptFile(); + fread(buf+1, 1, *ev_size - 1, _fd); + + if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) { + buf[0] = (0x80 | (buf[0] & 0x0F)); + buf[2] = 0x40; + } + + return *ev_size; + } +} + + +void +SMFReader::close() +{ + if (_fd) + fclose(_fd); + + _fd = NULL; +} + + +uint32_t +SMFReader::read_var_len() const throw(PrematureEOF) +{ + if (feof(_fd)) + throw PrematureEOF(); + + uint32_t value; + uint8_t c; + + if ( (value = getc(_fd)) & 0x80 ) { + value &= 0x7F; + do { + if (feof(_fd)) + throw PrematureEOF(); + value = (value << 7) + ((c = getc(_fd)) & 0x7F); + } while (c & 0x80); + } + + return value; +} + + +} // namespace ARDOUR + diff --git a/libs/ardour/smf_source.cc b/libs/ardour/smf_source.cc index 29f36cc2cd..59ad7269db 100644 --- a/libs/ardour/smf_source.cc +++ b/libs/ardour/smf_source.cc @@ -70,7 +70,7 @@ SMFSource::SMFSource (Session& s, std::string path, Flag flags) throw failed_constructor (); } - //cerr << "SMF Source path: " << path << endl; + cerr << "SMF Source path: " << path << endl; assert(_name.find("/") == string::npos); } @@ -99,7 +99,7 @@ SMFSource::SMFSource (Session& s, const XMLNode& node) throw failed_constructor (); } - //cerr << "SMF Source name: " << _name << endl; + cerr << "SMF Source name: " << _name << endl; assert(_name.find("/") == string::npos); } @@ -184,7 +184,7 @@ SMFSource::flush_header () const uint16_t type = GUINT16_TO_BE(0); // SMF Type 0 (single track) const uint16_t ntracks = GUINT16_TO_BE(1); // Number of tracks (always 1 for Type 0) - const uint16_t division = GUINT16_TO_BE(_ppqn); // Pulses per beat + const uint16_t division = GUINT16_TO_BE(_ppqn); // Pulses per quarter note (beat) char data[6]; memcpy(data, &type, 2); @@ -195,9 +195,7 @@ SMFSource::flush_header () assert(_fd); fseek(_fd, 0, 0); write_chunk("MThd", 6, data); - //if (_track_size > 0) { - write_chunk_header("MTrk", _track_size); - //} + write_chunk_header("MTrk", _track_size); fflush(_fd); @@ -446,7 +444,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt) void SMFSource::append_event_unlocked(const MidiEvent& ev) { - /*printf("SMF - writing event, time = %lf, size = %u, data = ", ev.time(), ev.size()); + /*printf("SMF %s - writing event, time = %lf, size = %u, data = ", _path.c_str(), ev.time(), ev.size()); for (size_t i=0; i < ev.size(); ++i) { printf("%X ", ev.buffer()[i]); } @@ -636,6 +634,12 @@ SMFSource::move_to_trash (const string trash_dir_name) return 0; } +bool +SMFSource::safe_file_extension(const Glib::ustring& file) +{ + return (file.rfind(".mid") != Glib::ustring::npos); +} + // FIXME: Merge this with audiofilesource somehow (make a generic filesource?) bool SMFSource::find (string pathstr, bool must_exist, bool& isnew) -- cgit v1.2.3