574 lines
13 KiB
C
574 lines
13 KiB
C
enum mid_event {
|
|
midev_noteoff = 0x80,
|
|
midev_noteon = 0x90,
|
|
midev_aftertouch = 0xa0,
|
|
midev_control = 0xb0,
|
|
midev_progchg = 0xc0,
|
|
midev_chnpressure = 0xd0,
|
|
midev_pitchbend = 0xe0,
|
|
midev_sysex = 0xf0,
|
|
midev_songpos = 0xf2,
|
|
midev_songsel = 0xf3,
|
|
midev_tunereq = 0xf6,
|
|
midev_endsysex = 0xf7,
|
|
midev_clock = 0xf8,
|
|
midev_start = 0xfa,
|
|
midev_continue = 0xfb,
|
|
midev_stop = 0xfc,
|
|
midev_actsense = 0xfe,
|
|
midev_meta = 0xff
|
|
};
|
|
|
|
enum mid_meta {
|
|
midmt_seqnum = 0x00, // nn nn
|
|
midmt_text = 0x01, // text...
|
|
midmt_copyright = 0x02, // text...
|
|
midmt_trackname = 0x03, // text...
|
|
midmt_instrname = 0x04, // text...
|
|
midmt_lyric = 0x05, // text...
|
|
midmt_marker = 0x06, // text...
|
|
midmt_cuepoint = 0x07, // text...
|
|
midmt_chnprefix = 0x20, // cc
|
|
midmt_endtrack = 0x2f, //
|
|
midmt_tempo = 0x51, // tt tt tt
|
|
midmt_smpte = 0x54, // hr mn se fr ff
|
|
midmt_timesig = 0x58, // nn dd cc bb
|
|
midmt_keysig = 0x59, // sf mi
|
|
midmt_seqspec = 0x7f // data...
|
|
};
|
|
|
|
typedef struct {
|
|
uint8_t *buffer;
|
|
uint32_t size;
|
|
uint32_t pos;
|
|
uint8_t status;
|
|
uint8_t ending;
|
|
uint32_t wait;
|
|
uint16_t trknum;
|
|
} mid_track;
|
|
|
|
typedef struct {
|
|
mid_track *track;
|
|
uint16_t tracks;
|
|
uint32_t tpqn;
|
|
uint32_t uspb;
|
|
uint32_t ticktime;
|
|
} mid_handle;
|
|
|
|
const char *mid_hdr = "MThd";
|
|
const char *mid_trk = "MTrk";
|
|
|
|
#define MID_DEFTEMPO 500000
|
|
|
|
void mid_settempo(mid_handle *mid, uint32_t tempo) {
|
|
mid->uspb = tempo;
|
|
mid->ticktime = mid->uspb / mid->tpqn;
|
|
}
|
|
|
|
uint32_t mid_read_uint32(uint8_t *data) {
|
|
uint32_t value = 0;
|
|
for(int h = 0; h < 4; h++) {
|
|
value |= ((uint32_t)data[h]);
|
|
value <<= h < 3 ? 8 : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint16_t mid_read_uint16(uint8_t *data) {
|
|
uint16_t value = 0;
|
|
for(int h = 0; h < 2; h++) {
|
|
value |= ((uint16_t)data[h]);
|
|
value <<= h < 1 ? 8 : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t midt_read_uint32(mid_track *trk) {
|
|
uint32_t value = 0;
|
|
for(int h = 0; h < 4; h++) {
|
|
if(trk->pos >= trk->size) {
|
|
break;
|
|
}
|
|
value |= ((uint32_t)trk->buffer[trk->pos++]);
|
|
value <<= h < 3 ? 8 : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t midt_read_uint24(mid_track *trk) {
|
|
uint32_t value = 0;
|
|
for(int h = 0; h < 3; h++) {
|
|
if(trk->pos >= trk->size) {
|
|
break;
|
|
}
|
|
value |= ((uint32_t)trk->buffer[trk->pos++]);
|
|
value <<= h < 2 ? 8 : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint16_t midt_read_uint16(mid_track *trk) {
|
|
uint16_t value = 0;
|
|
for(int h = 0; h < 2; h++) {
|
|
if(trk->pos >= trk->size) {
|
|
break;
|
|
}
|
|
value |= ((uint16_t)trk->buffer[trk->pos++]);
|
|
value <<= h < 1 ? 8 : 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint8_t midt_read_uint8(mid_track *trk) {
|
|
uint8_t value = 0;
|
|
if(trk->pos < trk->size) {
|
|
value = trk->buffer[trk->pos++];
|
|
}
|
|
return value;
|
|
}
|
|
|
|
void midt_read_var(mid_track *trk, uint32_t len, uint8_t *chars) {
|
|
memset(chars, 0, len);
|
|
for(int h = 0; h < len; h++) {
|
|
if(trk->pos >= trk->size) {
|
|
break;
|
|
}
|
|
chars[h] = trk->buffer[trk->pos++];
|
|
}
|
|
}
|
|
|
|
uint32_t mid_read_vlen(mid_track *trk)
|
|
{
|
|
uint32_t value;
|
|
uint8_t bt;
|
|
|
|
if(trk->pos >= trk->size) {
|
|
return 0;
|
|
}
|
|
if((value = trk->buffer[trk->pos++]) & 0x80)
|
|
{
|
|
value &= 0x7f;
|
|
do {
|
|
if(trk->pos >= trk->size) {
|
|
break;
|
|
}
|
|
value = (value << 7) + ((bt = trk->buffer[trk->pos++]) & 0x7f);
|
|
}
|
|
while(bt & 0x80);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
bool mid_read(mid_handle *mid, const char *filename) {
|
|
int err;
|
|
FILE *fd = fopen(filename, "rb");
|
|
if(fd == NULL) {
|
|
err = errno;
|
|
mid_log(STR_MID_OPNERR, filename, strerror(err), err);
|
|
return false;
|
|
}
|
|
if(fseek(fd, 0L, SEEK_END)) {
|
|
err = errno;
|
|
mid_log(STR_MID_EGEN, filename, strerror(err), err);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
long size = ftell(fd);
|
|
if(size < 0L) {
|
|
err = errno;
|
|
mid_log(STR_MID_EGEN, filename, strerror(err), err);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if(fseek(fd, 0L, SEEK_SET)) {
|
|
err = errno;
|
|
mid_log(STR_MID_EGEN, filename, strerror(err), err);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
|
|
if(size < 14) {
|
|
mid_log(STR_MID_ESHORT, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
uint8_t header[14];
|
|
if(fread(header, 1, 14, fd) != 14) {
|
|
err = feof(fd);
|
|
mid_log(err == 0 ? STR_MID_EIO : STR_MID_EEOF, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if(memcmp(header, mid_hdr, 4) != 0) {
|
|
mid_log(STR_MID_EMFHDR, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if(mid_read_uint32(header+4) != 6) {
|
|
mid_log(STR_MID_EHSIZE, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
bool mtrack;
|
|
uint16_t type = mid_read_uint16(header+8);
|
|
if(type == 0) {
|
|
mtrack = false;
|
|
}
|
|
else if((type == 1) || (type == 2)) {
|
|
mtrack = true;
|
|
}
|
|
else {
|
|
mid_log(STR_MID_ETKFMT, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
mid->tracks = mid_read_uint16(header+10);
|
|
if(mid->tracks == 0) {
|
|
mid_log(STR_MID_ENOTRK, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
else if((mtrack ^ 1) && (mid->tracks > 1)) {
|
|
mid_log(STR_MID_ESMULT, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
mid->tpqn = mid_read_uint16(header+12);
|
|
if((mid->tpqn & 0x8000) > 0) {
|
|
mid_log(STR_MID_ESMPTE, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if(mid->tpqn == 0) {
|
|
mid_log(STR_MID_EDZERO, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
uint32_t esize = 0;
|
|
for(uint16_t trk = 0; trk < mid->tracks; trk++) {
|
|
if(fread(header, 1, 8, fd) != 8) {
|
|
err = feof(fd);
|
|
mid_log(err == 0 ? STR_MID_EIO : STR_MID_EEOF, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if(memcmp(header, mid_trk, 4) != 0) {
|
|
mid_log(STR_MID_ETKHDR, filename);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
uint32_t trks = mid_read_uint32(header+4);
|
|
if((14 + esize + 8 + trks) > size) {
|
|
mid_log(STR_MID_EOUTOF, filename, trk+1);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
if(fseek(fd, trks, SEEK_CUR)) {
|
|
err = errno;
|
|
mid_log(STR_MID_EGEN, filename, strerror(err), err);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
esize += trks + 8;
|
|
}
|
|
if(fseek(fd, 14L, SEEK_SET)) {
|
|
err = errno;
|
|
mid_log(STR_MID_EGEN, filename, strerror(err), err);
|
|
fclose(fd);
|
|
return false;
|
|
}
|
|
esize -= (mid->tracks * 8);
|
|
uint8_t *buf = malloc((sizeof(mid_track) * mid->tracks) + esize);
|
|
mid->track = (mid_track*)buf;
|
|
buf += (sizeof(mid_track) * mid->tracks);
|
|
for(uint16_t trk = 0; trk < mid->tracks; trk++) {
|
|
memset((mid->track)+trk, 0, sizeof(mid_track));
|
|
if(fread(header, 1, 8, fd) != 8) {
|
|
err = feof(fd);
|
|
mid_log(err == 0 ? STR_MID_EIO : STR_MID_EEOF, filename);
|
|
fclose(fd);
|
|
free(mid->track);
|
|
return false;
|
|
}
|
|
mid->track[trk].size = mid_read_uint32(header+4);
|
|
if(fread(buf, 1, mid->track[trk].size, fd) != mid->track[trk].size) {
|
|
err = feof(fd);
|
|
mid_log(err == 0 ? STR_MID_EIO : STR_MID_EEOF, filename);
|
|
fclose(fd);
|
|
free(mid->track);
|
|
return false;
|
|
}
|
|
mid->track[trk].buffer = buf;
|
|
mid->track[trk].pos = 0;
|
|
mid->track[trk].wait = 0;
|
|
mid->track[trk].trknum = trk;
|
|
buf += mid->track[trk].size;
|
|
}
|
|
fclose(fd);
|
|
mid_settempo(mid, MID_DEFTEMPO);
|
|
return true;
|
|
}
|
|
|
|
void mid_free(mid_handle *mid) {
|
|
free(mid->track);
|
|
}
|
|
|
|
void mid_process_meta(mid_handle *mid, mid_track *trk) {
|
|
uint8_t meta = midt_read_uint8(trk);
|
|
uint32_t size = mid_read_vlen(trk);
|
|
switch(meta) {
|
|
case midmt_seqnum:
|
|
if(size == 0) {
|
|
mid_dlog(STR_MID_SEQNUMO, trk->trknum, trk->trknum);
|
|
return;
|
|
}
|
|
else if(size != 2) {
|
|
trk->pos += size;
|
|
mid_log(STR_MID_METALEN, trk->trknum, meta, 2, size);
|
|
return;
|
|
}
|
|
mid_dlog(STR_MID_SEQNUM, trk->trknum, midt_read_uint16(trk));
|
|
return;
|
|
case midmt_text:
|
|
case midmt_copyright:
|
|
case midmt_trackname:
|
|
case midmt_instrname:
|
|
case midmt_lyric:
|
|
case midmt_marker:
|
|
case midmt_cuepoint: {
|
|
uint8_t dt[size+1];
|
|
dt[size] = 0;
|
|
midt_read_var(trk, size, dt);
|
|
mid_tlog(meta, dt);
|
|
return;
|
|
}
|
|
case midmt_chnprefix:
|
|
if(size != 1) {
|
|
trk->pos += size;
|
|
mid_log(STR_MID_METALEN, trk->trknum, meta, 1, size);
|
|
return;
|
|
}
|
|
mid_dlog(STR_MID_CHNPFX, trk->trknum, midt_read_uint8(trk));
|
|
return;
|
|
case midmt_endtrack:
|
|
trk->pos += size;
|
|
trk->ending = 1;
|
|
mid_dlog(STR_MID_END, trk->trknum);
|
|
return;
|
|
case midmt_tempo:
|
|
if(size != 3) {
|
|
trk->pos += size;
|
|
mid_log(STR_MID_METALEN, trk->trknum, meta, 3, size);
|
|
return;
|
|
}
|
|
mid_settempo(mid, midt_read_uint24(trk));
|
|
mid_dlog(STR_MID_TEMPO, 60000000 / mid->uspb);
|
|
return;
|
|
case midmt_smpte:
|
|
if(size != 5) {
|
|
trk->pos += size;
|
|
mid_log(STR_MID_METALEN, trk->trknum, meta, 5, size);
|
|
return;
|
|
}
|
|
trk->pos += 5;
|
|
mid_dlog(STR_MID_SMPTE);
|
|
return;
|
|
case midmt_timesig: {
|
|
if(size != 4) {
|
|
trk->pos += size;
|
|
mid_log(STR_MID_METALEN, trk->trknum, meta, 4, size);
|
|
return;
|
|
}
|
|
uint8_t n = midt_read_uint8(trk);
|
|
uint32_t d = 1 << ((uint32_t)midt_read_uint8(trk));
|
|
uint8_t c = midt_read_uint8(trk);
|
|
uint8_t b = midt_read_uint8(trk);
|
|
mid_dlog(STR_MID_TIMESIG, n, d, c, b);
|
|
return;
|
|
}
|
|
case midmt_keysig: {
|
|
if(size != 2) {
|
|
trk->pos += size;
|
|
mid_log(STR_MID_METALEN, trk->trknum, meta, 2, size);
|
|
return;
|
|
}
|
|
int8_t s = midt_read_uint8(trk);
|
|
uint8_t m = midt_read_uint8(trk);
|
|
mid_dlog(STR_MID_KEYSIG, s, (m == 0) ? "MAJOR" : ((m == 1) ? "MINOR" : "- ? -"));
|
|
return;
|
|
}
|
|
case midmt_seqspec: {
|
|
trk->pos += size;
|
|
mid_dlog(STR_MID_SEQDATA, trk->trknum, size);
|
|
return;
|
|
}
|
|
default:
|
|
trk->pos += size;
|
|
mid_dlog(STR_MID_UNKMETA, trk->trknum, meta, size);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifndef MID_NOEVT
|
|
void mid_process(mid_handle *mid, mid_track *trk, bank_handle *bank, opl3_chip *chip) {
|
|
#else
|
|
void mid_process(mid_handle *mid, mid_track *trk) {
|
|
#endif
|
|
if(trk->pos >= trk->size) {
|
|
return;
|
|
}
|
|
uint8_t status = trk->buffer[trk->pos++];
|
|
if((status & 0x80) == 0) {
|
|
status = trk->status;
|
|
trk->pos -= 1;
|
|
}
|
|
else {
|
|
trk->status = status;
|
|
}
|
|
if((status & 0xf0) != 0xf0) {
|
|
uint8_t channel = status & 0x0f;
|
|
status &= 0xf0;
|
|
switch(status) {
|
|
case midev_noteoff: {
|
|
uint8_t o_key = midt_read_uint8(trk);
|
|
uint8_t o_velocity = midt_read_uint8(trk);
|
|
#ifndef MID_NOEVT
|
|
bank_noteoff(bank, chip, channel, o_key, o_velocity);
|
|
#endif
|
|
mid_dlog(STR_MID_NOTEOFF, channel+1, o_key, o_velocity);
|
|
break;
|
|
}
|
|
case midev_noteon: {
|
|
uint8_t key = midt_read_uint8(trk);
|
|
uint8_t velocity = midt_read_uint8(trk);
|
|
#ifndef MID_NOEVT
|
|
if(velocity == 0) {
|
|
bank_noteoff(bank, chip, channel, key, velocity);
|
|
}
|
|
else {
|
|
bank_noteon(bank, chip, channel, key, velocity);
|
|
}
|
|
#endif
|
|
mid_dlog(STR_MID_NOTEON, channel+1, key, velocity);
|
|
break;
|
|
}
|
|
case midev_aftertouch: {
|
|
uint8_t pressure = midt_read_uint8(trk);
|
|
mid_dlog(STR_MID_PRESS, channel+1, pressure);
|
|
break;
|
|
}
|
|
case midev_control: {
|
|
uint8_t control = midt_read_uint8(trk);
|
|
uint8_t value = midt_read_uint8(trk);
|
|
#ifndef MID_NOEVT
|
|
bank_control(bank, chip, channel, control, value);
|
|
#endif
|
|
mid_dlog(STR_MID_CC, channel+1, control, value);
|
|
break;
|
|
}
|
|
case midev_progchg: {
|
|
uint8_t program = midt_read_uint8(trk);
|
|
#ifndef MID_NOEVT
|
|
bank_progchange(bank, chip, channel, program);
|
|
#endif
|
|
mid_dlog(STR_MID_PROG, channel+1, program);
|
|
break;
|
|
}
|
|
case midev_chnpressure: {
|
|
uint8_t cpressure = midt_read_uint8(trk);
|
|
mid_dlog(STR_MID_CPRESS, channel+1, cpressure);
|
|
break;
|
|
}
|
|
case midev_pitchbend: {
|
|
uint16_t pb = ((uint16_t)midt_read_uint8(trk)) | (((uint16_t)midt_read_uint8(trk)) << 7);
|
|
int16_t pitch = ((int16_t)pb) - 0x2000;
|
|
#ifndef MID_NOEVT
|
|
bank_pitchbend(bank, chip, channel, pitch);
|
|
#endif
|
|
mid_dlog(STR_MID_PITCH, channel+1, pitch);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch(status) {
|
|
case midev_sysex: {
|
|
uint32_t slen = mid_read_vlen(trk);
|
|
trk->pos += slen;
|
|
mid_dlog(STR_MID_SYSEX, slen);
|
|
break;
|
|
}
|
|
case midev_songpos:
|
|
mid_dlog(STR_MID_SONGPOS, ((uint16_t)midt_read_uint8(trk)) | (((uint16_t)midt_read_uint8(trk)) << 7));
|
|
break;
|
|
case midev_songsel:
|
|
mid_dlog(STR_MID_SONGSEL, midt_read_uint8(trk));
|
|
break;
|
|
case midev_tunereq:
|
|
mid_dlog(STR_MID_TUNE);
|
|
break;
|
|
case midev_endsysex: {
|
|
uint32_t elen = mid_read_vlen(trk);
|
|
trk->pos += elen;
|
|
mid_dlog(STR_MID_ESYSEX, elen);
|
|
break;
|
|
}
|
|
case midev_clock:
|
|
case midev_start:
|
|
case midev_continue:
|
|
case midev_stop:
|
|
case midev_actsense:
|
|
mid_dlog(STR_MID_GENSTAT, status);
|
|
break;
|
|
case midev_meta:
|
|
mid_process_meta(mid, trk);
|
|
break;
|
|
default:
|
|
mid_log(STR_MID_UNKSTAT, status);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef MID_NOEVT
|
|
uint8_t mid_tick(mid_handle *mid, bank_handle *bank, opl3_chip *chip) {
|
|
#else
|
|
uint8_t mid_tick(mid_handle *mid) {
|
|
#endif
|
|
uint8_t end = 1;
|
|
for(int trk = 0; trk < mid->tracks; trk++) {
|
|
mid_track *track = (mid->track)+trk;
|
|
if(track->ending) {
|
|
continue;
|
|
}
|
|
if(track->wait > 0) {
|
|
track->wait -= 1;
|
|
if(track->wait > 0) {
|
|
end = 0;
|
|
continue;
|
|
}
|
|
}
|
|
while(true) {
|
|
if(track->pos > 0) {
|
|
#ifndef MID_NOEVT
|
|
mid_process(mid, track, bank, chip);
|
|
#else
|
|
mid_process(mid, track);
|
|
#endif
|
|
if((track->ending ^ 1) && (track->pos >= track->size)) {
|
|
mid_log(STR_MID_EEND, track->trknum);
|
|
track->ending = 1;
|
|
}
|
|
if(track->ending) {
|
|
break;
|
|
}
|
|
}
|
|
track->wait = mid_read_vlen(track);
|
|
if(track->wait > 0) {
|
|
break;
|
|
}
|
|
}
|
|
end &= track->ending;
|
|
}
|
|
return end ^ 1;
|
|
}
|