//============================================================================= // MusE // Linux Music Editor // $Id:$ // // Copyright (C) 2002-2006 by Werner Schweer and others // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= #include "song.h" #include "midi.h" #include "midifile.h" #include "midiedit/drummap.h" #include "event.h" #include "globals.h" #include "midictrl.h" #include "midictrl.h" #include "midievent.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() { fp = 0; curPos = 0; _error = MF_NO_ERROR; _tracks = new MidiFileTrackList; _midiType = MT_GENERIC; } MidiFile::~MidiFile() { delete _tracks; } //--------------------------------------------------------- // read // return true on error //--------------------------------------------------------- bool MidiFile::read(void* p, size_t len) { for (;;) { curPos += len; qint64 rv = fp->read((char*)p, len); if (rv == len) return false; if (fp->atEnd()) { _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) { qint64 rv = fp->write((char*)p, len); 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) { MidiEventList* 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 (;;) { MidiEvent event; lastport = -1; lastchannel = -1; int rv = readEvent(&event, t); if (lastport != -1) port = lastport; 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; //TODO3 event.setPort(port); if (event.type() == ME_SYSEX || event.type() == ME_META) event.setChannel(channel); else channel = event.channel(); el->insert(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(MidiEvent* 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 ((len == (signed)gmOnMsgLen) && memcmp(buffer, gmOnMsg, gmOnMsgLen) == 0) _midiType = MT_GM; else if ((len == (signed)gsOnMsgLen) && memcmp(buffer, gsOnMsg, gsOnMsgLen) == 0) _midiType = MT_GS; if (buffer[0] == 0x43) { // Yamaha _midiType = 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; } } else if (buffer[0] == 0x43) { if (_midiType != MT_XG) _midiType = MT_GS; } 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]; *buffer = 0; if (len) { if (read(buffer, len)) { printf("readEvent: error 7\n"); delete[] buffer; return -2; } buffer[len] = 0; } event->setType(ME_META); event->setData(buffer, len+1); event->setA(type); buffer[len] = 0; switch(type) { case 0x21: // switch port lastport = buffer[0]; return 3; case 0x20: // switch channel lastchannel = buffer[0]; return 3; case 0x2f: // End of Track delete[] buffer; return 0; default: 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) { // Meta events and sysex events cancel running status. // There are some midi files which do not send // status again after this events. Silently assume // old running status. if (debugMsg || sstatus == -1) printf("readEvent: no running status, read 0x%02x, old status 0x%02x\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) { const MidiEventList* events = &(t->events); write("MTrk", 4); int lenpos = fp->pos(); writeLong(0); // dummy len status = -1; int tick = 0; for (iMidiEvent 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 = fp->pos(); fp->seek(lenpos); writeLong(endpos-lenpos-4); // tracklen fp->seek(endpos); return false; } //--------------------------------------------------------- // writeEvent //--------------------------------------------------------- void MidiFile::writeEvent(const MidiEvent* 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(QFile* _fp) { fp = _fp; write("MThd", 4); writeLong(6); // header len writeShort(format); if (format == 0) { // ?? writeShort(1); MidiFileTrack dst; for (iMidiFileTrack i = _tracks->begin(); i != _tracks->end(); ++i) { MidiEventList* sl = &((*i)->events); for (iMidiEvent ie = sl->begin(); ie != sl->end(); ++ie) dst.events.insert(*ie); } writeShort(1); writeShort(_division); writeTrack(&dst); } else { writeShort(_tracks->size()); writeShort(_division); for (ciMidiFileTrack i = _tracks->begin(); i != _tracks->end(); ++i) writeTrack(*i); } return false; // (ferror(fp) != 0); } //--------------------------------------------------------- // readMidi // returns true on error //--------------------------------------------------------- bool MidiFile::read(QFile* _fp) { fp = _fp; _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; }