893 lines
30 KiB
C++
893 lines
30 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 "midifile.h"
|
|
#include "libmscore/xml.h"
|
|
#include "libmscore/part.h"
|
|
#include "libmscore/note.h"
|
|
#include "libmscore/drumset.h"
|
|
#include "libmscore/utils.h"
|
|
|
|
namespace Ms {
|
|
|
|
static const uchar gmOnMsg[] = {
|
|
0x7e, // Non-Real Time header
|
|
0x7f, // ID of target device (7f = all devices)
|
|
0x09,
|
|
0x01
|
|
};
|
|
static const uchar gsOnMsg[] = {
|
|
0x41, // roland id
|
|
0x10, // Id of target device (default = 10h for roland)
|
|
0x42, // model id (42h = gs devices)
|
|
0x12, // command id (12h = data set)
|
|
0x40, // address & value
|
|
0x00,
|
|
0x7f,
|
|
0x00,
|
|
0x41 // checksum?
|
|
};
|
|
static const uchar xgOnMsg[] = {
|
|
0x43, // yamaha id
|
|
0x10, // device number (0)
|
|
0x4c, // model id
|
|
0x00, // address (high, mid, low)
|
|
0x00,
|
|
0x7e,
|
|
0x00 // data
|
|
};
|
|
|
|
const int gmOnMsgLen = sizeof(gmOnMsg);
|
|
const int gsOnMsgLen = sizeof(gsOnMsg);
|
|
const int xgOnMsgLen = sizeof(xgOnMsg);
|
|
|
|
//---------------------------------------------------------
|
|
// MidiFile
|
|
//---------------------------------------------------------
|
|
|
|
MidiFile::MidiFile()
|
|
{
|
|
fp = 0;
|
|
_isDivisionInTps = false;
|
|
_format = 1;
|
|
_midiType = MidiType::UNKNOWN;
|
|
_noRunningStatus = false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
// returns true on error
|
|
//---------------------------------------------------------
|
|
|
|
bool MidiFile::write(QIODevice* out)
|
|
{
|
|
fp = out;
|
|
write("MThd", 4);
|
|
writeLong(6); // header len
|
|
writeShort(_format); // format
|
|
writeShort(_tracks.size());
|
|
writeShort(_division);
|
|
for (const auto &t: _tracks) {
|
|
if (writeTrack(t))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void MidiFile::writeEvent(const MidiEvent& event)
|
|
{
|
|
switch(event.type()) {
|
|
case ME_NOTEON:
|
|
writeStatus(ME_NOTEON, event.channel());
|
|
put(event.pitch());
|
|
put(event.velo());
|
|
break;
|
|
|
|
case ME_NOTEOFF:
|
|
writeStatus(ME_NOTEOFF, event.channel());
|
|
put(event.pitch());
|
|
put(event.velo());
|
|
break;
|
|
|
|
case ME_PITCHBEND:
|
|
writeStatus(ME_PITCHBEND, event.channel());
|
|
put(event.dataA());
|
|
put(event.dataB());
|
|
break;
|
|
|
|
case ME_CONTROLLER:
|
|
switch(event.controller()) {
|
|
case CTRL_PROGRAM:
|
|
writeStatus(ME_PROGRAM, event.channel());
|
|
put(event.value() & 0x7f);
|
|
break;
|
|
case CTRL_PRESS:
|
|
writeStatus(ME_AFTERTOUCH, event.channel());
|
|
put(event.value() & 0x7f);
|
|
break;
|
|
default:
|
|
writeStatus(ME_CONTROLLER, event.channel());
|
|
put(event.controller());
|
|
put(event.value() & 0x7f);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ME_META:
|
|
put(ME_META);
|
|
put(event.metaType());
|
|
putvl(event.len());
|
|
write(event.edata(), event.len());
|
|
resetRunningStatus(); // really ?!
|
|
break;
|
|
|
|
case ME_SYSEX:
|
|
put(ME_SYSEX);
|
|
putvl(event.len() + 1); // including 0xf7
|
|
write(event.edata(), event.len());
|
|
put(ME_ENDSYSEX);
|
|
resetRunningStatus();
|
|
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
|
|
//
|
|
// if track channel != -1, then use this
|
|
// channel for all events in this track
|
|
//
|
|
if (t.outChannel() != -1)
|
|
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(int nstat, int c)
|
|
{
|
|
nstat |= (c & 0xf);
|
|
//
|
|
// running status; except for Sysex- and Meta Events
|
|
//
|
|
if (_noRunningStatus || (((nstat & 0xf0) != 0xf0) && (nstat != status))) {
|
|
status = nstat;
|
|
put(nstat);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readMidi
|
|
// return false on error
|
|
//---------------------------------------------------------
|
|
|
|
bool MidiFile::read(QIODevice* in)
|
|
{
|
|
fp = in;
|
|
_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();
|
|
|
|
// ================ Read MIDI division =================
|
|
//
|
|
// 2 bytes
|
|
// +-------+---+-------------------+-----------------+
|
|
// | bit |15 | 14 8 | 7 0 |
|
|
// +-------+---+-------------------------------------+
|
|
// | | 0 | ticks per quarter note |
|
|
// | value +---+-------------------------------------+
|
|
// | | 1 | -frames/second | ticks/frame |
|
|
// +-------+---+-------------------+-----------------+
|
|
|
|
char firstByte;
|
|
fp->getChar(&firstByte);
|
|
char secondByte;
|
|
fp->getChar(&secondByte);
|
|
const char topBit = (firstByte & 0x80) >> 7;
|
|
|
|
if (topBit == 0) { // ticks per beat
|
|
_isDivisionInTps = false;
|
|
_division = (firstByte << 8) | (secondByte & 0xff);
|
|
}
|
|
else { // ticks per second = fps * ticks per frame
|
|
_isDivisionInTps = true;
|
|
const int framesPerSecond = -((signed char) firstByte);
|
|
const int ticksPerFrame = secondByte;
|
|
if (framesPerSecond == 29)
|
|
_division = qRound(29.97 * ticksPerFrame);
|
|
else
|
|
_division = framesPerSecond * ticksPerFrame;
|
|
}
|
|
|
|
// =====================================================
|
|
|
|
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 false;
|
|
}
|
|
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;
|
|
_tracks.push_back(MidiTrack());
|
|
|
|
int port = 0;
|
|
_tracks.back().setOutPort(port);
|
|
_tracks.back().setOutChannel(-1);
|
|
|
|
for (;;) {
|
|
MidiEvent event;
|
|
if (!readEvent(&event))
|
|
return true;
|
|
|
|
// check for end of track:
|
|
if ((event.type() == ME_META) && (event.metaType() == META_EOT))
|
|
break;
|
|
_tracks.back().insert(click, event);
|
|
}
|
|
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"));
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// writeShort
|
|
//---------------------------------------------------------
|
|
|
|
void MidiFile::writeShort(int i)
|
|
{
|
|
fp->putChar(i >> 8);
|
|
fp->putChar(i);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// writeLong
|
|
//---------------------------------------------------------
|
|
|
|
void MidiFile::writeLong(int i)
|
|
{
|
|
fp->putChar(i >> 24);
|
|
fp->putChar(i >> 16);
|
|
fp->putChar(i >> 8);
|
|
fp->putChar(i);
|
|
}
|
|
|
|
/*---------------------------------------------------------
|
|
* 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;
|
|
}
|
|
|
|
/*---------------------------------------------------------
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// MidiTrack
|
|
//---------------------------------------------------------
|
|
|
|
MidiTrack::MidiTrack()
|
|
{
|
|
_outChannel = -1;
|
|
_outPort = -1;
|
|
_drumTrack = false;
|
|
}
|
|
|
|
MidiTrack::~MidiTrack()
|
|
{
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// insert
|
|
//---------------------------------------------------------
|
|
|
|
void MidiTrack::insert(int tick, const MidiEvent& event)
|
|
{
|
|
_events.insert({tick, event});
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readEvent
|
|
// return true on success
|
|
//---------------------------------------------------------
|
|
|
|
bool MidiFile::readEvent(MidiEvent* event)
|
|
{
|
|
uchar me, a, b;
|
|
|
|
int nclick = getvl();
|
|
if (nclick == -1) {
|
|
qDebug("readEvent: error 1(getvl)");
|
|
return false;
|
|
}
|
|
click += nclick;
|
|
for (;;) {
|
|
read(&me, 1);
|
|
if (me >= 0xf1 && me <= 0xfe && me != 0xf7) {
|
|
qDebug("Midi: Unknown Message 0x%02x", me & 0xff);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
unsigned char* data;
|
|
int dataLen;
|
|
|
|
if (me == 0xf0 || me == 0xf7) {
|
|
status = -1; // no running status
|
|
int len = getvl();
|
|
if (len == -1) {
|
|
qDebug("readEvent: error 3");
|
|
return false;
|
|
}
|
|
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
|
|
event->setType(ME_SYSEX);
|
|
event->setEData(data);
|
|
event->setLen(dataLen);
|
|
return true;
|
|
}
|
|
|
|
if (me == ME_META) {
|
|
status = -1; // no running status
|
|
uchar type;
|
|
read(&type, 1);
|
|
dataLen = getvl(); // read len
|
|
if (dataLen == -1) {
|
|
qDebug("readEvent: error 6");
|
|
return false;
|
|
}
|
|
data = new unsigned char[dataLen + 1];
|
|
if (dataLen)
|
|
read(data, dataLen);
|
|
data[dataLen] = 0; // always terminate with zero so we get valid C++ strings
|
|
|
|
event->setType(ME_META);
|
|
event->setMetaType(type);
|
|
event->setLen(dataLen);
|
|
event->setEData(data);
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
switch (status & 0xf0) {
|
|
case ME_NOTEOFF:
|
|
case ME_NOTEON:
|
|
case ME_POLYAFTER:
|
|
case ME_CONTROLLER: // controller
|
|
case ME_PITCHBEND: // pitch bend
|
|
read(&b, 1);
|
|
break;
|
|
}
|
|
event->setType(status & 0xf0);
|
|
event->setChannel(channel);
|
|
switch (status & 0xf0) {
|
|
case ME_NOTEOFF:
|
|
event->setDataA(a & 0x7f);
|
|
event->setDataB(b & 0x7f);
|
|
break;
|
|
case ME_NOTEON:
|
|
event->setDataA(a & 0x7f);
|
|
event->setDataB(b & 0x7f);
|
|
break;
|
|
case ME_POLYAFTER:
|
|
event->setType(ME_CONTROLLER);
|
|
event->setController(CTRL_POLYAFTER);
|
|
event->setValue(((a & 0x7f) << 8) + (b & 0x7f));
|
|
break;
|
|
case ME_CONTROLLER: // controller
|
|
event->setController(a & 0x7f);
|
|
event->setValue(b & 0x7f);
|
|
break;
|
|
case ME_PITCHBEND: // pitch bend
|
|
event->setDataA(a & 0x7f);
|
|
event->setDataB(b & 0x7f);
|
|
break;
|
|
case ME_PROGRAM:
|
|
event->setValue(a & 0x7f);
|
|
break;
|
|
case ME_AFTERTOUCH:
|
|
event->setType(ME_CONTROLLER);
|
|
event->setController(CTRL_PRESS);
|
|
event->setValue(a & 0x7f);
|
|
break;
|
|
default: // f1 f2 f3 f4 f5 f6 f7 f8 f9
|
|
qDebug("BAD STATUS 0x%02x, me 0x%02x", status, me);
|
|
return false;
|
|
}
|
|
|
|
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 true;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setOutChannel
|
|
//---------------------------------------------------------
|
|
|
|
void MidiTrack::setOutChannel(int n)
|
|
{
|
|
_outChannel = n;
|
|
if (_outChannel == 9)
|
|
_drumTrack = true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// mergeNoteOnOffAndFindMidiType
|
|
// - find matching note on / note off events and merge
|
|
// into a note event with tick duration
|
|
// - find MIDI type
|
|
//---------------------------------------------------------
|
|
|
|
void MidiTrack::mergeNoteOnOffAndFindMidiType(MidiType *mt)
|
|
{
|
|
std::multimap<int, MidiEvent> el;
|
|
|
|
int hbank = 0xff;
|
|
int lbank = 0xff;
|
|
int rpnh = -1;
|
|
int rpnl = -1;
|
|
int datah = 0;
|
|
int datal = 0;
|
|
int dataType = 0; // 0 : disabled, 0x20000 : rpn, 0x30000 : nrpn;
|
|
|
|
for (auto i = _events.begin(); i != _events.end(); ++i) {
|
|
MidiEvent& ev = i->second;
|
|
if (ev.type() == ME_INVALID)
|
|
continue;
|
|
if ((ev.type() != ME_NOTEON) && (ev.type() != ME_NOTEOFF)) {
|
|
if (ev.type() == ME_CONTROLLER) {
|
|
int val = ev.value();
|
|
int cn = ev.controller();
|
|
if (cn == CTRL_HBANK) {
|
|
hbank = val;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else if (cn == CTRL_LBANK) {
|
|
lbank = val;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else if (cn == CTRL_HDATA) {
|
|
datah = val;
|
|
|
|
// check if a CTRL_LDATA follows
|
|
// e.g. wie have a 14 bit controller:
|
|
auto ii = i;
|
|
++ii;
|
|
bool found = false;
|
|
for (; ii != _events.end(); ++ii) {
|
|
MidiEvent& ev = ii->second;
|
|
if (ev.type() == ME_CONTROLLER) {
|
|
if (ev.controller() == CTRL_LDATA) {
|
|
// handle later
|
|
found = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
if (rpnh == -1 || rpnl == -1) {
|
|
qDebug("parameter number not defined, data 0x%x", datah);
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else {
|
|
ev.setController(dataType | (rpnh << 8) | rpnl);
|
|
ev.setValue(datah);
|
|
}
|
|
}
|
|
else {
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
}
|
|
else if (cn == CTRL_LDATA) {
|
|
datal = val;
|
|
|
|
if (rpnh == -1 || rpnl == -1) {
|
|
qDebug("parameter number not defined, data 0x%x 0x%x, tick %d, channel %d",
|
|
datah, datal, i->first, ev.channel());
|
|
break;
|
|
}
|
|
// assume that the sequence is always
|
|
// CTRL_HDATA - CTRL_LDATA
|
|
// eg. that LDATA is always send last
|
|
|
|
// 14 Bit RPN/NRPN
|
|
ev.setController((dataType+0x30000) | (rpnh << 8) | rpnl);
|
|
ev.setValue((datah << 7) | datal);
|
|
}
|
|
else if (cn == CTRL_HRPN) {
|
|
rpnh = val;
|
|
dataType = 0x20000;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else if (cn == CTRL_LRPN) {
|
|
rpnl = val;
|
|
dataType = 0x20000;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else if (cn == CTRL_HNRPN) {
|
|
rpnh = val;
|
|
dataType = 0x30000;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else if (cn == CTRL_LNRPN) {
|
|
rpnl = val;
|
|
dataType = 0x30000;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
else if (cn == CTRL_PROGRAM) {
|
|
ev.setValue((hbank << 16) | (lbank << 8) | ev.value());
|
|
// TODO el.insert(ev);
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
}
|
|
else if (ev.type() == ME_SYSEX) {
|
|
int len = ev.len();
|
|
const uchar* buffer = ev.edata();
|
|
if ((len == gmOnMsgLen) && memcmp(buffer, gmOnMsg, gmOnMsgLen) == 0) {
|
|
*mt = MidiType::GM;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
if ((len == gsOnMsgLen) && memcmp(buffer, gsOnMsg, gsOnMsgLen) == 0) {
|
|
*mt = MidiType::GS;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
if ((len == xgOnMsgLen) && memcmp(buffer, xgOnMsg, xgOnMsgLen) == 0) {
|
|
*mt = MidiType::XG;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
if (buffer[0] == 0x43) { // Yamaha
|
|
*mt = MidiType::XG;
|
|
int type = buffer[1] & 0xf0;
|
|
if (type == 0x10) {
|
|
//TODO if (buffer[1] != 0x10) {
|
|
// buffer[1] = 0x10; // fix to Device 1
|
|
// }
|
|
if ((len == xgOnMsgLen) && memcmp(buffer, xgOnMsg, xgOnMsgLen) == 0) {
|
|
*mt = MidiType::XG;
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
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
|
|
if (buffer[6] != 0 && buffer[4] == ev.channel()) {
|
|
_drumTrack = true;
|
|
}
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
el.insert(std::pair<int,MidiEvent>(i->first, ev));
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
int tick = i->first;
|
|
if (ev.type() == ME_NOTEOFF || ev.velo() == 0) {
|
|
qDebug("-extra note off at %d", tick);
|
|
ev.setType(ME_INVALID);
|
|
continue;
|
|
}
|
|
MidiEvent note(ME_NOTE, ev.channel(), ev.dataA(), ev.dataB());
|
|
|
|
auto k = i;
|
|
++k;
|
|
for (; k != _events.end(); ++k) {
|
|
MidiEvent& e = k->second;
|
|
if (e.type() != ME_NOTEON && e.type() != ME_NOTEOFF)
|
|
continue;
|
|
if ((e.type() == ME_NOTEOFF || (e.type() == ME_NOTEON && e.velo() == 0))
|
|
&& (e.pitch() == note.pitch())) {
|
|
int t = k->first - tick;
|
|
if (t <= 0)
|
|
t = 1;
|
|
note.setLen(t);
|
|
e.setType(ME_INVALID);
|
|
break;
|
|
}
|
|
}
|
|
if (k == _events.end()) {
|
|
qDebug("-no note-off for note at %d", tick);
|
|
note.setLen(1);
|
|
}
|
|
el.insert(std::pair<int,MidiEvent>(tick, note));
|
|
ev.setType(ME_INVALID);
|
|
}
|
|
_events = el;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// separateChannel
|
|
// if a track contains events for different midi channels,
|
|
// then split events into separate tracks
|
|
//---------------------------------------------------------
|
|
|
|
void MidiFile::separateChannel()
|
|
{
|
|
for (int i = 0; i < _tracks.size(); ++i) {
|
|
// create a list of channels used in current track
|
|
QList<int> channel;
|
|
MidiTrack &mt = _tracks[i]; // current track
|
|
for (const auto& ie : mt.events()) {
|
|
const MidiEvent& e = ie.second;
|
|
if (e.isChannelEvent() && !channel.contains(e.channel()))
|
|
channel.append(e.channel());
|
|
}
|
|
mt.setOutChannel(channel.empty() ? 0 : channel[0]);
|
|
int nn = channel.size();
|
|
if (nn <= 1)
|
|
continue;
|
|
qSort(channel);
|
|
// -- split --
|
|
// insert additional tracks, assign to them found channels
|
|
for (int ii = 1; ii < nn; ++ii) {
|
|
MidiTrack t;
|
|
t.setOutChannel(channel[ii]);
|
|
_tracks.insert(i + ii, t);
|
|
}
|
|
// extract all different channel events from current track to inserted tracks
|
|
for (auto ie = mt.events().begin(); ie != mt.events().end(); ) {
|
|
const MidiEvent& e = ie->second;
|
|
if (e.isChannelEvent()) {
|
|
int ch = e.channel();
|
|
int idx = channel.indexOf(ch);
|
|
MidiTrack &t = _tracks[i + idx];
|
|
if (&t != &mt) {
|
|
t.insert(ie->first, e);
|
|
ie = mt.events().erase(ie);
|
|
continue;
|
|
}
|
|
}
|
|
++ie;
|
|
}
|
|
i += nn - 1;
|
|
}
|
|
}
|
|
|
|
} // namespace Ms
|