summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2008-05-23 03:41:45 +0000
committerDavid Robillard <d@drobilla.net>2008-05-23 03:41:45 +0000
commit6ebefb4c8b523ecac35b7440907ef9e57d0154f0 (patch)
treecb2f25b531b9b38a642767a7fd21c5f285ca4623
parent264ce503aebaa87d275f23d1ea1a33034083b190 (diff)
downloadraul-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.hpp114
-rw-r--r--raul/SMFReader.hpp43
-rw-r--r--raul/midi_names.h94
-rw-r--r--src/SMFReader.cpp90
-rw-r--r--tests/smf_test.cpp2
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;