diff options
Diffstat (limited to 'muse2/muse/midifile.cpp')
-rw-r--r-- | muse2/muse/midifile.cpp | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/muse2/muse/midifile.cpp b/muse2/muse/midifile.cpp new file mode 100644 index 00000000..319152d7 --- /dev/null +++ b/muse2/muse/midifile.cpp @@ -0,0 +1,678 @@ +//========================================================= +// MusE +// Linux Music Editor +// $Id: midifile.cpp,v 1.17 2004/06/18 08:36:43 wschweer Exp $ +// +// (C) Copyright 1999-2003 Werner Schweer (ws@seh.de) +//========================================================= + +#include <errno.h> +#include <values.h> +#include <assert.h> + +#include "song.h" +#include "midi.h" +#include "midifile.h" +#include "drummap.h" +#include "event.h" +#include "globals.h" +#include "midictrl.h" +#include "marker/marker.h" +#include "midiport.h" +#include "midictrl.h" +#include "mpevent.h" +#include "gconfig.h" + +const char* errString[] = { + "no Error", + "unexpected EOF", + "read Error", + "write Error", + "bad midifile: 'MTrk' expected", + "bad midifile: 'MThd' expected", + "bad midi fileformat", + }; + +enum ERROR { + MF_NO_ERROR, + MF_EOF, + MF_READ, + MF_WRITE, + MF_MTRK, + MF_MTHD, + MF_FORMAT, + }; + +//--------------------------------------------------------- +// error +//--------------------------------------------------------- + +QString MidiFile::error() + { + return QString(errString[_error]); + } + +//--------------------------------------------------------- +// MidiFile +//--------------------------------------------------------- + +MidiFile::MidiFile(FILE* f) + { + fp = f; + curPos = 0; + _mtype = MT_UNKNOWN; + _error = MF_NO_ERROR; + _tracks = new MidiFileTrackList; + } + +MidiFile::~MidiFile() + { + delete _tracks; + } + +//--------------------------------------------------------- +// read +// return true on error +//--------------------------------------------------------- + +bool MidiFile::read(void* p, size_t len) + { + for (;;) { + curPos += len; + size_t rv = fread(p, 1, len, fp); + if (rv == len) + return false; + if (feof(fp)) { + _error = MF_EOF; + return true; + } + _error = MF_READ; + return true; + } + return false; + } + +//--------------------------------------------------------- +// write +// return true on error +//--------------------------------------------------------- + +bool MidiFile::write(const void* p, size_t len) + { + size_t rv = fwrite(p, 1, len, fp); + if (rv == len) + return false; + _error = MF_WRITE; + return true; + } + +//--------------------------------------------------------- +// writeShort +// return true on error +//--------------------------------------------------------- + +bool MidiFile::writeShort(int i) + { + short format = BE_SHORT(i); + return write(&format, 2); + } + +//--------------------------------------------------------- +// writeLong +// return true on error +//--------------------------------------------------------- + +bool MidiFile::writeLong(int i) + { + int format = BE_LONG(i); + return write(&format, 4); + } + +//--------------------------------------------------------- +// readShort +//--------------------------------------------------------- + +int MidiFile::readShort() + { + short format; + read(&format, 2); + return BE_SHORT(format); + } + +//--------------------------------------------------------- +// readLong +// writeLong +//--------------------------------------------------------- + +int MidiFile::readLong() + { + int format; + read(&format, 4); + return BE_LONG(format); + } + +/*--------------------------------------------------------- + * skip + * This is meant for skipping a few bytes in a + * file or fifo. + *---------------------------------------------------------*/ + +bool MidiFile::skip(size_t len) + { + char tmp[len]; + return read(tmp, len); + } + +/*--------------------------------------------------------- + * getvl + * Read variable-length number (7 bits per byte, MSB first) + *---------------------------------------------------------*/ + +int MidiFile::getvl() + { + int l = 0; + for (int i = 0; i < 16; i++) { + uchar c; + if (read(&c, 1)) + return -1; + l += (c & 0x7f); + if (!(c & 0x80)) + return l; + l <<= 7; + } + return -1; + } + +/*--------------------------------------------------------- + * putvl + * Write variable-length number (7 bits per byte, MSB first) + *---------------------------------------------------------*/ + +void MidiFile::putvl(unsigned val) + { + unsigned long buf = val & 0x7f; + while ((val >>= 7) > 0) { + buf <<= 8; + buf |= 0x80; + buf += (val & 0x7f); + } + for (;;) { + put(buf); + if (buf & 0x80) + buf >>= 8; + else + break; + } + } + +//--------------------------------------------------------- +// readTrack +// return true on error +//--------------------------------------------------------- + +bool MidiFile::readTrack(MidiFileTrack* t) + { + MPEventList* el = &(t->events); + char tmp[4]; + if (read(tmp, 4)) + return true; + if (memcmp(tmp, "MTrk", 4)) { + _error = MF_MTRK; + return true; + } + int len = readLong(); // len + int endPos = curPos + len; + status = -1; + sstatus = -1; // running status, not reset scanning meta or sysex + click = 0; + + int port = 0; + int channel = 0; + + for (;;) { + MidiPlayEvent event; + lastport = -1; + lastchannel = -1; + + int rv = readEvent(&event, t); + if (lastport != -1) { + port = lastport; + if (port >= MIDI_PORTS) { + printf("port %d >= %d, reset to 0\n", port, MIDI_PORTS); + port = 0; + } + } + if (lastchannel != -1) { + channel = lastchannel; + if (channel >= MIDI_CHANNELS) { + printf("channel %d >= %d, reset to 0\n", port, MIDI_CHANNELS); + channel = 0; + } + } + if (rv == 0) + break; + else if (rv == -1) + continue; + else if (rv == -2) // error + return true; + + event.setPort(port); + if (event.type() == ME_SYSEX || event.type() == ME_META) + event.setChannel(channel); + else + channel = event.channel(); + el->add(event); + } + int end = curPos; + if (end != endPos) { + printf("MidiFile::readTrack(): TRACKLEN does not fit %d+%d != %d, %d too much\n", + endPos-len, len, end, endPos-end); + if (end < endPos) + skip(endPos - end); + } + return false; + } + +//--------------------------------------------------------- +// readEvent +// returns: +// 0 End of track +// -1 Event filtered +// -2 Error +//--------------------------------------------------------- + +int MidiFile::readEvent(MidiPlayEvent* event, MidiFileTrack* t) + { + uchar me, type, a, b; + + int nclick = getvl(); + if (nclick == -1) { + printf("readEvent: error 1\n"); + return 0; + } + click += nclick; + for (;;) { + if (read(&me, 1)) { + printf("readEvent: error 2\n"); + return 0; + } + if (me >= 0xf8 && me <= 0xfe) + printf("Midi: Real Time Message 0x%02x??\n", me & 0xff); + else + break; + } + + event->setTime(click); + int len; + unsigned char* buffer; + + if ((me & 0xf0) == 0xf0) { + if (me == 0xf0 || me == 0xf7) { + // + // SYSEX + // + status = -1; // no running status + len = getvl(); + if (len == -1) { + printf("readEvent: error 3\n"); + return -2; + } + buffer = new unsigned char[len]; + if (read(buffer, len)) { + printf("readEvent: error 4\n"); + delete[] buffer; + return -2; + } + if (buffer[len-1] != 0xf7) { + printf("SYSEX endet nicht mit 0xf7!\n"); + // Forstsetzung folgt? + } + else + --len; // don't count 0xf7 + event->setType(ME_SYSEX); + event->setData(buffer, len); + if (((unsigned)len == gmOnMsgLen) && memcmp(buffer, gmOnMsg, gmOnMsgLen) == 0) { + setMType(MT_GM); + return -1; + } + if (((unsigned)len == gsOnMsgLen) && memcmp(buffer, gsOnMsg, gsOnMsgLen) == 0) { + setMType(MT_GS); + return -1; + } + if (((unsigned)len == xgOnMsgLen) && memcmp(buffer, xgOnMsg, xgOnMsgLen) == 0) { + setMType(MT_XG); + return -1; + } + if (buffer[0] == 0x41) { // Roland + if (mtype() != MT_UNKNOWN) + setMType(MT_GS); + } + else if (buffer[0] == 0x43) { // Yamaha + if (mtype() == MT_UNKNOWN || mtype() == MT_GM) + setMType(MT_XG); + int type = buffer[1] & 0xf0; + switch (type) { + case 0x00: // bulk dump + buffer[1] = 0; + break; + case 0x10: + if (buffer[1] != 0x10) { + buffer[1] = 0x10; // fix to Device 1 + } + if (len == 7 && buffer[2] == 0x4c && buffer[3] == 0x08 && buffer[5] == 7) { + // part mode + // 0 - normal + // 1 - DRUM + // 2 - DRUM 1 + // 3 - DRUM 2 + // 4 - DRUM 3 + // 5 - DRUM 4 + printf("xg set part mode channel %d to %d\n", buffer[4]+1, buffer[6]); + if (buffer[6] != 0) + t->isDrumTrack = true; + } + break; + case 0x20: + printf("YAMAHA DUMP REQUEST\n"); + return -1; + case 0x30: + printf("YAMAHA PARAMETER REQUEST\n"); + return -1; + default: + printf("YAMAHA unknown SYSEX: data[2]=%02x\n", buffer[1]); + return -1; + } + } + return 3; + } + if (me == 0xff) { + // + // META + // + status = -1; // no running status + if (read(&type, 1)) { // read type + printf("readEvent: error 5\n"); + return -2; + } + len = getvl(); // read len + if (len == -1) { + printf("readEvent: error 6\n"); + return -2; + } + buffer = new unsigned char[len+1]; + if (len) { + if (read(buffer, len)) { + printf("readEvent: error 7\n"); + delete[] buffer; + return -2; + } + } + buffer[len] = 0; + switch(type) { + case 0x21: // switch port + lastport = buffer[0]; + delete[] buffer; + return -1; + case 0x20: // switch channel + lastchannel = buffer[0]; + delete[] buffer; + return -1; + case 0x2f: // End of Track + delete[] buffer; + return 0; + default: + event->setType(ME_META); + event->setData(buffer, len+1); + event->setA(type); + return 3; + } + } + else { + printf("Midi: unknown Message 0x%02x\n", me & 0xff); + return -1; + } + } + + if (me & 0x80) { // status byte + status = me; + sstatus = status; + if (read(&a, 1)) { + printf("readEvent: error 9\n"); + return -2; + } + a &= 0x7F; + } + else { + if (status == -1) { + printf("readEvent: no running status, read 0x%02x sstatus %x\n", me, sstatus); + if (sstatus == -1) + return -1; + status = sstatus; + } + a = me; + } + b = 0; + switch (status & 0xf0) { + case ME_NOTEOFF: + case ME_NOTEON: + case ME_POLYAFTER: + case ME_CONTROLLER: + case ME_PITCHBEND: + if (read(&b, 1)) { + printf("readEvent: error 15\n"); + return -2; + } + event->setB(b & 0x80 ? 0 : b); + break; + case ME_PROGRAM: + case ME_AFTERTOUCH: + break; + default: // f1 f2 f3 f4 f5 f6 f7 f8 f9 + printf("BAD STATUS 0x%02x, me 0x%02x\n", status, me); + return -2; + } + event->setA(a & 0x7f); + event->setType(status & 0xf0); + event->setChannel(status & 0xf); + if ((a & 0x80) || (b & 0x80)) { + printf("8'tes Bit in Daten(%02x %02x): tick %d read 0x%02x status:0x%02x\n", + a & 0xff, b & 0xff, click, me, status); + printf("readEvent: error 16\n"); + if (b & 0x80) { + // Try to fix: interpret as channel byte + status = b & 0xf0; + sstatus = status; + return 3; + } + return -1; + } + if (event->type() == ME_PITCHBEND) { + int val = (event->dataB() << 7) + event->dataA(); + val -= 8192; + event->setA(val); + } + return 3; + } + +//--------------------------------------------------------- +// writeTrack +//--------------------------------------------------------- + +bool MidiFile::writeTrack(const MidiFileTrack* t) + { + //FIXME: By T356 01/19/2010 + // If saving as a compressed file (gz or bz2), + // the file is a pipe, and pipes can't seek ! + // This results in a corrupted midi file. + // So exporting compressed midi has been disabled (elsewhere) + // for now... + + const MPEventList* events = &(t->events); + write("MTrk", 4); + int lenpos = ftell(fp); + writeLong(0); // dummy len + + status = -1; + int tick = 0; + for (iMPEvent i = events->begin(); i != events->end(); ++i) { + int ntick = i->time(); + if (ntick < tick) { + printf("MidiFile::writeTrack: ntick %d < tick %d\n", ntick, tick); + ntick = tick; + } + putvl(((ntick - tick) * config.midiDivision + config.division/2)/config.division); + tick = ntick; + writeEvent(&(*i)); + } + + //--------------------------------------------------- + // write "End Of Track" Meta + // write Track Len + // + + putvl(0); + put(0xff); // Meta + put(0x2f); // EOT + putvl(0); // len 0 + int endpos = ftell(fp); + fseek(fp, lenpos, SEEK_SET); + writeLong(endpos-lenpos-4); // tracklen + fseek(fp, endpos, SEEK_SET); + return false; + } + +//--------------------------------------------------------- +// writeEvent +//--------------------------------------------------------- + +void MidiFile::writeEvent(const MidiPlayEvent* event) + { + int c = event->channel(); + int nstat = event->type(); + + // we dont save meta data into smf type 0 files: + + if (config.smfFormat == 0 && nstat == ME_META) + return; + + nstat |= c; + // + // running status; except for Sysex- and Meta Events + // + if (((nstat & 0xf0) != 0xf0) && (nstat != status)) { + status = nstat; + put(nstat); + } + switch (event->type()) { + case ME_NOTEOFF: + case ME_NOTEON: + case ME_POLYAFTER: + case ME_CONTROLLER: + case ME_PITCHBEND: + put(event->dataA()); + put(event->dataB()); + break; + case ME_PROGRAM: // Program Change + case ME_AFTERTOUCH: // Channel Aftertouch + put(event->dataA()); + break; + case ME_SYSEX: + put(0xf0); + putvl(event->len() + 1); // including 0xf7 + write(event->data(), event->len()); + put(0xf7); + status = -1; // invalidate running status + break; + case ME_META: + put(0xff); + put(event->dataA()); + putvl(event->len()); + write(event->data(), event->len()); + status = -1; + break; + } + } + +//--------------------------------------------------------- +// write +// returns true on error +//--------------------------------------------------------- + +bool MidiFile::write() + { + write("MThd", 4); + writeLong(6); // header len + writeShort(config.smfFormat); + if (config.smfFormat == 0) { + writeShort(1); + MidiFileTrack dst; + for (iMidiFileTrack i = _tracks->begin(); i != _tracks->end(); ++i) { + MPEventList* sl = &((*i)->events); + for (iMPEvent ie = sl->begin(); ie != sl->end(); ++ie) + dst.events.add(*ie); + } + writeShort(1); + writeShort(_division); + writeTrack(&dst); + } + else { + writeShort(ntracks); + + writeShort(_division); + for (ciMidiFileTrack i = _tracks->begin(); i != _tracks->end(); ++i) + writeTrack(*i); + } + return (ferror(fp) != 0); + } + +//--------------------------------------------------------- +// readMidi +// returns true on error +//--------------------------------------------------------- + +bool MidiFile::read() + { + _error = MF_NO_ERROR; + int i; + char tmp[4]; + + if (read(tmp, 4)) + return true; + int len = readLong(); + if (memcmp(tmp, "MThd", 4) || len < 6) { + _error = MF_MTHD; + return true; + } + format = readShort(); + ntracks = readShort(); + _division = readShort(); + + if (_division < 0) + _division = (-(_division/256)) * (_division & 0xff); + if (len > 6) + skip(len-6); // skip excess bytes + + switch (format) { + case 0: + { + MidiFileTrack* t = new MidiFileTrack; + _tracks->push_back(t); + if (readTrack(t)) + return true; + } + break; + case 1: + for (i = 0; i < ntracks; i++) { + MidiFileTrack* t = new MidiFileTrack; + _tracks->push_back(t); + if (readTrack(t)) + return true; + } + break; + default: + _error = MF_FORMAT; + return true; + } + return false; + } + |