summaryrefslogtreecommitdiffabout
path: root/lib
Side-by-side diff
Diffstat (limited to 'lib') (more/less context) (show whitespace changes)
-rw-r--r--lib/.gitignore7
-rw-r--r--lib/MThd.cc38
-rw-r--r--lib/MTrk.cc34
-rw-r--r--lib/Makefile.am15
-rw-r--r--lib/SMF.cc62
-rw-r--r--lib/chunk.cc31
-rw-r--r--lib/event.cc58
-rw-r--r--lib/message.cc247
-rw-r--r--lib/util.cc96
9 files changed, 588 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..dc5e416
--- a/dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,7 @@
+*.lo
+*.o
+Makefile.in
+.libs
+.deps
+Makefile
+*.la
diff --git a/lib/MThd.cc b/lib/MThd.cc
new file mode 100644
index 0000000..110c98a
--- a/dev/null
+++ b/lib/MThd.cc
@@ -0,0 +1,38 @@
+#include <midillo/MThd.h>
+#include <midillo/util.h>
+#include <midillo/exception.h>
+
+namespace midillo {
+ using std::endl;
+
+ void MThd_t::load(istream& s) {
+ header.load(s);
+ if(header.id_number!=chunk_id_MThd)
+ throw exception_unexpected_input(CODEPOINT,"MThd chunk expected");
+ if(header.length!=6)
+ throw exception_invalid_input(CODEPOINT,"MThd chunk is not 6 bytes long");
+ load_data(s);
+ }
+
+ void MThd_t::load_data(istream& s) {
+ fmt = read16(s);
+ ntracks = read16(s);
+ division = read16(s);
+ }
+
+ void MThd_t::save(ostream& s) const {
+ header.save(s);
+ write16(s,fmt);
+ write16(s,ntracks);
+ write16(s,division);
+ }
+
+ void MThd_t::dump(ostream& s) const {
+ std::ios::fmtflags ff = s.flags();
+ s.unsetf(std::ios::hex); s.setf(std::ios::dec);
+ s << " " << header << endl
+ << " fmt=" << fmt << ", " << ntracks << " track(s), division=" << division << endl;
+ s.flags(ff);
+ }
+
+}
diff --git a/lib/MTrk.cc b/lib/MTrk.cc
new file mode 100644
index 0000000..fa1e0f8
--- a/dev/null
+++ b/lib/MTrk.cc
@@ -0,0 +1,34 @@
+#include <algorithm>
+#include <iterator>
+#include <midillo/MTrk.h>
+#include <midillo/exception.h>
+
+namespace midillo {
+ using std::copy;
+ using std::ostream_iterator;
+ using std::endl;
+
+ void MTrk_t::load(istream& s) {
+ header.load(s);
+ if(header.id_number!=chunk_id_MTrk)
+ throw exception_unexpected_input(CODEPOINT,"MTrk chunk expected");
+ events.load(s);
+ }
+
+ void MTrk_t::save(ostream& s) const {
+ chunk_header_t h = header;
+ h.id_number = chunk_id_MTrk;
+ h.length = events.calculate_save_size();
+ h.save(s);
+ events.save(s);
+ }
+
+ void MTrk_t::dump(ostream& s) const {
+ s << " " << header << endl
+ << " ";
+ copy(
+ events.begin(), events.end(),
+ ostream_iterator<event_t>(s,"\n ") );
+ }
+
+}
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..de49c97
--- a/dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,15 @@
+lib_LTLIBRARIES = libmidillo.la
+
+INCLUDES = -I${top_srcdir}/include -I${top_srcdir}
+AM_CXXFLAGS = ${KONFORKA_CFLAGS}
+LDADD = ${KONFORKA_LIBS}
+
+libmidillo_la_SOURCES = \
+ util.cc \
+ SMF.cc \
+ chunk.cc \
+ MThd.cc MTrk.cc \
+ event.cc \
+ message.cc
+
+libmidillo_la_LDFLAGS = -version-info 0:0:0
diff --git a/lib/SMF.cc b/lib/SMF.cc
new file mode 100644
index 0000000..ba3179d
--- a/dev/null
+++ b/lib/SMF.cc
@@ -0,0 +1,62 @@
+#include <iostream>
+#include <fstream>
+#include <algorithm>
+#include <iterator>
+#include <midillo/SMF.h>
+
+namespace midillo {
+ using std::ifstream;
+ using std::ofstream;
+ using std::cin;
+ using std::cout;
+ using std::copy;
+ using std::ostream_iterator;
+ using std::endl;
+
+ void SMF_t::load(const char *f,bool stdinable) {
+ if(stdinable && !strcmp(f,"-")) {
+ load(cin);
+ }else{
+ ifstream s(f,std::ios::in|std::ios::binary);
+ load(s);
+ }
+ }
+
+ void SMF_t::load(istream& s) {
+ mthd.load(s);
+ tracks.resize(mthd.ntracks);
+ tracks_t::iterator i = tracks.begin();
+ for(int t=0;t<mthd.ntracks;++t,++i) {
+ i->load(s);
+ }
+ }
+
+ void SMF_t::save(const char *f,bool stdoutable) const {
+ if(stdoutable && !strcmp(f,"-")) {
+ save(cout);
+ }else{
+ ofstream s(f,std::ios::out|std::ios::trunc|std::ios::binary);
+ save(s);
+ }
+ }
+
+ void SMF_t::save(ostream& s) const {
+ mthd.save(s);
+ for(tracks_t::const_iterator i=tracks.begin();i!=tracks.end();++i) {
+ i->save(s);
+ }
+ }
+
+ void SMF_t::dump(ostream& s) const {
+ std::ios::fmtflags ff = s.flags();
+ s.unsetf(std::ios::hex); s.setf(std::ios::dec);
+ s
+ << "SMF with " << tracks.size() << " track(s)" << endl
+ << mthd << endl;
+ copy(
+ tracks.begin(), tracks.end(),
+ ostream_iterator<MTrk_t>(s,"\n") );
+ s.flags(ff);
+ }
+
+}
diff --git a/lib/chunk.cc b/lib/chunk.cc
new file mode 100644
index 0000000..7cc15ff
--- a/dev/null
+++ b/lib/chunk.cc
@@ -0,0 +1,31 @@
+#include <midillo/chunk.h>
+#include <midillo/util.h>
+#include <midillo/exception.h>
+
+namespace midillo {
+
+ void chunk_header_t::load(istream& s) {
+ s.read((char*)id_chars,sizeof(id_chars));
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading chunk header");
+ length = read32(s);
+ }
+
+ void chunk_header_t::save(ostream& s) const {
+ s.write((char*)id_chars,sizeof(id_chars));
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing chunk header");
+ write32(s,length);
+ }
+
+ void chunk_header_t::dump(ostream& s) const {
+ std::ios::fmtflags ff = s.flags();
+ s.unsetf(std::ios::hex); s.setf(std::ios::dec);
+ s
+ << id_chars[0] << id_chars[1]
+ << id_chars[2] << id_chars[3]
+ << " chunk of " << length << " byte(s)";
+ s.flags(ff);
+ }
+
+}
diff --git a/lib/event.cc b/lib/event.cc
new file mode 100644
index 0000000..39438fa
--- a/dev/null
+++ b/lib/event.cc
@@ -0,0 +1,58 @@
+#include <midillo/event.h>
+#include <midillo/util.h>
+
+namespace midillo {
+
+ void event_t::load(int& rs,istream& s) {
+ deltat = readVL(s);
+ message.load(rs,s);
+ }
+
+ void event_t::save(int& rs,ostream& s) const {
+ writeVL(s,deltat);
+ message.save(rs,s);
+ }
+
+ unsigned long event_t::calculate_save_size(int& rs) const {
+ return calcVLsize(deltat) + message.calculate_save_size(rs);
+ }
+
+ void event_t::dump(ostream& s) const {
+ std::ios::fmtflags ff = s.flags();
+ s.unsetf(std::ios::hex); s.setf(std::ios::dec);
+ s << "deltat=" << deltat << " [" << message << "]";
+ s.flags(ff);
+ }
+
+
+ events_t::iterator events_t::append_event() {
+ static event_t empty;
+ return insert(end(),empty);
+ }
+
+ void events_t::load(istream& s) {
+ int rs = -1;
+ for(;;) {
+ iterator i=append_event();
+ i->load(rs,s);
+ if(i->message.is_meta(meta_EOT))
+ break;
+ }
+ }
+
+ void events_t::save(ostream& s) const {
+ int rs = -1;
+ for(const_iterator i=begin();i!=end();++i) {
+ i->save(rs,s);
+ }
+ }
+
+ unsigned long events_t::calculate_save_size() const {
+ unsigned long rv = 0;
+ int rs = -1;
+ for(const_iterator i=begin();i!=end();++i) {
+ rv += i->calculate_save_size(rs);
+ }
+ return rv;
+ }
+}
diff --git a/lib/message.cc b/lib/message.cc
new file mode 100644
index 0000000..8f9e68a
--- a/dev/null
+++ b/lib/message.cc
@@ -0,0 +1,247 @@
+#include <algorithm>
+#include <iterator>
+#include <midillo/message.h>
+#include <midillo/util.h>
+#include <midillo/exception.h>
+
+namespace midillo {
+ using std::copy;
+ using std::ostream_iterator;
+
+ unsigned long message_t::calculate_save_size(int& rs) const {
+ unsigned long rv = 0;
+ if(status!=rs) {
+ ++rv;
+ rs = status;
+ }else if((status&status_event_bits)==status_system) {
+ rs = -1;
+ ++rv; // XXX: is it really needed?
+ }
+ switch(status&status_event_bits) {
+ case status_note_off:
+ case status_note_on:
+ case status_polyphonic_key_pressure: // aka status_aftertouch
+ case status_control_change:
+ case status_pitch_wheel_change:
+ rv += 2; break;
+ case status_program_change:
+ case status_channel_pressure:
+ ++rv; break;
+ case status_system:
+ switch(status&status_system_bits) {
+ case status_system_sysex:
+ case status_system_end_of_sysex:
+ rv += data.size()+1; break;
+ case status_system_MTC_quarter_frame:
+ case status_system_song_select:
+ ++rv; break;
+ case status_system_song_position_pointer:
+ rv += 2; break;
+ case status_system_tune_request:
+ case status_system_timing_clock: // aka status_system_midi_clock
+ case status_system_midi_tick:
+ case status_system_start: // aka status_system_midi_start
+ case status_system_stop: // aka status_system_midi_stop
+ case status_system_continue: // aka status_system_midi_continue
+ case status_system_active_sense:
+ break; /* XXX: ensure there is no data? */
+ case status_system_meta: // also reset, but not for the purpose of midi file
+ ++rv;
+ rv += calcVLsize(data.size());
+ rv += data.size();
+ break;
+ default:
+ throw exception(CODEPOINT,"Internal error");
+ break;
+ }
+ break;
+ default:
+ throw exception(CODEPOINT,"Internal error");
+ break;
+ }
+ return rv;
+ }
+
+ void message_t::save(int& rs,ostream& s) const {
+ if(status!=rs) {
+ s.put(status);
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing midi status byte");
+ rs = status;
+ }else if((status&status_event_bits)==status_system) {
+ rs = -1;
+ s.put(status); // XXX: is it really needed?
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing midi system status byte");
+ }
+ switch(status&status_event_bits) {
+ case status_note_off:
+ case status_note_on:
+ case status_polyphonic_key_pressure: // aka status_aftertouch
+ case status_control_change:
+ case status_pitch_wheel_change:
+ save_data(s,2); break;
+ case status_program_change:
+ case status_channel_pressure:
+ save_data(s,1); break;
+ case status_system:
+ switch(status&status_system_bits) {
+ case status_system_sysex:
+ case status_system_end_of_sysex:
+ save_sysex(s); break;
+ case status_system_MTC_quarter_frame:
+ case status_system_song_select:
+ save_data(s,1); break;
+ case status_system_song_position_pointer:
+ save_data(s,2); break;
+ case status_system_tune_request:
+ case status_system_timing_clock: // aka status_system_midi_clock
+ case status_system_midi_tick:
+ case status_system_start: // aka status_system_midi_start
+ case status_system_stop: // aka status_system_midi_stop
+ case status_system_continue: // aka status_system_midi_continue
+ case status_system_active_sense:
+ break; /* XXX: ensure there is no data? */
+ case status_system_meta: // also reset, but not for the purpose of midi file
+ s.put(meta_status&0xFF);
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing meta event");
+ writeVL(s,data.size());
+ save_data(s);
+ break;
+ default:
+ throw exception(CODEPOINT,"Internal error");
+ break;
+ }
+ break;
+ default:
+ throw exception(CODEPOINT,"Internal error");
+ break;
+ }
+ }
+
+ void message_t::load(int& rs,istream& s) {
+ data.clear();
+ status = s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading MIDI event status byte");
+ if(status&status_bit) {
+ if((status&status_event_bits)!=status_system)
+ rs = status;
+ }else{
+ if(rs<0)
+ throw exception_invalid_input(CODEPOINT,"Attempt to rely on the absent running status");
+ data.push_back(status);
+ status = rs;
+ }
+ switch(status&status_event_bits) {
+ case status_note_off:
+ case status_note_on:
+ case status_polyphonic_key_pressure: // a.k.a. status_aftertouch
+ case status_control_change:
+ case status_pitch_wheel_change:
+ load_data(s,2); break;
+ case status_program_change:
+ case status_channel_pressure:
+ load_data(s,1); break;
+ case status_system:
+ switch(status&status_system_bits) {
+ case status_system_sysex:
+ case status_system_end_of_sysex:
+ load_sysex(s); break;
+ case status_system_MTC_quarter_frame:
+ case status_system_song_select:
+ load_data(s,1); break;
+ case status_system_song_position_pointer:
+ load_data(s,2); break;
+ case status_system_tune_request:
+ case status_system_timing_clock: // a.k.a. status_system_midi_clock
+ case status_system_midi_tick:
+ case status_system_start: // a.k.a. status_system_midi_start
+ case status_system_stop: // a.k.a. status_system_midi_stop
+ case status_system_continue: // a.k.a. status_system_midi_continue
+ case status_system_active_sense:
+ break;
+ case status_system_meta: // also, reset, but not in midi files
+ {
+ meta_status = s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading meta event type");
+ int l = readVL(s);
+ load_data(s,l);
+ }
+ break;
+ default:
+ throw exception(CODEPOINT,"Internal error");
+ break;
+ }
+ break;
+ default:
+ throw exception(CODEPOINT,"Internal error");
+ break;
+ }
+ }
+
+ void message_t::save_data(ostream& s,int c) const {
+ if(c!=data.size())
+ throw exception(CODEPOINT,"Writing corrupt data");
+ save_data(s);
+ }
+
+ void message_t::save_data(ostream& s) const {
+ for(bytes_t::const_iterator i=data.begin();i!=data.end();++i)
+ s.put(*i);
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing MIDI message data");
+ }
+
+ void message_t::load_data(istream& s,int c) {
+ c -= data.size();
+ if(c<0)
+ throw exception(CODEPOINT,"Internal error");
+ while(c-- > 0) {
+ data.push_back(s.get());
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading MIDI data");
+ }
+ }
+
+ void message_t::save_sysex(ostream& s) const {
+ save_data(s);
+ s.put(0xF7); /* XXX: Or is it better to put it into data? */
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing sysex data");
+ }
+
+ void message_t::load_sysex(istream& s) {
+ int b = s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading sysex data");
+ assert(!(b&0x80)); // manufacturer ought to be 7 bit. not sure if it belongs here, it may well be continuation.
+ do {
+ data.push_back(b);
+ b = s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading sysex data");
+ }while(b!=0xF7);
+ }
+
+ void message_t::dump(ostream& s) const {
+ std::ios::fmtflags ff = s.flags();
+ int w = s.width(2);
+ s.unsetf(std::ios::dec); s.setf(std::ios::hex);
+ s << "status=" << status;
+ if(is_meta()) {
+ s << ", meta_status=" << meta_status;
+ }
+ if(!data.empty()) {
+ s << ", data: ";
+ copy(
+ data.begin(), data.end(),
+ ostream_iterator<int>(s," ") );
+ }
+ s.width(w);
+ s.flags(ff);
+ }
+
+}
diff --git a/lib/util.cc b/lib/util.cc
new file mode 100644
index 0000000..4bbe6f3
--- a/dev/null
+++ b/lib/util.cc
@@ -0,0 +1,96 @@
+#include <midillo/util.h>
+#include <midillo/exception.h>
+
+namespace midillo {
+
+ unsigned long read32(istream& s) {
+ unsigned long rv = 0;
+ for(int t=0;t<4;++t) {
+ rv<<=8;
+ rv|=0xFF&s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Input error");
+ }
+ return rv;
+ }
+
+ unsigned int read16(istream& s) {
+ unsigned int rv = 0;
+ for(int t=0;t<2;++t) {
+ rv<<=8;
+ rv|=0xFF&s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Input error");
+ }
+ return rv;
+ }
+
+ unsigned long readVL(istream& s) {
+ unsigned long rv=s.get();
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading VLQ");
+ int b;
+ if(rv&0x80) {
+ rv&=0x7F;
+ do {
+ rv = (rv<<7)|( (b=s.get())&0x7F );
+ if(!s.good())
+ throw exception_input_error(CODEPOINT,"Error reading VLQ");
+ }while(b&0x80);
+ }
+ return rv;
+ }
+
+ void write32(ostream& s,unsigned long d) {
+ for(int t=3;t>=0;--t) {
+ s.put(((const char*)&d)[t]);
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Output error");
+ }
+ }
+
+ void write16(ostream& s,unsigned int d) {
+ for(int t=1;t>=0;--t) {
+ s.put(((const char*)&d)[t]);
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Output error");
+ }
+ }
+
+ void writeVL(ostream& s,unsigned long d) {
+ // TODO: I don't think this is perfectly written code
+ unsigned long tmp = d&0x7F;
+ while(d>>=7) {
+ tmp<<=8;
+ tmp |=((d&0x7F)|0x80);
+ }
+ for(;;) {
+ s.put(tmp&0xFF);
+ if(!s.good())
+ throw exception_output_error(CODEPOINT,"Error writing VLQ");
+ if(tmp&0x80)
+ tmp>>=8;
+ else
+ break;
+ }
+ }
+
+ unsigned long calcVLsize(unsigned long d) {
+ unsigned long rv = 0;
+ // TODO: imperfect code revisited
+ unsigned long tmp = d&0x7F;
+ while(d>>=7) {
+ tmp<<=8;
+ tmp |=((d&0x7F)|0x80);
+ }
+ for(;;) {
+ ++rv;
+ if(tmp&0x80)
+ tmp>>=8;
+ else
+ break;
+ }
+ return rv;
+ }
+
+}