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; }