/* * This code handles HMP files. It can: * - Open/read/close HMP files * - Play HMP via Windows MIDI * - Convert HMP to MIDI for further use * Based on work of Arne de Bruijn and the JFFEE project */ #include #include #include #include "hmp.h" #include "u_mem.h" #include "cfile.h" #ifdef WORDS_BIGENDIAN #define MIDIINT(x) (x) #define MIDISHORT(x) (x) #else #define MIDIINT(x) SWAPINT(x) #define MIDISHORT(x) SWAPSHORT(x) #endif #ifdef _WIN32 void hmp_stop(hmp_file *hmp); #endif // READ/OPEN/CLOSE HMP void hmp_close(hmp_file *hmp) { int i; #ifdef _WIN32 hmp_stop(hmp); #endif for (i = 0; i < hmp->num_trks; i++) if (hmp->trks[i].data) d_free(hmp->trks[i].data); d_free(hmp); } hmp_file *hmp_open(const char *filename) { int i; char buf[256]; long data; CFILE *fp; hmp_file *hmp; int num_tracks; unsigned char *p; if (!(fp = cfopen((char *)filename, "rb"))) return NULL; hmp = d_malloc(sizeof(hmp_file)); if (!hmp) { cfclose(fp); return NULL; } memset(hmp, 0, sizeof(*hmp)); if ((cfread(buf, 1, 8, fp) != 8) || (memcmp(buf, "HMIMIDIP", 8))) { cfclose(fp); hmp_close(hmp); return NULL; } if (cfseek(fp, 0x30, SEEK_SET)) { cfclose(fp); hmp_close(hmp); return NULL; } if (cfread(&num_tracks, 4, 1, fp) != 1) { cfclose(fp); hmp_close(hmp); return NULL; } if ((num_tracks < 1) || (num_tracks > HMP_TRACKS)) { cfclose(fp); hmp_close(hmp); return NULL; } hmp->num_trks = num_tracks; hmp->tempo = 120; if (cfseek(fp, 0x308, SEEK_SET)) { cfclose(fp); hmp_close(hmp); return NULL; } for (i = 0; i < num_tracks; i++) { if ((cfseek(fp, 4, SEEK_CUR)) || (cfread(&data, 4, 1, fp) != 1)) { cfclose(fp); hmp_close(hmp); return NULL; } data -= 12; hmp->trks[i].len = data; if (!(p = hmp->trks[i].data = d_malloc(data))) { cfclose(fp); hmp_close(hmp); return NULL; } /* finally, read track data */ if ((cfseek(fp, 4, SEEK_CUR)) || (cfread(p, data, 1, fp) != 1)) { cfclose(fp); hmp_close(hmp); return NULL; } } cfclose(fp); return hmp; } #ifdef WIN32 // PLAY HMP AS MIDI void hmp_stop(hmp_file *hmp) { MIDIHDR *mhdr; if (!hmp->stop) { hmp->stop = 1; //PumpMessages(); midiStreamStop(hmp->hmidi); while (hmp->bufs_in_mm) Sleep(0); } while ((mhdr = hmp->evbuf)) { midiOutUnprepareHeader((HMIDIOUT)hmp->hmidi, mhdr, sizeof(MIDIHDR)); hmp->evbuf = mhdr->lpNext; d_free(mhdr); } if (hmp->hmidi) { midiStreamClose(hmp->hmidi); hmp->hmidi = NULL; } } /* * read a HMI type variabele length number */ static int get_var_num_hmi(unsigned char *data, int datalen, unsigned long *value) { unsigned char *p; unsigned long v = 0; int shift = 0; p = data; while ((datalen > 0) && !(*p & 0x80)) { v += *(p++) << shift; shift += 7; datalen --; } if (!datalen) return 0; v += (*(p++) & 0x7f) << shift; if (value) *value = v; return p - data; } /* * read a MIDI type variabele length number */ static int get_var_num(unsigned char *data, int datalen, unsigned long *value) { unsigned char *orgdata = data; unsigned long v = 0; while ((datalen > 0) && (*data & 0x80)) v = (v << 7) + (*(data++) & 0x7f); if (!datalen) return 0; v = (v << 7) + *(data++); if (value) *value = v; return data - orgdata; } static int get_event(hmp_file *hmp, event *ev) { static int cmdlen[7]={3,3,3,3,2,2,3}; unsigned long got; unsigned long mindelta, delta; int i, ev_num; hmp_track *trk, *fndtrk; mindelta = INT_MAX; fndtrk = NULL; for (trk = hmp->trks, i = hmp->num_trks; (i--) > 0; trk++) { if (!trk->left) continue; if (!(got = get_var_num_hmi(trk->cur, trk->left, &delta))) return HMP_INVALID_FILE; if (trk->left > got + 2 && *(trk->cur + got) == 0xff && *(trk->cur + got + 1) == 0x2f) {/* end of track */ trk->left = 0; continue; } delta += trk->cur_time - hmp->cur_time; if (delta < mindelta) { mindelta = delta; fndtrk = trk; } } if (!(trk = fndtrk)) return HMP_EOF; got = get_var_num_hmi(trk->cur, trk->left, &delta); trk->cur_time += delta; ev->delta = trk->cur_time - hmp->cur_time; hmp->cur_time = trk->cur_time; if ((trk->left -= got) < 3) return HMP_INVALID_FILE; trk->cur += got; /*memset(ev, 0, sizeof(*ev));*/ev->datalen = 0; ev->msg[0] = ev_num = *(trk->cur++); trk->left--; if (ev_num < 0x80) return HMP_INVALID_FILE; /* invalid command */ if (ev_num < 0xf0) { ev->msg[1] = *(trk->cur++); trk->left--; if (cmdlen[((ev_num) >> 4) - 8] == 3) { ev->msg[2] = *(trk->cur++); trk->left--; } } else if (ev_num == 0xff) { ev->msg[1] = *(trk->cur++); trk->left--; if (!(got = get_var_num(ev->data = trk->cur, trk->left, (unsigned long *)&ev->datalen))) return HMP_INVALID_FILE; trk->cur += ev->datalen; if (trk->left <= ev->datalen) return HMP_INVALID_FILE; trk->left -= ev->datalen; } else /* sysex -> error */ return HMP_INVALID_FILE; return 0; } static int fill_buffer(hmp_file *hmp) { MIDIHDR *mhdr = hmp->evbuf; unsigned int *p = (unsigned int *)(mhdr->lpData + mhdr->dwBytesRecorded); unsigned int *pend = (unsigned int *)(mhdr->lpData + mhdr->dwBufferLength); unsigned int i; event ev; while (p + 4 <= pend) { if (hmp->pending_size) { i = (p - pend) * 4; if (i > hmp->pending_size) i = hmp->pending_size; *(p++) = hmp->pending_event | i; *(p++) = 0; memcpy((unsigned char *)p, hmp->pending, i); hmp->pending_size -= i; p += (i + 3) / 4; } else { if ((i = get_event(hmp, &ev))) { mhdr->dwBytesRecorded = ((unsigned char *)p) - ((unsigned char *)mhdr->lpData); return i; } if (ev.datalen) { hmp->pending_size = ev.datalen; hmp->pending = ev.data; hmp->pending_event = ev.msg[0] << 24; } else { *(p++) = ev.delta; *(p++) = 0; *(p++) = (((DWORD)MEVT_SHORTMSG) << 24) | ((DWORD)ev.msg[0]) | (((DWORD)ev.msg[1]) << 8) | (((DWORD)ev.msg[2]) << 16); } } } mhdr->dwBytesRecorded = ((unsigned char *)p) - ((unsigned char *)mhdr->lpData); return 0; } static int setup_buffers(hmp_file *hmp) { int i; MIDIHDR *buf, *lastbuf; lastbuf = NULL; for (i = 0; i < HMP_BUFFERS; i++) { if (!(buf = d_malloc(HMP_BUFSIZE + sizeof(MIDIHDR)))) return HMP_OUT_OF_MEM; memset(buf, 0, sizeof(MIDIHDR)); buf->lpData = (unsigned char *)buf + sizeof(MIDIHDR); buf->dwBufferLength = HMP_BUFSIZE; buf->dwUser = (DWORD)hmp; buf->lpNext = lastbuf; lastbuf = buf; } hmp->evbuf = lastbuf; return 0; } static void reset_tracks(struct hmp_file *hmp) { int i; for (i = 0; i < hmp->num_trks; i++) { hmp->trks [i].cur = hmp->trks [i].data; hmp->trks [i].left = hmp->trks [i].len; hmp->trks [i].cur_time = 0; } hmp->cur_time=0; } static void _stdcall midi_callback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) { MIDIHDR *mhdr; hmp_file *hmp; int rc; if (uMsg != MOM_DONE) return; mhdr = ((MIDIHDR *)dw1); mhdr->dwBytesRecorded = 0; hmp = (hmp_file *)(mhdr->dwUser); mhdr->lpNext = hmp->evbuf; hmp->evbuf = mhdr; hmp->bufs_in_mm--; if (!hmp->stop) { while (fill_buffer(hmp) == HMP_EOF) { if (hmp->bLoop) hmp->stop = 0; else hmp->stop = 1; reset_tracks(hmp); } if ((rc = midiStreamOut(hmp->hmidi, hmp->evbuf, sizeof(MIDIHDR))) != MMSYSERR_NOERROR) { /* ??? */ } else { hmp->evbuf = hmp->evbuf->lpNext; hmp->bufs_in_mm++; } } } static void setup_tempo(hmp_file *hmp, unsigned long tempo) { MIDIHDR *mhdr = hmp->evbuf; unsigned int *p = (unsigned int *)(mhdr->lpData + mhdr->dwBytesRecorded); *(p++) = 0; *(p++) = 0; *(p++) = (((DWORD)MEVT_TEMPO)<<24) | tempo; mhdr->dwBytesRecorded += 12; } int hmp_play(hmp_file *hmp, int bLoop) { int rc; MIDIPROPTIMEDIV mptd; #if 1 unsigned int numdevs; int i=0; // ZICO - LOOP HERE numdevs=midiOutGetNumDevs(); hmp->devid=-1; do { MIDIOUTCAPS devcaps; midiOutGetDevCaps(i,&devcaps,sizeof(MIDIOUTCAPS)); if ((devcaps.wTechnology==MOD_FMSYNTH) || (devcaps.wTechnology==MOD_SYNTH)) hmp->devid=i; i++; } while ((i<(int)numdevs) && (hmp->devid==-1)); if (hmp->devid == -1) #endif hmp->bLoop = bLoop; hmp->devid = MIDI_MAPPER; if ((rc = setup_buffers(hmp))) return rc; if ((midiStreamOpen(&hmp->hmidi, &hmp->devid,1, (DWORD) (size_t) midi_callback, 0, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) { hmp->hmidi = NULL; return HMP_MM_ERR; } mptd.cbStruct = sizeof(mptd); mptd.dwTimeDiv = hmp->tempo; if ((midiStreamProperty(hmp->hmidi,(LPBYTE)&mptd,MIDIPROP_SET|MIDIPROP_TIMEDIV)) != MMSYSERR_NOERROR) { return HMP_MM_ERR; } reset_tracks(hmp); setup_tempo(hmp, 0x0f4240); hmp->stop = 0; while (hmp->evbuf) { if ((rc = fill_buffer(hmp))) { if (rc == HMP_EOF) { reset_tracks(hmp); continue; } else return rc; } if ((rc = midiOutPrepareHeader((HMIDIOUT)hmp->hmidi, hmp->evbuf, sizeof(MIDIHDR))) != MMSYSERR_NOERROR) { return HMP_MM_ERR; } if ((rc = midiStreamOut(hmp->hmidi, hmp->evbuf, sizeof(MIDIHDR))) != MMSYSERR_NOERROR) { return HMP_MM_ERR; } hmp->evbuf = hmp->evbuf->lpNext; hmp->bufs_in_mm++; } midiStreamRestart(hmp->hmidi); return 0; } #endif // CONVERSION FROM HMP TO MIDI static unsigned int hmptrk2mid(ubyte* data, int size, unsigned char **midbuf, unsigned int *midlen) { ubyte *dptr = data; ubyte lc1 = 0,last_com = 0; uint d; int n1, n2; unsigned int offset = *midlen; while (data < dptr + size) { if (data[0] & 0x80) { ubyte b = (data[0] & 0x7F); *midbuf = d_realloc(*midbuf, *midlen + 1); memcpy(&(*midbuf)[*midlen], &b, 1); *midlen += 1; } else { d = (data[0] & 0x7F); n1 = 0; while ((data[n1] & 0x80) == 0) { n1++; d += (data[n1] & 0x7F) << (n1 * 7); } n1 = 1; while ((data[n1] & 0x80) == 0) { n1++; if (n1 == 4) return 0; } for(n2 = 0; n2 <= n1; n2++) { ubyte b = (data[n1 - n2] & 0x7F); if (n2 != n1) b |= 0x80; *midbuf = d_realloc(*midbuf, *midlen + 1); memcpy(&(*midbuf)[*midlen], &b, 1); *midlen += 1; } data += n1; } data++; if (*data == 0xFF) { //meta? *midbuf = d_realloc(*midbuf, *midlen + 3 + data[2]); memcpy(&(*midbuf)[*midlen], data, 3 + data[2]); *midlen += 3 + data[2]; if (data[1] == 0x2F) break; } else { lc1=data[0] ; if ((lc1&0x80) == 0) return 0; switch (lc1 & 0xF0) { case 0x80: case 0x90: case 0xA0: case 0xB0: case 0xE0: if (lc1 != last_com) { *midbuf = d_realloc(*midbuf, *midlen + 1); memcpy(&(*midbuf)[*midlen], &lc1, 1); *midlen += 1; } *midbuf = d_realloc(*midbuf, *midlen + 2); memcpy(&(*midbuf)[*midlen], data + 1, 2); *midlen += 2; data += 3; break; case 0xC0: case 0xD0: if (lc1 != last_com) { *midbuf = d_realloc(*midbuf, *midlen + 1); memcpy(&(*midbuf)[*midlen], &lc1, 1); *midlen += 1; } *midbuf = d_realloc(*midbuf, *midlen + 1); memcpy(&(*midbuf)[*midlen], data + 1, 1); *midlen += 1; data += 2; break; default: return 0; } last_com = lc1; } } return (*midlen - offset); } ubyte tempo [19] = {'M','T','r','k',0,0,0,11,0,0xFF,0x51,0x03,0x18,0x80,0x00,0,0xFF,0x2F,0}; void hmp2mid(char *hmp_name, unsigned char **midbuf, unsigned int *midlen) { int mi, i; short ms; hmp_file *hmp=NULL; hmp = hmp_open(hmp_name); if (hmp == NULL) return; *midlen = 0; // write MIDI-header *midbuf = d_realloc(*midbuf, *midlen + 4); memcpy(&(*midbuf)[*midlen], "MThd", 4); *midlen += 4; mi = MIDIINT(6); *midbuf = d_realloc(*midbuf, *midlen + sizeof(mi)); memcpy(&(*midbuf)[*midlen], &mi, sizeof(mi)); *midlen += sizeof(mi); ms = MIDISHORT(1); *midbuf = d_realloc(*midbuf, *midlen + sizeof(ms)); memcpy(&(*midbuf)[*midlen], &ms, sizeof(ms)); *midlen += sizeof(ms); ms = MIDISHORT(hmp->num_trks); *midbuf = d_realloc(*midbuf, *midlen + sizeof(ms)); memcpy(&(*midbuf)[*midlen], &ms, sizeof(ms)); *midlen += sizeof(ms); ms = MIDISHORT((short) 0xC0); *midbuf = d_realloc(*midbuf, *midlen + sizeof(ms)); memcpy(&(*midbuf)[*midlen], &ms, sizeof(ms)); *midlen += sizeof(ms); *midbuf = d_realloc(*midbuf, *midlen + sizeof(tempo)); memcpy(&(*midbuf)[*midlen], &tempo, sizeof(tempo)); *midlen += sizeof(tempo); // tracks for (i = 1; i < hmp->num_trks; i++) { int midtrklenpos = 0; *midbuf = d_realloc(*midbuf, *midlen + 4); memcpy(&(*midbuf)[*midlen], "MTrk", 4); *midlen += 4; midtrklenpos = *midlen; mi = 0; *midbuf = d_realloc(*midbuf, *midlen + sizeof(mi)); *midlen += sizeof(mi); mi = hmptrk2mid(hmp->trks[i].data, hmp->trks[i].len, midbuf, midlen); mi = MIDIINT(mi); memcpy(&(*midbuf)[midtrklenpos], &mi, 4); } hmp_close(hmp); }