From: David Robillard Date: Tue, 30 Sep 2008 00:45:26 +0000 (+0000) Subject: Separate low level details of SMF reading/writing from concept of 'midi source in... X-Git-Tag: 3.0-alpha5~4074 X-Git-Url: https://main.carlh.net/gitweb/?a=commitdiff_plain;h=9a30bb2aecabcdd16568e6548dd1aeade8effa48;p=ardour.git Separate low level details of SMF reading/writing from concept of 'midi source in ardour'. git-svn-id: svn://localhost/ardour2/branches/3.0@3839 d708f5d6-7413-0410-9779-e7cbd77b26cf --- diff --git a/gtk2_ardour/sfdb_ui.cc b/gtk2_ardour/sfdb_ui.cc index 14017cf6ab..601b83f0c0 100644 --- a/gtk2_ardour/sfdb_ui.cc +++ b/gtk2_ardour/sfdb_ui.cc @@ -37,12 +37,13 @@ #include +#include + #include #include #include #include #include -#include #include #include #include @@ -1097,7 +1098,7 @@ SoundFileOmega::check_info (const vector& paths, bool& same_size, bool& } else if (SMFSource::safe_file_extension (*i)) { - SMFReader reader(*i); + Evoral::SMFReader reader(*i); if (reader.num_tracks() > 1) { multichannel = true; // "channel" == track here... } diff --git a/libs/ardour/SConscript b/libs/ardour/SConscript index f310e7ca0f..b117b41b7a 100644 --- a/libs/ardour/SConscript +++ b/libs/ardour/SConscript @@ -152,7 +152,6 @@ session_time.cc session_transport.cc session_utils.cc silentfilesource.cc -smf_reader.cc smf_source.cc sndfile_helpers.cc sndfileimportable.cc diff --git a/libs/ardour/ardour/midi_source.h b/libs/ardour/ardour/midi_source.h index 9cb222d207..895dcb25cc 100644 --- a/libs/ardour/ardour/midi_source.h +++ b/libs/ardour/ardour/midi_source.h @@ -95,8 +95,9 @@ class MidiSource : public Source void set_model(boost::shared_ptr m) { _model = m; } protected: - virtual int flush_header() = 0; - virtual int flush_footer() = 0; + virtual void flush_midi() = 0; + //virtual int flush_header() = 0; + //virtual int flush_footer() = 0; virtual nframes_t read_unlocked (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset, nframes_t negative_stamp_offset) const = 0; virtual nframes_t write_unlocked (MidiRingBuffer& dst, nframes_t cnt) = 0; diff --git a/libs/ardour/ardour/smf_reader.h b/libs/ardour/ardour/smf_reader.h deleted file mode 100644 index e41dcc3bc4..0000000000 --- a/libs/ardour/ardour/smf_reader.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - 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; }; - - 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(); - - static uint32_t read_var_len(FILE* fd) throw (PrematureEOF); - -protected: - /** size of SMF header, including MTrk chunk header */ - static const uint32_t HEADER_SIZE = 22; - - std::string _filename; - FILE* _fd; - 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 3217c8e3e8..7d114d49ed 100644 --- a/libs/ardour/ardour/smf_source.h +++ b/libs/ardour/ardour/smf_source.h @@ -25,6 +25,7 @@ #include #include +#include namespace Evoral { class Event; } @@ -33,7 +34,7 @@ namespace ARDOUR { class MidiRingBuffer; /** Standard Midi File (Type 0) Source */ -class SMFSource : public MidiSource { +class SMFSource : public MidiSource, public Evoral::SMF { public: enum Flag { Writable = 0x1, @@ -67,7 +68,7 @@ class SMFSource : public MidiSource { int set_source_name (string newname, bool destructive); static bool safe_file_extension (const Glib::ustring& path); - + Glib::ustring path() const { return _path; } void set_allow_remove_if_empty (bool yn); @@ -75,12 +76,8 @@ class SMFSource : public MidiSource { void append_event_unlocked(EventTimeUnit unit, const Evoral::Event& ev); - int flush_header (); - int flush_footer (); - int move_to_trash (const string trash_dir_name); - bool is_empty () const; void mark_streaming_midi_write_started (NoteMode mode, nframes_t start_time); void mark_streaming_write_completed (); @@ -93,56 +90,34 @@ class SMFSource : public MidiSource { XMLNode& get_state (); int set_state (const XMLNode&); - void seek_to(nframes_t time); - void load_model(bool lock=true, bool force_reload=false); void destroy_model(); - uint16_t ppqn() const { return _ppqn; } + void flush_midi(); private: int init (string idstr, bool must_exist); - nframes_t read_unlocked (MidiRingBuffer& dst, nframes_t start, nframes_t cn, nframes_t stamp_offset, nframes_t negative_stamp_offset) const; - nframes_t write_unlocked (MidiRingBuffer& dst, nframes_t cnt); + nframes_t read_unlocked ( + MidiRingBuffer& dst, + nframes_t start, + nframes_t cn, + nframes_t stamp_offset, + nframes_t negative_stamp_offset) const; + + nframes_t write_unlocked ( + MidiRingBuffer& src, + nframes_t cnt); bool find (std::string path, bool must_exist, bool& is_new); bool removable() const; bool writable() const { return _flags & Writable; } - int open(); - void close(); - - /** - * This method is only used by flush_footer() to find the right seek position - * for the footer (at the end after recording or -4 offset ro SEEK_END - * if a footer is already present) - */ - void seek_to_footer_position(); - - /** - * write the track footer at the current seek position - */ - void write_footer(); - - void write_chunk_header(const char id[4], uint32_t length); - void write_chunk(const char id[4], uint32_t length, void* data); - size_t write_var_len(uint32_t val); - uint32_t read_var_len() const; - int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const; - - static const uint16_t _ppqn = 19200; - Glib::ustring _path; Flag _flags; string _take_id; bool _allow_remove_if_empty; - FILE* _fd; - double _last_ev_time; ///< last frame time written, relative to source start - uint32_t _track_size; - uint32_t _header_size; ///< size of SMF header, including MTrk chunk header - bool _empty; ///< true iff file contains (non-empty) events static string _search_path; }; diff --git a/libs/ardour/import.cc b/libs/ardour/import.cc index 370991be5f..3f6e60af33 100644 --- a/libs/ardour/import.cc +++ b/libs/ardour/import.cc @@ -37,6 +37,8 @@ #include #include +#include + #include #include #include @@ -50,7 +52,6 @@ #include #include #include -#include #include #include @@ -302,7 +303,7 @@ write_audio_data_to_new_files (ImportableSource* source, Session::import_status& } static void -write_midi_data_to_new_files (SMFReader* source, Session::import_status& status, +write_midi_data_to_new_files (Evoral::SMFReader* source, Session::import_status& status, vector >& newfiles) { Evoral::Event ev(0, 0.0, 4, NULL, true); @@ -344,9 +345,7 @@ write_midi_data_to_new_files (SMFReader* source, Session::import_status& status, smfs->session().tempo_map().meter_at(timeline_position)); smfs->update_length(0, (nframes_t) ceil ((t / (double)source->ppqn()) * frames_per_beat)); - - smfs->flush_header(); - smfs->flush_footer(); + smfs->end_write(); if (status.cancel) break; @@ -384,7 +383,7 @@ Session::import_audiofiles (import_status& status) ++p, ++cnt) { boost::shared_ptr source; - std::auto_ptr smf_reader; + std::auto_ptr smf_reader; const DataType type = ((*p).rfind(".mid") != string::npos) ? DataType::MIDI : DataType::AUDIO; @@ -400,9 +399,9 @@ Session::import_audiofiles (import_status& status) } else { try { - smf_reader = std::auto_ptr(new SMFReader(*p)); + smf_reader = std::auto_ptr(new Evoral::SMFReader(*p)); channels = smf_reader->num_tracks(); - } catch (const SMFReader::UnsupportedTime& err) { + } catch (const Evoral::SMFReader::UnsupportedTime& err) { error << _("Import: unsupported MIDI time stamp format") << endmsg; status.done = status.cancel = true; return; diff --git a/libs/ardour/midi_source.cc b/libs/ardour/midi_source.cc index d6d27a1f89..ac52b81ee5 100644 --- a/libs/ardour/midi_source.cc +++ b/libs/ardour/midi_source.cc @@ -165,8 +165,7 @@ MidiSource::mark_streaming_write_completed () void MidiSource::session_saved() { - flush_header(); - flush_footer(); + flush_midi(); if (_model && _model->edited()) { string newname; @@ -197,8 +196,7 @@ MidiSource::session_saved() newsrc->set_model(_model); _model->set_midi_source(newsrc.get()); - newsrc->flush_header(); - newsrc->flush_footer(); + newsrc->flush_midi(); Switched.emit(newsrc); } diff --git a/libs/ardour/smf_reader.cc b/libs/ardour/smf_reader.cc deleted file mode 100644 index 48ec3dc386..0000000000 --- a/libs/ardour/smf_reader.cc +++ /dev/null @@ -1,285 +0,0 @@ -/* - 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 -#include - -using namespace std; - -namespace ARDOUR { - - -SMFReader::SMFReader(const string filename) - : _fd(NULL) - , _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(); - - 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; - } 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); - - // Running status state - static uint8_t last_status = 0; - static uint32_t last_size = 0; - - *delta_time = read_var_len(_fd); - 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); - } else { - last_status = status; - *ev_size = midi_event_size(status) + 1; - last_size = *ev_size; - } - - 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(_fd); - /*cerr.flags(ios::hex); - cerr << "SMF - meta 0x" << (int)type << ", size = "; - cerr.flags(ios::dec); - cerr << size << endl;*/ - - if ((uint8_t)type == 0x2F) { - 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 << "SMF - 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(FILE* fd) 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 3bd929d2ca..5b70f8e9c6 100644 --- a/libs/ardour/smf_source.cc +++ b/libs/ardour/smf_source.cc @@ -32,13 +32,14 @@ #include +#include + #include #include #include #include #include #include -#include #include #include "i18n.h" @@ -54,13 +55,9 @@ uint64_t SMFSource::header_position_offset; SMFSource::SMFSource (Session& s, std::string path, Flag flags) : MidiSource (s, region_name_from_path(path, false)) + , SMF () , _flags (Flag(flags | Writable)) // FIXME: this needs to be writable for now , _allow_remove_if_empty(true) - , _fd (0) - , _last_ev_time(0) - , _track_size(4) // 4 bytes for the ever-present EOT event - , _header_size(22) - , _empty(true) { /* constructor used for new internal-to-session files. file cannot exist */ @@ -68,7 +65,7 @@ SMFSource::SMFSource (Session& s, std::string path, Flag flags) throw failed_constructor (); } - if (open()) { + if (open(path)) { throw failed_constructor (); } @@ -79,11 +76,6 @@ SMFSource::SMFSource (Session& s, const XMLNode& node) : MidiSource (s, node) , _flags (Flag (Writable|CanRename)) , _allow_remove_if_empty(true) - , _fd (0) - , _last_ev_time(0) - , _track_size(4) // 4 bytes for the ever-present EOT event - , _header_size(22) - , _empty(true) { /* constructor used for existing internal-to-session files. file must exist */ @@ -95,7 +87,7 @@ SMFSource::SMFSource (Session& s, const XMLNode& node) throw failed_constructor (); } - if (open()) { + if (open(_path)) { throw failed_constructor (); } @@ -134,245 +126,6 @@ SMFSource::init (string pathstr, bool must_exist) return 0; } -/** Attempt to open the SMF file for reading and writing. - * - * Currently SMFSource is always read/write. - * - * \return 0 on success - * -1 if the file can not be opened for reading, - * -2 if the file can not be opened for writing - */ -int -SMFSource::open() -{ - //cerr << "Opening SMF file " << path() << " writeable: " << writable() << endl; - - assert(writable()); // FIXME; - - _fd = fopen(path().c_str(), "r+"); - - // File already exists - if (_fd) { - fseek(_fd, _header_size - 4, 0); - uint32_t track_size_be = 0; - fread(&track_size_be, 4, 1, _fd); - _track_size = GUINT32_FROM_BE(track_size_be); - _empty = _track_size > 4; - //cerr << "SMF - read track size " << _track_size << endl; - - // We're making a new file - } else { - _fd = fopen(path().c_str(), "w+"); - if (_fd == NULL) { - cerr << "ERROR: Can not open SMF file " << path() << " for writing: " << - strerror(errno) << endl; - return -2; - } - _track_size = 4; - _empty = true; - - // Write a tentative header just to pad things out so writing happens in the right spot - flush_header(); - flush_footer(); - } - - return (_fd == 0) ? -1 : 0; -} - -void -SMFSource::close() -{ - if (_fd) { - flush_header(); - flush_footer(); - fclose(_fd); - _fd = NULL; - } -} - -void -SMFSource::seek_to_footer_position() -{ - uint8_t buffer[4]; - - // lets check if there is a track end marker at the end of the data - fseek(_fd, -4, SEEK_END); - //cerr << "SMFSource::seek_to_footer_position: At position: " << ftell(_fd); - size_t read_bytes = fread(buffer, sizeof(uint8_t), 4, _fd); - /*cerr << " read size: " << read_bytes << " buffer: "; - for (size_t i=0; i < read_bytes; ++i) { - printf("%x ", buffer[i]); - } - printf("\n"); - */ - - if( (read_bytes == 4) && - buffer[0] == 0x00 && - buffer[1] == 0xFF && - buffer[2] == 0x2F && - buffer[3] == 0x00) { - // there is one, so overwrite it - fseek(_fd, -4, SEEK_END); - } else { - // there is none, so append - fseek(_fd, 0, SEEK_END); - } -} - -int -SMFSource::flush_header() -{ - // FIXME: write timeline position somehow? - - //cerr << path() << " SMF Flushing header\n"; - - assert(_fd); - - 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 quarter note (beat) - - char data[6]; - memcpy(data, &type, 2); - memcpy(data+2, &ntracks, 2); - memcpy(data+4, &division, 2); - - _fd = freopen(path().c_str(), "r+", _fd); - assert(_fd); - fseek(_fd, 0, SEEK_SET); - write_chunk("MThd", 6, data); - write_chunk_header("MTrk", _track_size); - - fflush(_fd); - - return 0; -} - -int -SMFSource::flush_footer() -{ - //cerr << path() << " SMF Flushing footer\n"; - seek_to_footer_position(); - write_footer(); - seek_to_footer_position(); - - return 0; -} - -void -SMFSource::write_footer() -{ - write_var_len(0); - char eot[3] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event - fwrite(eot, 1, 3, _fd); - fflush(_fd); -} - -/** Returns the offset of the first event in the file with a time past @a start, - * relative to the start of the source. - * - * Returns -1 if not found. - */ -/* -long -SMFSource::find_first_event_after(nframes_t start) -{ - // FIXME: obviously this is slooow - - fseek(_fd, _header_size, 0); - - while ( ! feof(_fd) ) { - const uint32_t delta_time = read_var_len(); - - if (delta_time > start) - return delta_time; - } - - return -1; -} -*/ - -/** 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, in SMF tempo-based ticks, using the - * rate given by ppqn() (it is the caller's responsibility to calculate a real time). - * - * \a size should be the capacity of \a buf. If it is not large enough, \a buf will - * be freed and a new buffer allocated in its place, the size of which will be placed - * in size. - * - * 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). - */ -int -SMFSource::read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const -{ - if (feof(_fd)) { - return -1; - } - - assert(delta_t); - assert(size); - assert(buf); - - try { - *delta_t = SMFReader::read_var_len(_fd); - } catch (...) { - return -1; // Premature EOF - } - - if (feof(_fd)) { - return -1; // Premature EOF - } - - const int status = fgetc(_fd); - - if (status == EOF) { - return -1; // Premature EOF - } - - //printf("Status @ %X = %X\n", (unsigned)ftell(_fd) - 1, status); - - if (status == 0xFF) { - if (feof(_fd)) { - return -1; // Premature EOF - } - const int type = fgetc(_fd); - if ((unsigned char)type == 0x2F) { - return -1; // hit end of track - } else { - *size = 0; - return 0; - } - } - - const int event_size = midi_event_size((unsigned char)status) + 1; - if (event_size <= 0) { - *size = 0; - return 0; - } - - // Make sure we have enough scratch buffer - if (*size < (unsigned)event_size) - *buf = (uint8_t*)realloc(*buf, event_size); - - *size = event_size; - - (*buf)[0] = (unsigned char)status; - if (event_size > 1) - fread((*buf) + 1, 1, *size - 1, _fd); - - /*printf("SMFSource %s read event: delta = %u, size = %u, data = ", _name.c_str(), *delta_t, *size); - for (size_t i=0; i < *size; ++i) { - printf("%X ", (*buf)[i]); - } - printf("\n");*/ - - return (int)*size; -} - /** All stamps in audio frames */ nframes_t SMFSource::read_unlocked (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, nframes_t stamp_offset, nframes_t negative_stamp_offset) const @@ -393,16 +146,16 @@ SMFSource::read_unlocked (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, n size_t scratch_size = 0; // keep track of scratch to minimize reallocs // FIXME: don't seek to start and search every read (brutal!) - fseek(_fd, _header_size, SEEK_SET); + SMF::seek_to_start(); // FIXME: assumes tempo never changes after start const double frames_per_beat = _session.tempo_map().tempo_at(_timeline_position).frames_per_beat( _session.engine().frame_rate(), _session.tempo_map().meter_at(_timeline_position)); - const uint64_t start_ticks = (uint64_t)((start / frames_per_beat) * _ppqn); + const uint64_t start_ticks = (uint64_t)((start / frames_per_beat) * ppqn()); - while (!feof(_fd)) { + while (!SMF::eof()) { int ret = read_event(&ev_delta_t, &ev_size, &ev_buffer); if (ret == -1) { // EOF //cerr << "SMF - EOF\n"; @@ -420,7 +173,7 @@ SMFSource::read_unlocked (MidiRingBuffer& dst, nframes_t start, nframes_t cnt, n if (time >= start_ticks) { const nframes_t ev_frame_time = (nframes_t)( - ((time / (double)_ppqn) * frames_per_beat)) + stamp_offset; + ((time / (double)ppqn()) * frames_per_beat)) + stamp_offset; if (ev_frame_time <= start + cnt) dst.write(ev_frame_time - negative_stamp_offset, ev_type, ev_size, ev_buffer); @@ -493,7 +246,7 @@ SMFSource::write_unlocked (MidiRingBuffer& src, nframes_t cnt) _model->append(ev); } - fflush(_fd); + SMF::flush(); free(buf); const nframes_t oldlen = _length; @@ -520,8 +273,8 @@ SMFSource::append_event_unlocked(EventTimeUnit unit, const Evoral::Event& ev) assert(ev.time() >= 0); - if (ev.time() < _last_ev_time) { - cerr << "SMFSource: Warning: Skipping event with ev.time() < _last_ev_time" << endl; + if (ev.time() < last_event_time()) { + cerr << "SMFSource: Warning: Skipping event with ev.time() < last.time()" << endl; return; } @@ -533,22 +286,15 @@ SMFSource::append_event_unlocked(EventTimeUnit unit, const Evoral::Event& ev) _session.engine().frame_rate(), _session.tempo_map().meter_at(_timeline_position)); - delta_time = (uint32_t)((ev.time() - _last_ev_time) / frames_per_beat * _ppqn); + delta_time = (uint32_t)((ev.time() - last_event_time()) / frames_per_beat * ppqn()); } else { assert(unit == Beats); - delta_time = (uint32_t)((ev.time() - _last_ev_time) * _ppqn); + delta_time = (uint32_t)((ev.time() - last_event_time()) * ppqn()); } - - const size_t stamp_size = write_var_len(delta_time); - fwrite(ev.buffer(), 1, ev.size(), _fd); + SMF::append_event_unlocked(delta_time, ev); - _track_size += stamp_size + ev.size(); _write_data_count += ev.size(); - _last_ev_time = ev.time(); - - if (ev.size() > 0) - _empty = false; } @@ -601,8 +347,7 @@ void SMFSource::mark_streaming_midi_write_started (NoteMode mode, nframes_t start_frame) { MidiSource::mark_streaming_midi_write_started (mode, start_frame); - _last_ev_time = 0; - fseek(_fd, _header_size, SEEK_SET); + SMF::begin_write (start_frame); } void @@ -615,8 +360,7 @@ SMFSource::mark_streaming_write_completed () } _model->set_edited(false); - flush_header(); - flush_footer(); + SMF::end_write (); } void @@ -855,56 +599,6 @@ SMFSource::set_source_name (string newname, bool destructive) return 0;//rename_peakfile (peak_path (_path)); } -bool -SMFSource::is_empty () const -{ - return _empty; -} - - -void -SMFSource::write_chunk_header(const char id[4], uint32_t length) -{ - const uint32_t length_be = GUINT32_TO_BE(length); - - fwrite(id, 1, 4, _fd); - fwrite(&length_be, 4, 1, _fd); -} - -void -SMFSource::write_chunk(const char id[4], uint32_t length, void* data) -{ - write_chunk_header(id, length); - - fwrite(data, 1, length, _fd); -} - -/** Returns the size (in bytes) of the value written. */ -size_t -SMFSource::write_var_len(uint32_t value) -{ - size_t ret = 0; - - uint32_t buffer = value & 0x7F; - - while ( (value >>= 7) ) { - buffer <<= 8; - buffer |= ((value & 0x7F) | 0x80); - } - - while (true) { - //printf("Writing var len byte %X\n", (unsigned char)buffer); - ++ret; - fputc(buffer, _fd); - if (buffer & 0x80) - buffer >>= 8; - else - break; - } - - return ret; -} - void SMFSource::load_model(bool lock, bool force_reload) { @@ -927,8 +621,7 @@ SMFSource::load_model(bool lock, bool force_reload) } _model->start_write(); - - fseek(_fd, _header_size, SEEK_SET); + SMF::seek_to_start(); uint64_t time = 0; /* in SMF ticks */ Evoral::Event ev; @@ -951,7 +644,7 @@ SMFSource::load_model(bool lock, bool force_reload) if (ret > 0) { // didn't skip (meta) event // make ev.time absolute time in frames - ev.time() = time * frames_per_beat / (EventTime)_ppqn; + ev.time() = time * frames_per_beat / (EventTime)ppqn(); ev.set_event_type(EventTypeMap::instance().midi_event_type(buf[0])); _model->append(ev); } @@ -976,3 +669,9 @@ SMFSource::destroy_model() _model.reset(); } +void +SMFSource::flush_midi() +{ + SMF::end_write(); +} + diff --git a/libs/evoral/SConscript b/libs/evoral/SConscript index 2b18d59107..29d0dfcaf9 100644 --- a/libs/evoral/SConscript +++ b/libs/evoral/SConscript @@ -31,6 +31,8 @@ src/Curve.cpp src/Event.cpp src/MIDIEvent.cpp src/Note.cpp +src/SMF.cpp +src/SMFReader.cpp src/Sequence.cpp """) diff --git a/libs/evoral/evoral/SMF.hpp b/libs/evoral/evoral/SMF.hpp new file mode 100644 index 0000000000..e0ac91b515 --- /dev/null +++ b/libs/evoral/evoral/SMF.hpp @@ -0,0 +1,83 @@ +/* This file is part of Evoral. + * Copyright(C) 2008 Dave Robillard + * Copyright(C) 2000-2008 Paul Davis + * + * Evoral 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. + * + * Evoral 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVORAL_SMF_HPP +#define EVORAL_SMF_HPP + +#include +#include + +namespace Evoral { + +class Event; +class EventRingBuffer; + + +/** Standard Midi File (Type 0) + */ +class SMF { +public: + SMF(); + virtual ~SMF(); + + void seek_to_start() const; + + uint16_t ppqn() const { return _ppqn; } + bool is_empty() const { return _empty; } + bool eof() const { return feof(_fd); } + + EventTime last_event_time() const { return _last_ev_time; } + + void begin_write(nframes_t start_time); + void append_event_unlocked(uint32_t delta_t, const Evoral::Event& ev); + void end_write(); + + void flush(); + int flush_header(); + int flush_footer(); + +protected: + int open(const std::string& path); + void close(); + + /** Used by flush_footer() to find the position to write the footer */ + void seek_to_footer_position(); + + /** Write the track footer at the current seek position */ + void write_footer(); + + void write_chunk_header(const char id[4], uint32_t length); + void write_chunk(const char id[4], uint32_t length, void* data); + size_t write_var_len(uint32_t val); + uint32_t read_var_len() const; + int read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const; + +private: + static const uint16_t _ppqn = 19200; + + FILE* _fd; + EventTime _last_ev_time; ///< last frame time written, relative to source start + uint32_t _track_size; + uint32_t _header_size; ///< size of SMF header, including MTrk chunk header + bool _empty; ///< true iff file contains(non-empty) events +}; + +}; /* namespace Evoral */ + +#endif /* EVORAL_SMF_HPP */ + diff --git a/libs/evoral/evoral/SMFReader.hpp b/libs/evoral/evoral/SMFReader.hpp new file mode 100644 index 0000000000..b977f71dca --- /dev/null +++ b/libs/evoral/evoral/SMFReader.hpp @@ -0,0 +1,86 @@ +/* This file is part of Evoral. + * Copyright(C) 2008 Dave Robillard + * Copyright(C) 2000-2008 Paul Davis + * + * Evoral 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. + * + * Evoral 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef EVORAL_SMF_READER_HPP +#define EVORAL_SMF_READER_HPP + +#include +#include +#include +#include + +namespace Evoral { + + +/** 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; }; + + 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(); + + static uint32_t read_var_len(FILE* fd) throw (PrematureEOF); + +protected: + /** size of SMF header, including MTrk chunk header */ + static const uint32_t HEADER_SIZE = 22; + + std::string _filename; + FILE* _fd; + uint16_t _type; + uint16_t _ppqn; + uint16_t _num_tracks; + uint32_t _track; + uint32_t _track_size; +}; + + +} // namespace Evoral + +#endif // EVORAL_SMF_READER_HPP + diff --git a/libs/evoral/src/SMF.cpp b/libs/evoral/src/SMF.cpp new file mode 100644 index 0000000000..4165a331cb --- /dev/null +++ b/libs/evoral/src/SMF.cpp @@ -0,0 +1,362 @@ +/* This file is part of Evoral. + * Copyright (C) 2008 Dave Robillard + * Copyright (C) 2000-2008 Paul Davis + * + * Evoral 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. + * + * Evoral 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace Evoral { + +SMF::SMF() + : _fd(0) + , _last_ev_time(0) + , _track_size(4) // 4 bytes for the ever-present EOT event + , _header_size(22) + , _empty(true) +{ +} + +SMF::~SMF() +{ +} + +/** Attempt to open the SMF file for reading and writing. + * + * Currently SMF is always read/write. + * + * \return 0 on success + * -1 if the file can not be opened for reading, + * -2 if the file can not be opened for writing + */ +int +SMF::open(const std::string& path) +{ + //cerr << "Opening SMF file " << path() << " writeable: " << writable() << endl; + _fd = fopen(path.c_str(), "r+"); + + // File already exists + if (_fd) { + fseek(_fd, _header_size - 4, 0); + uint32_t track_size_be = 0; + fread(&track_size_be, 4, 1, _fd); + _track_size = GUINT32_FROM_BE(track_size_be); + _empty = _track_size > 4; + //cerr << "SMF - read track size " << _track_size << endl; + + // We're making a new file + } else { + _fd = fopen(path.c_str(), "w+"); + if (_fd == NULL) { + cerr << "ERROR: Can not open SMF file " << path << " for writing: " << + strerror(errno) << endl; + return -2; + } + _track_size = 4; + _empty = true; + + // Write a tentative header just to pad things out so writing happens in the right spot + flush_header(); + flush_footer(); + } + + return (_fd == 0) ? -1 : 0; +} + +void +SMF::close() +{ + if (_fd) { + flush_header(); + flush_footer(); + fclose(_fd); + _fd = NULL; + } +} + +void +SMF::seek_to_start() const +{ + fseek(_fd, _header_size, SEEK_SET); +} + +void +SMF::seek_to_footer_position() +{ + uint8_t buffer[4]; + + // Check if there is a track end marker at the end of the data + fseek(_fd, -4, SEEK_END); + size_t read_bytes = fread(buffer, sizeof(uint8_t), 4, _fd); + + if ((read_bytes == 4) + && buffer[0] == 0x00 + && buffer[1] == 0xFF + && buffer[2] == 0x2F + && buffer[3] == 0x00) { + // there is one, so overwrite it + fseek(_fd, -4, SEEK_END); + } else { + // there is none, so append + fseek(_fd, 0, SEEK_END); + } +} + +void +SMF::flush() +{ + fflush(_fd); +} + +int +SMF::flush_header() +{ + // FIXME: write timeline position somehow? + + //cerr << path() << " SMF Flushing header\n"; + + assert(_fd); + + 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 quarter note (beat) + + char data[6]; + memcpy(data, &type, 2); + memcpy(data+2, &ntracks, 2); + memcpy(data+4, &division, 2); + + //_fd = freopen(path().c_str(), "r+", _fd); + //assert(_fd); + fseek(_fd, 0, SEEK_SET); + write_chunk("MThd", 6, data); + write_chunk_header("MTrk", _track_size); + + fflush(_fd); + + return 0; +} + +int +SMF::flush_footer() +{ + //cerr << path() << " SMF Flushing footer\n"; + seek_to_footer_position(); + write_footer(); + seek_to_footer_position(); + + return 0; +} + +void +SMF::write_footer() +{ + write_var_len(0); + char eot[3] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event + fwrite(eot, 1, 3, _fd); + fflush(_fd); +} + +/** Returns the offset of the first event in the file with a time past @a start, + * relative to the start of the source. + * + * Returns -1 if not found. + */ +/* +long +SMF::find_first_event_after(nframes_t start) +{ + // FIXME: obviously this is slooow + + fseek(_fd, _header_size, 0); + + while ( ! feof(_fd) ) { + const uint32_t delta_time = read_var_len(); + + if (delta_time > start) + return delta_time; + } + + return -1; +} +*/ + +/** 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, in SMF tempo-based ticks, using the + * rate given by ppqn() (it is the caller's responsibility to calculate a real time). + * + * \a size should be the capacity of \a buf. If it is not large enough, \a buf will + * be freed and a new buffer allocated in its place, the size of which will be placed + * in size. + * + * 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). + */ +int +SMF::read_event(uint32_t* delta_t, uint32_t* size, uint8_t** buf) const +{ + if (feof(_fd)) { + return -1; + } + + assert(delta_t); + assert(size); + assert(buf); + + try { + *delta_t = SMFReader::read_var_len(_fd); + } catch (...) { + return -1; // Premature EOF + } + + if (feof(_fd)) { + return -1; // Premature EOF + } + + const int status = fgetc(_fd); + + if (status == EOF) { + return -1; // Premature EOF + } + + //printf("Status @ %X = %X\n", (unsigned)ftell(_fd) - 1, status); + + if (status == 0xFF) { + if (feof(_fd)) { + return -1; // Premature EOF + } + const int type = fgetc(_fd); + if ((unsigned char)type == 0x2F) { + return -1; // hit end of track + } else { + *size = 0; + return 0; + } + } + + const int event_size = midi_event_size((unsigned char)status) + 1; + if (event_size <= 0) { + *size = 0; + return 0; + } + + // Make sure we have enough scratch buffer + if (*size < (unsigned)event_size) + *buf = (uint8_t*)realloc(*buf, event_size); + + *size = event_size; + + (*buf)[0] = (unsigned char)status; + if (event_size > 1) + fread((*buf) + 1, 1, *size - 1, _fd); + + /*printf("SMF %s read event: delta = %u, size = %u, data = ", _name.c_str(), *delta_t, *size); + for (size_t i=0; i < *size; ++i) { + printf("%X ", (*buf)[i]); + } + printf("\n");*/ + + return (int)*size; +} + +void +SMF::append_event_unlocked(uint32_t delta_t, const Evoral::Event& ev) +{ + if (ev.size() == 0) + return; + + const size_t stamp_size = write_var_len(delta_t); + fwrite(ev.buffer(), 1, ev.size(), _fd); + + _track_size += stamp_size + ev.size(); + _last_ev_time = ev.time(); + + if (ev.size() > 0) + _empty = false; +} + +void +SMF::begin_write(nframes_t start_frame) +{ + _last_ev_time = 0; + fseek(_fd, _header_size, SEEK_SET); +} + +void +SMF::end_write() +{ + flush_header(); + flush_footer(); +} + +void +SMF::write_chunk_header(const char id[4], uint32_t length) +{ + const uint32_t length_be = GUINT32_TO_BE(length); + + fwrite(id, 1, 4, _fd); + fwrite(&length_be, 4, 1, _fd); +} + +void +SMF::write_chunk(const char id[4], uint32_t length, void* data) +{ + write_chunk_header(id, length); + + fwrite(data, 1, length, _fd); +} + +/** Returns the size (in bytes) of the value written. */ +size_t +SMF::write_var_len(uint32_t value) +{ + size_t ret = 0; + + uint32_t buffer = value & 0x7F; + + while ( (value >>= 7) ) { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + } + + while (true) { + //printf("Writing var len byte %X\n", (unsigned char)buffer); + ++ret; + fputc(buffer, _fd); + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + + return ret; +} + +} // namespace Evoral diff --git a/libs/evoral/src/SMFReader.cpp b/libs/evoral/src/SMFReader.cpp new file mode 100644 index 0000000000..8da6f95c85 --- /dev/null +++ b/libs/evoral/src/SMFReader.cpp @@ -0,0 +1,283 @@ +/* This file is part of Evoral. + * Copyright(C) 2008 Dave Robillard + * Copyright(C) 2000-2008 Paul Davis + * + * Evoral 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. + * + * Evoral 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; + +namespace Evoral { + + +SMFReader::SMFReader(const string& filename) + : _fd(NULL) + , _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(); + + 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; + } 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); + + // Running status state + static uint8_t last_status = 0; + static uint32_t last_size = 0; + + *delta_time = read_var_len(_fd); + 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); + } else { + last_status = status; + *ev_size = midi_event_size(status) + 1; + last_size = *ev_size; + } + + 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(_fd); + /*cerr.flags(ios::hex); + cerr << "SMF - meta 0x" << (int)type << ", size = "; + cerr.flags(ios::dec); + cerr << size << endl;*/ + + if ((uint8_t)type == 0x2F) { + 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 << "SMF - 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(FILE* fd) 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 Evoral +