//========================================================= // 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) // (C) Copyright 2012 Tim E. Real (terminator356 on users dot sourceforge dot net) // // This program 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 Foundation; version 2 of // the License, or (at your option) any later version. // // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //========================================================= #include #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" namespace MusECore { 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; lastMtype = MT_UNKNOWN; _error = MF_NO_ERROR; _tracks = new MidiFileTrackList; _usedPortMap = new MidiFilePortMap; } MidiFile::~MidiFile() { delete _tracks; delete _usedPortMap; } //--------------------------------------------------------- // 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; lastMtype = MT_UNKNOWN; lastInstrName.clear(); lastDeviceName.clear(); 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(!lastDeviceName.isEmpty()) { iMidiFilePort iup = _usedPortMap->begin(); for( ; iup != _usedPortMap->end(); ++iup) { if(iup->second._subst4DevName == lastDeviceName) { port = iup->first; break; } } if(iup == _usedPortMap->end()) { MidiDevice* md = MusEGlobal::midiDevices.find(lastDeviceName); if(md) { int pn = md->midiPort(); if(pn != -1) port = pn; else { for(int i = 0; i < MIDI_PORTS; ++i) { iMidiFilePort ip = _usedPortMap->find(i); MidiPort* mp = &MusEGlobal::midiPorts[i]; if(!mp->device() && (ip == _usedPortMap->end() || ip->second._subst4DevName.isEmpty())) { //mp->setMidiDevice(); // No, done in importMidi //msgSetMidiDevice( port = i; break; } } } } } } iMidiFilePort iup = _usedPortMap->find(port); if(iup == _usedPortMap->end()) { MidiFilePort up; if(lastMtype != MT_UNKNOWN) up._midiType = lastMtype; if(!lastInstrName.isEmpty()) up._instrName = lastInstrName; if(!lastDeviceName.isEmpty()) up._subst4DevName = lastDeviceName; _usedPortMap->insert(std::pair(port, up)); } else { if(lastMtype != MT_UNKNOWN) iup->second._midiType = lastMtype; if(!lastInstrName.isEmpty()) iup->second._instrName = lastInstrName; if(!lastDeviceName.isEmpty()) iup->second._subst4DevName = lastDeviceName; } 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 can be deleted by caller's event when it goes out of scope. 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 doesn't end with 0xf7!\n"); // to be continued? } else --len; // don't count 0xf7 event->setType(ME_SYSEX); event->setData(buffer, len); if (((unsigned)len == gmOnMsgLen) && memcmp(buffer, gmOnMsg, gmOnMsgLen) == 0) { lastMtype = MT_GM; return -1; } if (((unsigned)len == gm2OnMsgLen) && memcmp(buffer, gm2OnMsg, gm2OnMsgLen) == 0) { lastMtype = MT_GM2; return -1; } if (((unsigned)len == gsOnMsgLen) && memcmp(buffer, gsOnMsg, gsOnMsgLen) == 0) { lastMtype = MT_GS; return -1; } if (((unsigned)len == xgOnMsgLen) && memcmp(buffer, xgOnMsg, xgOnMsgLen) == 0) { lastMtype = MT_XG; return -1; } if (buffer[0] == 0x41) { // Roland lastMtype = MT_GS; } else if (buffer[0] == 0x43) { // Yamaha lastMtype = 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; } } if(MusEGlobal::debugMsg) printf("MidiFile::readEvent: unknown Sysex 0x%02x unabsorbed, passing thru intead\n", me & 0xff); 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 ME_META_TEXT_9_DEVICE_NAME: // device name lastDeviceName = QString((const char*)buffer); delete[] buffer; return -1; case ME_META_TEXT_4_INSTRUMENT_NAME: // instrument name lastInstrName = QString((const char*)buffer); delete[] buffer; return -1; case ME_META_PORT_CHANGE: // switch port lastport = buffer[0]; delete[] buffer; return -1; case ME_META_CHANNEL_CHANGE: // switch channel lastchannel = buffer[0]; delete[] buffer; return -1; case ME_META_END_OF_TRACK: // End of Track delete[] buffer; return 0; default: if(MusEGlobal::debugMsg) printf("MidiFile::readEvent: unknown Meta 0x%x %d unabsorbed, passing thru instead\n", type, type); 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) * MusEGlobal::config.midiDivision + MusEGlobal::config.division/2)/MusEGlobal::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: DELETETHIS 4 ??? // Oct 16, 2011: Apparently it is legal to do that. Part of fix for bug tracker 3293339. //if (MusEGlobal::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(MusEGlobal::config.smfFormat); if (MusEGlobal::config.smfFormat == 0) { // DELETETHIS 30 /* // REMOVE Tim. MidiFileTrack dst; for (iMidiFileTrack i = _tracks->begin(); i != _tracks->end(); ++i) { MPEventList* sl = &((*i)->events); for (iMPEvent ie = sl->begin(); ie != sl->end(); ++ie) { // ALERT: Observed a problem here, apparently some of the events are being added too fast. // The dump below tells me some of the events (sysex/meta) are missing from the list! // Apparently it's a timing problem. Very puzzling. // Attempting wild-guess fix now to eliminate multiple MidiFileTracks in MusE::exportMidi()... // Nope. Didn't help. Now that it's a single MidiFileTrack, try skipping this section altogether... // Yes that appears to have fixed it. Weird. What's the difference - the local 'dst' variable ? // Or are there still lurking problems, or something more fundamentally wrong with Event or MPEvent? printf("MidiFile::write adding event to dst:\n"); ie->dump(); dst.events.add(*ie); } } writeShort(1); writeShort(_division); writeTrack(&dst); */ writeShort(1); //writeShort(_division); DELETETHIS 3 //if(!_tracks->empty()) // writeTrack(*(_tracks->begin())); } 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; } } // namespace MusECore