//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2007-2011 Werner Schweer // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 // as published by the Free Software Foundation and appearing in // the file LICENCE.GPL //============================================================================= #include #include "midifile.h" //--------------------------------------------------------- // MidiFile //--------------------------------------------------------- MidiFile::MidiFile() { fp = 0; _format = 1; _division = 480; } //--------------------------------------------------------- // readMidi // return false on error //--------------------------------------------------------- bool MidiFile::read(const QString& path) { QFile f(path); if (!f.open(QIODevice::ReadOnly)) return false; return read(&f); } bool MidiFile::read(QIODevice* f) { fp = f; _tracks.clear(); curPos = 0; char tmp[4]; read(tmp, 4); int len = readLong(); if (memcmp(tmp, "MThd", 4) || len < 6) throw(QString("bad midifile: MThd expected")); _format = readShort(); int ntracks = readShort(); _division = readShort(); if (_division < 0) _division = (-(_division/256)) * (_division & 0xff); if (len > 6) skip(len-6); // skip the excess switch (_format) { case 0: if (readTrack()) return false; break; case 1: for (int i = 0; i < ntracks; i++) { if (readTrack()) return false; } break; default: throw(QString("midi file format %1 not implemented").arg(_format)); } return true; } //--------------------------------------------------------- // readTrack // return true on error //--------------------------------------------------------- bool MidiFile::readTrack() { char tmp[4]; read(tmp, 4); if (memcmp(tmp, "MTrk", 4)) throw(QString("bad midifile: MTrk expected")); int len = readLong(); // len qint64 endPos = curPos + len; status = -1; sstatus = -1; // running status, will not be reset on meta or sysex click = 0; MidiTrack* track = new MidiTrack(this); _tracks.push_back(track); for (;;) { MidiEvent event; int rv = readEvent(&event); if (rv == 0) track->events().insert(std::pair(click, event)); else if (rv == 2) break; } if (curPos != endPos) { qWarning("bad track len: %lld != %lld, %lld bytes too much\n", endPos, curPos, endPos - curPos); if (curPos < endPos) { qWarning(" skip %lld\n", endPos-curPos); skip(endPos - curPos); } } return false; } /*--------------------------------------------------------- * read * return false on error *---------------------------------------------------------*/ void MidiFile::read(void* p, qint64 len) { curPos += len; qint64 rv = fp->read((char*)p, len); if (rv != len) throw(QString("bad midifile: unexpected EOF")); } //--------------------------------------------------------- // readShort //--------------------------------------------------------- int MidiFile::readShort() { char c; int val = 0; for (int i = 0; i < 2; ++i) { fp->getChar(&c); val <<= 8; val += (c & 0xff); } return val; } //--------------------------------------------------------- // readLong // writeLong //--------------------------------------------------------- int MidiFile::readLong() { char c; int val = 0; for (int i = 0; i < 4; ++i) { fp->getChar(&c); val <<= 8; val += (c & 0xff); } return val; } /*--------------------------------------------------------- * skip * This is meant for skipping a few bytes in a * file or fifo. *---------------------------------------------------------*/ void MidiFile::skip(qint64 len) { if (len <= 0) return; char tmp[len]; 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; read(&c, 1); l += (c & 0x7f); if (!(c & 0x80)) { return l; } l <<= 7; } return -1; } //--------------------------------------------------------- // MidiTrack //--------------------------------------------------------- MidiTrack::MidiTrack(MidiFile* f) { mf = f; // _outChannel = -1; // _outPort = -1; // _drumTrack = false; // _hasKey = false; // _staffIdx = -1; } //--------------------------------------------------------- // readEvent // return 0 - valid event // 1 - skip event // 2 - EOT //--------------------------------------------------------- int MidiFile::readEvent(MidiEvent* event) { uchar me, a, b; int nclick = getvl(); if (nclick == -1) throw(QString("readEvent: error 1(getvl)")); click += nclick; for (;;) { read(&me, 1); if (me >= 0xf1 && me <= 0xfe && me != 0xf7) qDebug("Midi: Unknown Message 0x%02x", me & 0xff); else break; } uchar* data; int dataLen; if (me == 0xf0 || me == 0xf7) { status = -1; // no running status int len = getvl(); if (len == -1) throw(QString("readEvent: error 3")); data = new unsigned char[len+1]; dataLen = len; read(data, len); data[dataLen] = 0; // always terminate with zero if (data[len-1] != 0xf7) { qDebug("SYSEX does not end with 0xf7!"); // more to come? } else dataLen--; // don't count 0xf7 delete[] data; #if 0 event->setType(MidiEventType::SYSEX); event->setData(data); event->setLen(dataLen); event->setOntime(click); #endif return 1; } if (me == 0xff) { // MidiEventType::META) { status = -1; // no running status uchar type; read(&type, 1); dataLen = getvl(); // read len if (dataLen == -1) throw(QString("readEvent: error 6")); data = new unsigned char[dataLen + 1]; if (dataLen) read(data, dataLen); data[dataLen] = 0; // always terminate with zero so we get valid C++ strings if (type == META_TEMPO) { unsigned tempo = data[2] + (data[1] << 8) + (data[0] << 16); double t = 1000000.0 / double(tempo); _tempoMap.insert(std::pair(click, t)); } //else //printf("META %02x\n", type); delete[] data; if (type == META_EOT) return 2; return 1; } if (me & 0x80) { // status byte status = me; sstatus = status; read(&a, 1); } else { if (status == -1) { qDebug("readEvent: no running status, read 0x%02x", me); qDebug("sstatus ist 0x%02x", sstatus); if (sstatus == -1) return 0; status = sstatus; } a = me; } int channel = status & 0x0f; b = 0; MidiEventType t = MidiEventType(status & 0xf0); switch (t) { case MidiEventType::NOTEOFF: case MidiEventType::NOTEON: case MidiEventType::POLYAFTER: case MidiEventType::CONTROLLER: // controller case MidiEventType::PITCHBEND: // pitch bend read(&b, 1); default: break; } switch (t) { case MidiEventType::NOTEOFF: case MidiEventType::NOTEON: case MidiEventType::CONTROLLER: case MidiEventType::PITCHBEND: case MidiEventType::POLYAFTER: event->set(t, channel, a, b); break; case MidiEventType::PROGRAM: case MidiEventType::AFTERTOUCH: event->set(t, channel, a, 0); break; default: // f1 f2 f3 f4 f5 f6 f7 f8 f9 throw(QString("BAD STATUS")); // 0x%02x, me 0x%02x", status, me); } if ((a & 0x80) || (b & 0x80)) { qDebug("8't bit in data set(%02x %02x): tick %d read 0x%02x status:0x%02x", a & 0xff, b & 0xff, click, me, status); qDebug("readEvent: error 16"); if (b & 0x80) { // Try to fix: interpret as channel byte status = b; sstatus = status; return 0; } throw(QString("readEvent: error 16")); } return 0; } //--------------------------------------------------------- // write //--------------------------------------------------------- bool MidiFile::write(const QString& path) { QFile f(path); if (!f.open(QIODevice::WriteOnly)) return false; return write(&f); } bool MidiFile::write(QIODevice* f) { fp = f; write("MThd", 4); writeLong(6); // header len writeShort(_format); // format writeShort(_tracks.size()); writeShort(_division); foreach (const MidiTrack* t, _tracks) { if (writeTrack(t)) return true; } return false; } //--------------------------------------------------------- // write //--------------------------------------------------------- void MidiFile::writeEvent(const MidiEvent& event) { switch (event.type()) { case MidiEventType::NOTEON: writeStatus(event.type(), event.channel()); put(event.dataA() & 0x7f); put(event.dataB() & 0x7f); break; case MidiEventType::NOTEOFF: writeStatus(event.type(), event.channel()); put(event.dataA() & 0x7f); put(event.dataB() & 0x7f); break; case MidiEventType::CONTROLLER: writeStatus(event.type(), event.channel()); put(event.dataA() & 0x7f); put(event.dataB() & 0x7f); break; case MidiEventType::PROGRAM: writeStatus(event.type(), event.channel()); put(event.dataA() & 0x7f); break; #if 0 case MidieEventType::META: put(MidiEventType::META); put(event.metaType()); putvl(event.len()); write(event.edata(), event.len()); resetRunningStatus(); // really ?! break; case MidiEventType::SYSEX: put(MidiEventType::SYSEX); putvl(event.len() + 1); // including 0xf7 write(event.edata(), event.len()); put(MidiEventTppe::ENDSYSEX); resetRunningStatus(); break; #endif default: //fprintf(stderr, "unsupported\n"); break; } } //--------------------------------------------------------- // writeTrack //--------------------------------------------------------- bool MidiFile::writeTrack(const MidiTrack* t) { write("MTrk", 4); qint64 lenpos = fp->pos(); writeLong(0); // dummy len status = -1; int tick = 0; for (auto i : t->events()) { int ntick = i.first; putvl(ntick - tick); // write tick delta writeEvent(i.second); tick = ntick; } //--------------------------------------------------- // write "End Of Track" Meta // write Track Len // putvl(1); put(0xff); // Meta put(0x2f); // EOT putvl(0); // len 0 qint64 endpos = fp->pos(); fp->seek(lenpos); writeLong(endpos-lenpos-4); // tracklen fp->seek(endpos); return false; } //--------------------------------------------------------- // writeStatus //--------------------------------------------------------- void MidiFile::writeStatus(MidiEventType st, int c) { uchar nstat = uchar(st) | (c & 0xf); // // running status; except for Sysex- and Meta Events // if ((((nstat & 0xf0) != 0xf0) && (nstat != status))) { status = nstat; put(nstat); } } //--------------------------------------------------------- // write //--------------------------------------------------------- bool MidiFile::write(const void* p, qint64 len) { qint64 rv = fp->write((char*)p, len); if (rv == len) return false; qDebug("write midifile failed: %s", fp->errorString().toLatin1().data()); return true; } //--------------------------------------------------------- // writeShort //--------------------------------------------------------- void MidiFile::writeShort(int i) { fp->putChar(i >> 8); fp->putChar(i); } //--------------------------------------------------------- // writeLong //--------------------------------------------------------- void MidiFile::writeLong(int i) { fp->putChar(i >> 24); fp->putChar(i >> 16); fp->putChar(i >> 8); fp->putChar(i); } /*--------------------------------------------------------- * 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; } }