diff options
author | David Robillard <d@drobilla.net> | 2008-05-23 03:41:45 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2008-05-23 03:41:45 +0000 |
commit | 6ebefb4c8b523ecac35b7440907ef9e57d0154f0 (patch) | |
tree | cb2f25b531b9b38a642767a7fd21c5f285ca4623 | |
parent | 264ce503aebaa87d275f23d1ea1a33034083b190 (diff) | |
download | raul-6ebefb4c8b523ecac35b7440907ef9e57d0154f0.tar.gz raul-6ebefb4c8b523ecac35b7440907ef9e57d0154f0.tar.bz2 raul-6ebefb4c8b523ecac35b7440907ef9e57d0154f0.zip |
Merge improvemennts to MIDI stuff from Ardour.
git-svn-id: http://svn.drobilla.net/lad/raul@1227 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r-- | raul/RingBuffer.hpp | 114 | ||||
-rw-r--r-- | raul/SMFReader.hpp | 43 | ||||
-rw-r--r-- | raul/midi_names.h | 94 | ||||
-rw-r--r-- | src/SMFReader.cpp | 90 | ||||
-rw-r--r-- | tests/smf_test.cpp | 2 |
5 files changed, 209 insertions, 134 deletions
diff --git a/raul/RingBuffer.hpp b/raul/RingBuffer.hpp index 4098a2f..dff1464 100644 --- a/raul/RingBuffer.hpp +++ b/raul/RingBuffer.hpp @@ -1,5 +1,5 @@ /* This file is part of Raul. - * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * Copyright (C) 2007-2008 Dave Robillard <http://drobilla.net> * * Raul 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 @@ -19,6 +19,7 @@ #define RAUL_RING_BUFFER_HPP #include <cassert> +#include <iostream> #include <glib.h> namespace Raul { @@ -62,7 +63,7 @@ public: if (w > r) { return ((r - w + _size) % _size) - 1; - } else if(w < r) { + } else if (w < r) { return (r - w) - 1; } else { return _size - 1; @@ -83,20 +84,65 @@ public: size_t capacity() const { return _size; } + size_t peek(size_t size, T* dst); + bool full_peek(size_t size, T* dst); + size_t read(size_t size, T* dst); - void write(size_t size, const T* src); - bool full_read(size_t size, T* dst); + bool skip(size_t size); + + void write(size_t size, const T* src); + protected: - mutable gint _write_ptr; - mutable gint _read_ptr; + mutable int _write_ptr; + mutable int _read_ptr; size_t _size; ///< Size (capacity) in bytes - T* _buf; ///< size, event, size, event... + T* _buf; ///< size, event, size, event... }; +/** Peek at the ringbuffer (read w/o advancing read pointer). + * + * Note that a full read may not be done if the data wraps around. + * Caller must check return value and call again if necessary, or use the + * full_peek method which does this automatically. + */ +template<typename T> +size_t +RingBuffer<T>::peek(size_t size, T* dst) +{ + const size_t priv_read_ptr = g_atomic_int_get(&_read_ptr); + + const size_t read_size = (priv_read_ptr + size < _size) + ? size + : _size - priv_read_ptr; + + memcpy(dst, &_buf[priv_read_ptr], read_size); + + return read_size; +} + + +template<typename T> +bool +RingBuffer<T>::full_peek(size_t size, T* dst) +{ + if (read_space() < size) { + return false; + } + + const size_t read_size = peek(size, dst); + + if (read_size < size) { + peek(size - read_size, dst + read_size); + } + + return true; +} + + /** Read from the ringbuffer. * * Note that a full read may not be done if the data wraps around. @@ -122,6 +168,40 @@ RingBuffer<T>::read(size_t size, T* dst) template<typename T> +bool +RingBuffer<T>::full_read(size_t size, T* dst) +{ + if (read_space() < size) { + return false; + } + + const size_t read_size = read(size, dst); + + if (read_size < size) { + read(size - read_size, dst + read_size); + } + + return true; +} + + +template<typename T> +bool +RingBuffer<T>::skip(size_t size) +{ + if (read_space() < size) { + std::cerr << "WARNING: Attempt to skip past end of MIDI ring buffer" << std::endl; + return false; + } + + const size_t priv_read_ptr = g_atomic_int_get(&_read_ptr); + g_atomic_int_set(&_read_ptr, (priv_read_ptr + size) % _size); + + return true; +} + + +template<typename T> inline void RingBuffer<T>::write(size_t size, const T* src) { @@ -129,33 +209,17 @@ RingBuffer<T>::write(size_t size, const T* src) if (priv_write_ptr + size <= _size) { memcpy(&_buf[priv_write_ptr], src, size); - g_atomic_int_set(&_write_ptr, (priv_write_ptr + size) % _size); + g_atomic_int_set(&_write_ptr, (priv_write_ptr + size) % _size); } else { const size_t this_size = _size - priv_write_ptr; assert(this_size < size); assert(priv_write_ptr + this_size <= _size); memcpy(&_buf[priv_write_ptr], src, this_size); memcpy(&_buf[0], src+this_size, size - this_size); - g_atomic_int_set(&_write_ptr, size - this_size); + g_atomic_int_set(&_write_ptr, size - this_size); } } - -template<typename T> -bool -RingBuffer<T>::full_read(size_t size, T* dst) -{ - if (read_space() < size) - return false; - - const size_t read_size = read(size, dst); - - if (read_size < size) - read(size - read_size, dst + read_size); - - return true; -} - } // namespace Raul diff --git a/raul/SMFReader.hpp b/raul/SMFReader.hpp index 7ce6f5b..1bd1f0d 100644 --- a/raul/SMFReader.hpp +++ b/raul/SMFReader.hpp @@ -32,39 +32,48 @@ namespace Raul { */ class SMFReader { public: - SMFReader(); + 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); + bool open(const std::string& filename) throw (std::logic_error, UnsupportedTime); bool seek_to_track(unsigned track) throw (std::logic_error); - TimeUnit unit() const { return _unit; } uint16_t type() const { return _type; } uint16_t ppqn() const { return _ppqn; } size_t num_tracks() { return _num_tracks; } - int read_event(size_t buf_len, - uint8_t* buf, - uint32_t* ev_size, - TimeStamp* ev_delta_time) throw (std::logic_error); + 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; - uint32_t read_var_len() const; - - std::string _filename; - FILE* _fd; - TimeUnit _unit; - uint16_t _type; - uint16_t _ppqn; - uint16_t _num_tracks; - uint32_t _track; - uint32_t _track_size; + std::string _filename; + FILE* _fd; + uint16_t _type; + uint16_t _ppqn; + uint16_t _num_tracks; + uint32_t _track; + uint32_t _track_size; }; diff --git a/raul/midi_names.h b/raul/midi_names.h index 1b98f9a..114c299 100644 --- a/raul/midi_names.h +++ b/raul/midi_names.h @@ -81,69 +81,69 @@ inline static const char* midi_name(uint8_t status) return "Reset"; break; case MIDI_CTL_MSB_BANK: - return "Bank Selection"; break; + return "Bank Select (Coarse)"; break; case MIDI_CTL_MSB_MODWHEEL: - return "Modulation"; break; + return "Modulation (Coarse)"; break; case MIDI_CTL_MSB_BREATH: - return "Breath"; break; + return "Breath (Coarse)"; break; case MIDI_CTL_MSB_FOOT: - return "Foot"; break; + return "Foot (Coarse)"; break; case MIDI_CTL_MSB_PORTAMENTO_TIME: - return "Portamento Time"; break; + return "Portamento Time (Coarse)"; break; case MIDI_CTL_MSB_DATA_ENTRY: - return "Data Entry"; break; + return "Data Entry (Coarse)"; break; case MIDI_CTL_MSB_MAIN_VOLUME: - return "Main Volume"; break; + return "Main Volume (Coarse)"; break; case MIDI_CTL_MSB_BALANCE: - return "Balance"; break; + return "Balance (Coarse)"; break; case MIDI_CTL_MSB_PAN: - return "Panpot"; break; + return "Pan (Coarse)"; break; case MIDI_CTL_MSB_EXPRESSION: - return "Expression"; break; + return "Expression (Coarse)"; break; case MIDI_CTL_MSB_EFFECT1: - return "Effect1"; break; + return "Effect 1 (Coarse)"; break; case MIDI_CTL_MSB_EFFECT2: - return "Effect2"; break; + return "Effect 2 (Coarse)"; break; case MIDI_CTL_MSB_GENERAL_PURPOSE1: - return "General Purpose 1"; break; + return "General Purpose 1 (Coarse)"; break; case MIDI_CTL_MSB_GENERAL_PURPOSE2: - return "General Purpose 2"; break; + return "General Purpose 2 (Coarse)"; break; case MIDI_CTL_MSB_GENERAL_PURPOSE3: - return "General Purpose 3"; break; + return "General Purpose 3 (Coarse)"; break; case MIDI_CTL_MSB_GENERAL_PURPOSE4: - return "General Purpose 4"; break; + return "General Purpose 4 (Coarse)"; break; case MIDI_CTL_LSB_BANK: - return "Bank Selection"; break; + return "Bank Select (Fine)"; break; case MIDI_CTL_LSB_MODWHEEL: - return "Modulation"; break; + return "Modulation (Fine)"; break; case MIDI_CTL_LSB_BREATH: - return "Breath"; break; + return "Breath (Fine)"; break; case MIDI_CTL_LSB_FOOT: - return "Foot"; break; + return "Foot (Fine)"; break; case MIDI_CTL_LSB_PORTAMENTO_TIME: - return "Portamento Time"; break; + return "Portamento Time (Fine)"; break; case MIDI_CTL_LSB_DATA_ENTRY: - return "Data Entry"; break; + return "Data Entry (Fine)"; break; case MIDI_CTL_LSB_MAIN_VOLUME: - return "Main Volume"; break; + return "Main Volume (Fine)"; break; case MIDI_CTL_LSB_BALANCE: - return "Balance"; break; + return "Balance (Fine)"; break; case MIDI_CTL_LSB_PAN: - return "Panpot"; break; + return "Pan (Fine)"; break; case MIDI_CTL_LSB_EXPRESSION: - return "Expression"; break; + return "Expression (Fine)"; break; case MIDI_CTL_LSB_EFFECT1: - return "Effect1"; break; + return "Effect 1 (Fine)"; break; case MIDI_CTL_LSB_EFFECT2: - return "Effect2"; break; + return "Effect 2 (Fine)"; break; case MIDI_CTL_LSB_GENERAL_PURPOSE1: - return "General Purpose 1"; break; + return "General Purpose 1 (Fine)"; break; case MIDI_CTL_LSB_GENERAL_PURPOSE2: - return "General Purpose 2"; break; + return "General Purpose 2 (Fine)"; break; case MIDI_CTL_LSB_GENERAL_PURPOSE3: - return "General Purpose 3"; break; + return "General Purpose 3 (Fine)"; break; case MIDI_CTL_LSB_GENERAL_PURPOSE4: - return "General Purpose 4"; break; + return "General Purpose 4 (Fine)"; break; case MIDI_CTL_SUSTAIN: return "Sustain Pedal"; break; case MIDI_CTL_PORTAMENTO: @@ -157,25 +157,25 @@ inline static const char* midi_name(uint8_t status) case MIDI_CTL_HOLD2: return "Hold2"; break; case MIDI_CTL_SC1_SOUND_VARIATION: - return "SC1 Sound Variation"; break; + return "Sound Variation"; break; case MIDI_CTL_SC2_TIMBRE: - return "SC2 Timbre"; break; + return "Sound Timbre"; break; case MIDI_CTL_SC3_RELEASE_TIME: - return "SC3 Release Time"; break; + return "Sound Release Time"; break; case MIDI_CTL_SC4_ATTACK_TIME: - return "SC4 Attack Time"; break; + return "Sound Attack Time"; break; case MIDI_CTL_SC5_BRIGHTNESS: - return "SC5 Brightness"; break; + return "Sound Brightness"; break; case MIDI_CTL_SC6: - return "SC6"; break; + return "Sound Control 6"; break; case MIDI_CTL_SC7: - return "SC7"; break; + return "Sound Control 7"; break; case MIDI_CTL_SC8: - return "SC8"; break; + return "Sound Control 8"; break; case MIDI_CTL_SC9: - return "SC9"; break; + return "Sound Control 9"; break; case MIDI_CTL_SC10: - return "SC10"; break; + return "Sound Control 10"; break; case MIDI_CTL_GENERAL_PURPOSE5: return "General Purpose 5"; break; case MIDI_CTL_GENERAL_PURPOSE6: @@ -187,15 +187,15 @@ inline static const char* midi_name(uint8_t status) case MIDI_CTL_PORTAMENTO_CONTROL: return "Portamento Control"; break; case MIDI_CTL_E1_REVERB_DEPTH: - return "E1 Reverb Depth"; break; + return "Reverb Depth"; break; case MIDI_CTL_E2_TREMOLO_DEPTH: - return "E2 Tremolo Depth"; break; + return "Tremolo Depth"; break; case MIDI_CTL_E3_CHORUS_DEPTH: - return "E3 Chorus Depth"; break; + return "Chorus Depth"; break; case MIDI_CTL_E4_DETUNE_DEPTH: - return "E4 Detune Depth"; break; + return "Detune Depth"; break; case MIDI_CTL_E5_PHASER_DEPTH: - return "E5 Phaser Depth"; break; + return "Phaser Depth"; break; case MIDI_CTL_DATA_INCREMENT: return "Data Increment"; break; case MIDI_CTL_DATA_DECREMENT: diff --git a/src/SMFReader.cpp b/src/SMFReader.cpp index e2fd24b..d96340f 100644 --- a/src/SMFReader.cpp +++ b/src/SMFReader.cpp @@ -71,13 +71,14 @@ midi_event_size(unsigned char status) } -SMFReader::SMFReader() +SMFReader::SMFReader(const string filename) : _fd(NULL) - , _unit(TimeUnit::BEATS, 192) , _ppqn(0) , _track(0) , _track_size(0) { + if (filename.length() > 0) + open(filename); } @@ -87,14 +88,14 @@ SMFReader::~SMFReader() close(); } - + bool -SMFReader::open(const string& filename) +SMFReader::open(const string& filename) throw (logic_error, UnsupportedTime) { if (_fd) throw logic_error("Attempt to start new read while write in progress."); - cerr << "Opening SMF file " << filename << " for reading." << endl; + cout << "Opening SMF file " << filename << " for reading." << endl; _fd = fopen(filename.c_str(), "r+"); @@ -105,12 +106,11 @@ SMFReader::open(const string& filename) mthd[4] = '\0'; fread(mthd, 1, 4, _fd); if (strcmp(mthd, "MThd")) { - cerr << "File is not an SMF file, aborting." << endl; + 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); @@ -124,11 +124,13 @@ SMFReader::open(const string& filename) _num_tracks = GUINT16_FROM_BE(num_tracks_be); // Read PPQN (bytes 12..13) - // FIXME: broken for time-based divisions uint16_t ppqn_be = 0; fread(&ppqn_be, 2, 1, _fd); _ppqn = GUINT16_FROM_BE(ppqn_be); - _unit = TimeUnit::beats(_ppqn); + + // TODO: Absolute (SMPTE seconds) time support + if ((_ppqn & 0x8000) != 0) + throw UnsupportedTime(); seek_to_track(1); @@ -145,7 +147,8 @@ SMFReader::open(const string& filename) bool SMFReader::seek_to_track(unsigned track) throw (std::logic_error) { - assert(track > 0); + 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."); @@ -153,7 +156,7 @@ SMFReader::seek_to_track(unsigned track) throw (std::logic_error) unsigned track_pos = 0; fseek(_fd, 14, SEEK_SET); - char id[5]; + char id[5]; id[4] = '\0'; uint32_t chunk_size = 0; @@ -162,7 +165,6 @@ SMFReader::seek_to_track(unsigned track) throw (std::logic_error) if (!strcmp(id, "MTrk")) { ++track_pos; - //std::cerr << "Found track " << track_pos << endl; } else { std::cerr << "Unknown chunk ID " << id << endl; } @@ -201,10 +203,11 @@ SMFReader::seek_to_track(unsigned track) throw (std::logic_error) * set to the actual size of the event. */ int -SMFReader::read_event(size_t buf_len, - uint8_t* buf, - uint32_t* ev_size, - TimeStamp* delta_time) throw (std::logic_error) +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"); @@ -218,45 +221,43 @@ SMFReader::read_event(size_t buf_len, 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 unsigned char last_status = 0; - static uint32_t last_size = 1234567; + static uint8_t last_status = 0; + static uint32_t last_size = 0; - *delta_time = read_var_len(); + *delta_time = read_var_len(_fd); int status = fgetc(_fd); - assert(status != EOF); // FIXME die gracefully - assert(status <= 0xFF); + 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] = (unsigned char)status; + buf[0] = (uint8_t)status; if (status == 0xFF) { *ev_size = 0; - assert(!feof(_fd)); - unsigned char type = fgetc(_fd); - const uint32_t size = read_var_len(); + 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 ((unsigned char)type == 0x2F) { - //cerr << "SMF - hit EOT" << endl; + if ((uint8_t)type == 0x2F) { return -1; // we hit the logical EOF anyway... } else { fseek(_fd, size, SEEK_CUR); @@ -265,13 +266,15 @@ SMFReader::read_event(size_t buf_len, } if (*ev_size > buf_len || *ev_size == 0 || feof(_fd)) { - //cerr << "Skipping event" << endl; + //cerr << "SMF - Skipping event" << endl; // Skip event, return 0 fseek(_fd, *ev_size - 1, SEEK_CUR); return 0; } else { // Read event, return size - assert(!ferror(_fd)); + if (ferror(_fd)) + throw CorruptFile(); + fread(buf+1, 1, *ev_size - 1, _fd); if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) { @@ -295,21 +298,20 @@ SMFReader::close() uint32_t -SMFReader::read_var_len() const +SMFReader::read_var_len(FILE* fd) throw (PrematureEOF) { - assert(!feof(_fd)); - - //int offset = ftell(_fd); - //cerr << "SMF - reading var len at " << offset << endl; + if (feof(fd)) + throw PrematureEOF(); uint32_t value; - unsigned char c; + uint8_t c; - if ( (value = getc(_fd)) & 0x80 ) { + if ( (value = getc(fd)) & 0x80 ) { value &= 0x7F; do { - assert(!feof(_fd)); - value = (value << 7) + ((c = getc(_fd)) & 0x7F); + if (feof(fd)) + throw PrematureEOF(); + value = (value << 7) + ((c = getc(fd)) & 0x7F); } while (c & 0x80); } diff --git a/tests/smf_test.cpp b/tests/smf_test.cpp index a123a61..3ac0894 100644 --- a/tests/smf_test.cpp +++ b/tests/smf_test.cpp @@ -44,7 +44,7 @@ main(int argc, char** argv) unsigned char buf[4]; uint32_t ev_size; - TimeStamp ev_delta_time(reader.unit()); + uint32_t ev_delta_time; while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) { cout << "Event, size = " << ev_size << ", time = " << ev_delta_time; |