-rw-r--r-- | lib/.gitignore | 7 | ||||
-rw-r--r-- | lib/MThd.cc | 38 | ||||
-rw-r--r-- | lib/MTrk.cc | 34 | ||||
-rw-r--r-- | lib/Makefile.am | 15 | ||||
-rw-r--r-- | lib/SMF.cc | 62 | ||||
-rw-r--r-- | lib/chunk.cc | 31 | ||||
-rw-r--r-- | lib/event.cc | 58 | ||||
-rw-r--r-- | lib/message.cc | 247 | ||||
-rw-r--r-- | lib/util.cc | 96 |
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; + } + +} |