MuseScore/miditools/midifile.cpp
2018-12-18 14:55:54 +01:00

531 lines
15 KiB
C++

//=============================================================================
// 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 <QFile>
#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<int, MidiEvent>(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<const int, qreal>(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;
}
}