Casting `&unsigned` to `(unsigned long *)` is wrong. It happened to work because these types are the same size on Windows and this code is only used on Windows. Remove the bogus cast and fix the function prototype.
725 lines
18 KiB
C++
725 lines
18 KiB
C++
/*
|
|
* This file is part of the DXX-Rebirth project <http://www.dxx-rebirth.com/>.
|
|
* It is copyright by its individual contributors, as recorded in the
|
|
* project's Git history. See COPYING.txt at the top level for license
|
|
* terms and a link to the Git history.
|
|
*/
|
|
/*
|
|
* 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 <stdexcept>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "hmp.h"
|
|
#include "u_mem.h"
|
|
#include "console.h"
|
|
#include "timer.h"
|
|
#include "serial.h"
|
|
|
|
#include "dxxsconf.h"
|
|
#include "dsx-ns.h"
|
|
#include "compiler-make_unique.h"
|
|
#include "compiler-range_for.h"
|
|
#include "partial_range.h"
|
|
|
|
namespace dcx {
|
|
|
|
#define MIDIINT(x) (words_bigendian ? (x) : (SWAPINT(x)))
|
|
#define MIDISHORT(x) (words_bigendian ? (x) : (SWAPSHORT(x)))
|
|
|
|
#ifdef _WIN32
|
|
static int midi_volume;
|
|
static int channel_volume[16];
|
|
static void hmp_stop(hmp_file *hmp);
|
|
#endif
|
|
|
|
// READ/OPEN/CLOSE HMP
|
|
|
|
hmp_file::~hmp_file()
|
|
{
|
|
#ifdef _WIN32
|
|
hmp_stop(this);
|
|
#endif
|
|
}
|
|
|
|
std::unique_ptr<hmp_file> hmp_open(const char *filename) {
|
|
int data, num_tracks, tempo;
|
|
auto fp = PHYSFSX_openReadBuffered(filename);
|
|
|
|
if (!fp)
|
|
return NULL;
|
|
|
|
std::unique_ptr<hmp_file> hmp(new hmp_file{});
|
|
char buf[8];
|
|
if ((PHYSFS_read(fp, buf, 1, 8) != 8) || (memcmp(buf, "HMIMIDIP", 8)))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (PHYSFSX_fseek(fp, 0x30, SEEK_SET))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (PHYSFS_read(fp, &num_tracks, 4, 1) != 1)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if ((num_tracks < 1) || (num_tracks > HMP_TRACKS))
|
|
{
|
|
return NULL;
|
|
}
|
|
hmp->num_trks = num_tracks;
|
|
|
|
if (PHYSFSX_fseek(fp, 0x38, SEEK_SET))
|
|
{
|
|
return NULL;
|
|
}
|
|
if (PHYSFS_read(fp, &tempo, 4, 1) != 1)
|
|
{
|
|
return NULL;
|
|
}
|
|
hmp->tempo = INTEL_INT(tempo);
|
|
|
|
if (PHYSFSX_fseek(fp, 0x308, SEEK_SET))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < num_tracks; i++) {
|
|
if ((PHYSFSX_fseek(fp, 4, SEEK_CUR)) || (PHYSFS_read(fp, &data, 4, 1) != 1))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
data -= 12;
|
|
hmp->trks[i].len = data;
|
|
|
|
hmp->trks[i].data = make_unique<uint8_t[]>(data);
|
|
/* finally, read track data */
|
|
if ((PHYSFSX_fseek(fp, 4, SEEK_CUR)) || (PHYSFS_read(fp, hmp->trks[i].data.get(), data, 1) != 1))
|
|
{
|
|
return NULL;
|
|
}
|
|
hmp->trks[i].loop_set = 0;
|
|
}
|
|
hmp->filesize = PHYSFS_fileLength(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)
|
|
timer_delay(1);
|
|
}
|
|
while ((mhdr = hmp->evbuf)) {
|
|
midiOutUnprepareHeader(reinterpret_cast<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 variable 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 variable length number
|
|
*/
|
|
static int get_var_num(unsigned char *data, int datalen,
|
|
unsigned *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 const array<int, 7> cmdlen{{3,3,3,3,2,2,3}};
|
|
unsigned long got;
|
|
unsigned long mindelta, delta;
|
|
int ev_num;
|
|
hmp_track *fndtrk = nullptr;
|
|
|
|
mindelta = INT_MAX;
|
|
range_for (auto &rtrk, partial_range(hmp->trks, static_cast<unsigned>(hmp->num_trks)))
|
|
{
|
|
const auto trk = &rtrk;
|
|
if (!trk->left || (hmp->loop_start && hmp->looping && !trk->loop_set))
|
|
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;
|
|
}
|
|
|
|
if (hmp->loop_start && hmp->looping)
|
|
if (trk->cur == trk->loop)
|
|
delta = trk->loop_start - hmp->loop_start;
|
|
|
|
delta += trk->cur_time - hmp->cur_time;
|
|
if (delta < mindelta) {
|
|
mindelta = delta;
|
|
fndtrk = trk;
|
|
}
|
|
}
|
|
const auto trk = fndtrk;
|
|
if (!trk)
|
|
return HMP_EOF;
|
|
|
|
got = get_var_num_hmi(trk->cur, trk->left, &delta);
|
|
|
|
if (hmp->loop_start && hmp->looping)
|
|
if (trk->cur == trk->loop)
|
|
delta = trk->loop_start - hmp->loop_start;
|
|
|
|
trk->cur_time += delta;
|
|
|
|
if (!hmp->loop_start && *(trk->cur + got) >> 4 == MIDI_CONTROL_CHANGE && *(trk->cur + got + 1) == HMP_LOOP_START)
|
|
{
|
|
hmp->loop_start = trk->cur_time;
|
|
if ((hmp->filesize == 86949) && (trk->cur_time == 29)) // special ugly HACK HACK HACK for Descent2-version of descent.hmp where loop at this point causes instruments not being reset properly. No track supporting HMP loop I know of meets the requirements for the condition below and even if so - it only disabled the HMP loop feature.
|
|
{
|
|
hmp->loop_start = 0;
|
|
}
|
|
|
|
}
|
|
|
|
if (!hmp->loop_end && *(trk->cur + got) >> 4 == MIDI_CONTROL_CHANGE && *(trk->cur + got + 1) == HMP_LOOP_END)
|
|
hmp->loop_end = trk->cur_time;
|
|
|
|
if (hmp->loop_start && !trk->loop_set && trk->cur_time >= hmp->loop_start)
|
|
{
|
|
trk->loop_start = trk->cur_time;
|
|
trk->loop = trk->cur;
|
|
trk->len = trk->left;
|
|
trk->loop_set = 1;
|
|
}
|
|
|
|
if (hmp->loop_end && trk->cur_time > hmp->loop_end)
|
|
return HMP_EOF;
|
|
|
|
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--;
|
|
|
|
if (ev->msg[0] >> 4 == MIDI_CONTROL_CHANGE && ev->msg[1] == MIDI_VOLUME)
|
|
{
|
|
channel_volume[ev->msg[0] & 0xf] = ev->msg[2];
|
|
|
|
ev->msg[2] = ev->msg[2] * midi_volume / MIDI_VOLUME_SCALE;
|
|
}
|
|
}
|
|
} else if (ev_num == 0xff) {
|
|
ev->msg[1] = *(trk->cur++);
|
|
trk->left--;
|
|
if (!(got = get_var_num(ev->data = trk->cur,
|
|
trk->left, &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 = reinterpret_cast<unsigned int *>(mhdr->lpData + mhdr->dwBytesRecorded);
|
|
unsigned int *pend = reinterpret_cast<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(reinterpret_cast<uint8_t *>(p), hmp->pending, i);
|
|
hmp->pending_size -= i;
|
|
p += (i + 3) / 4;
|
|
} else {
|
|
if ((i = get_event(hmp, &ev))) {
|
|
mhdr->dwBytesRecorded = (reinterpret_cast<uint8_t *>(p)) - (reinterpret_cast<uint8_t *>(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++) = ((static_cast<DWORD>(MEVT_SHORTMSG)) << 24) |
|
|
((DWORD)ev.msg[0]) |
|
|
(((DWORD)ev.msg[1]) << 8) |
|
|
(((DWORD)ev.msg[2]) << 16);
|
|
}
|
|
}
|
|
}
|
|
mhdr->dwBytesRecorded = (reinterpret_cast<uint8_t *>(p)) - (reinterpret_cast<uint8_t *>(mhdr->lpData));
|
|
return 0;
|
|
}
|
|
|
|
static int setup_buffers(hmp_file *hmp) {
|
|
MIDIHDR *buf, *lastbuf;
|
|
|
|
lastbuf = NULL;
|
|
for (int i = 0; i < HMP_BUFFERS; i++) {
|
|
if (!(buf = (MIDIHDR *)d_malloc(HMP_BUFSIZE + sizeof(MIDIHDR))))
|
|
return HMP_OUT_OF_MEM;
|
|
memset(buf, 0, sizeof(MIDIHDR));
|
|
buf->lpData = reinterpret_cast<char *>(buf) + sizeof(MIDIHDR);
|
|
buf->dwBufferLength = HMP_BUFSIZE;
|
|
buf->dwUser = reinterpret_cast<uintptr_t>(hmp);
|
|
buf->lpNext = lastbuf;
|
|
lastbuf = buf;
|
|
}
|
|
hmp->evbuf = lastbuf;
|
|
return 0;
|
|
}
|
|
|
|
static void reset_tracks(struct hmp_file *hmp)
|
|
{
|
|
for (int i = 0; i < hmp->num_trks; i++) {
|
|
if (hmp->trks[i].loop_set)
|
|
hmp->trks[i].cur = hmp->trks[i].loop;
|
|
else
|
|
hmp->trks[i].cur = hmp->trks[i].data.get();
|
|
hmp->trks[i].left = hmp->trks[i].len;
|
|
hmp->trks[i].cur_time = 0;
|
|
}
|
|
hmp->cur_time = 0;
|
|
}
|
|
|
|
static void _stdcall midi_callback(HMIDISTRM, UINT uMsg, DWORD, DWORD_PTR dw1, DWORD)
|
|
{
|
|
hmp_file *hmp;
|
|
int rc;
|
|
|
|
if (uMsg != MOM_DONE)
|
|
return;
|
|
|
|
const auto mhdr = reinterpret_cast<MIDIHDR *>(dw1);
|
|
mhdr->dwBytesRecorded = 0;
|
|
hmp = reinterpret_cast<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;
|
|
|
|
hmp->looping = 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 = reinterpret_cast<unsigned int *>(mhdr->lpData + mhdr->dwBytesRecorded);
|
|
*(p++) = 0;
|
|
*(p++) = 0;
|
|
*(p++) = ((static_cast<DWORD>(MEVT_TEMPO))<<24) | tempo;
|
|
mhdr->dwBytesRecorded += 12;
|
|
}
|
|
|
|
void hmp_setvolume(hmp_file *hmp, int volume)
|
|
{
|
|
if (hmp)
|
|
for (int channel = 0; channel < 16; channel++)
|
|
midiOutShortMsg(reinterpret_cast<HMIDIOUT>(hmp->hmidi), static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_VOLUME << 8 | (channel_volume[channel] * volume / MIDI_VOLUME_SCALE) << 16));
|
|
|
|
midi_volume = volume;
|
|
}
|
|
|
|
int hmp_play(hmp_file *hmp, int bLoop)
|
|
{
|
|
int rc;
|
|
MIDIPROPTIMEDIV mptd;
|
|
|
|
hmp->bLoop = bLoop;
|
|
hmp->loop_start = 0;
|
|
hmp->loop_end = 0;
|
|
hmp->looping = 0;
|
|
hmp->devid = MIDI_MAPPER;
|
|
|
|
if ((rc = setup_buffers(hmp)))
|
|
return rc;
|
|
if ((midiStreamOpen(&hmp->hmidi, &hmp->devid,1, static_cast<DWORD>(reinterpret_cast<uintptr_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, reinterpret_cast<BYTE *>(&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(reinterpret_cast<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;
|
|
}
|
|
|
|
void hmp_pause(hmp_file *hmp)
|
|
{
|
|
if (hmp)
|
|
midiStreamPause(hmp->hmidi);
|
|
}
|
|
|
|
void hmp_resume(hmp_file *hmp)
|
|
{
|
|
if (hmp)
|
|
midiStreamRestart(hmp->hmidi);
|
|
}
|
|
|
|
void hmp_reset()
|
|
{
|
|
HMIDIOUT hmidi;
|
|
MIDIHDR mhdr;
|
|
MMRESULT rval;
|
|
char GS_Reset[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
|
|
|
|
|
|
if ((rval = midiOutOpen(&hmidi, MIDI_MAPPER, 0, 0, 0)) != MMSYSERR_NOERROR)
|
|
{
|
|
switch (rval)
|
|
{
|
|
case MIDIERR_NODEVICE:
|
|
con_printf(CON_DEBUG, "midiOutOpen Error: no MIDI port was found.");
|
|
break;
|
|
case MMSYSERR_ALLOCATED:
|
|
con_printf(CON_DEBUG, "midiOutOpen Error: specified resource is already allocated.");
|
|
break;
|
|
case MMSYSERR_BADDEVICEID:
|
|
con_printf(CON_DEBUG, "midiOutOpen Error: specified device identifier is out of range.");
|
|
break;
|
|
case MMSYSERR_INVALPARAM:
|
|
con_printf(CON_DEBUG, "midiOutOpen Error: specified pointer or structure is invalid.");
|
|
break;
|
|
case MMSYSERR_NOMEM:
|
|
con_printf(CON_DEBUG, "midiOutOpen Error: unable to allocate or lock memory.");
|
|
break;
|
|
default:
|
|
con_printf(CON_DEBUG, "midiOutOpen Error code %i",rval);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (int channel = 0; channel < 16; channel++)
|
|
{
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_ALL_SOUNDS_OFF << 8 | 0 << 16));
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_RESET_ALL_CONTROLLERS << 8 | 0 << 16));
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_ALL_NOTES_OFF << 8 | 0 << 16));
|
|
|
|
channel_volume[channel] = 100;
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_PANPOT << 8 | 64 << 16));
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_REVERB << 8 | 40 << 16));
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_CHORUS << 8 | 0 << 16));
|
|
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_BANK_SELECT_MSB << 8 | 0 << 16));
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_BANK_SELECT_LSB << 8 | 0 << 16));
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_PROGRAM_CHANGE << 4 | 0 << 8));
|
|
}
|
|
|
|
mhdr.lpData = GS_Reset;
|
|
mhdr.dwBufferLength = sizeof(GS_Reset);
|
|
mhdr.dwFlags = 0;
|
|
if ((rval = midiOutPrepareHeader(hmidi, &mhdr, sizeof(MIDIHDR))) == MMSYSERR_NOERROR)
|
|
{
|
|
if ((rval = midiOutLongMsg(hmidi, &mhdr, sizeof(MIDIHDR))) == MMSYSERR_NOERROR)
|
|
{
|
|
fix64 wait_done = timer_query();
|
|
while (!(mhdr.dwFlags & MHDR_DONE))
|
|
{
|
|
auto timer = timer_update();
|
|
if (timer >= wait_done + F1_0)
|
|
{
|
|
con_printf(CON_DEBUG, "hmp_reset: Timeout waiting for MHDR_DONE");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (rval)
|
|
{
|
|
case MIDIERR_NOTREADY:
|
|
con_printf(CON_DEBUG, "midiOutLongMsg Error: the hardware is busy with other data.");
|
|
break;
|
|
case MIDIERR_UNPREPARED:
|
|
con_printf(CON_DEBUG, "midiOutLongMsg Error: the buffer pointed to by lpMidiOutHdr has not been prepared.");
|
|
break;
|
|
case MMSYSERR_INVALHANDLE:
|
|
con_printf(CON_DEBUG, "midiOutLongMsg Error: the specified device handle is invalid.");
|
|
break;
|
|
case MMSYSERR_INVALPARAM:
|
|
con_printf(CON_DEBUG, "midiOutLongMsg Error: the specified pointer or structure is invalid.");
|
|
break;
|
|
default:
|
|
con_printf(CON_DEBUG, "midiOutLongMsg Error code %i",rval);
|
|
break;
|
|
}
|
|
}
|
|
midiOutUnprepareHeader(hmidi, &mhdr, sizeof(MIDIHDR));
|
|
|
|
timer_delay(F1_0/20);
|
|
}
|
|
else
|
|
{
|
|
switch (rval)
|
|
{
|
|
case MMSYSERR_INVALHANDLE:
|
|
con_printf(CON_DEBUG, "midiOutPrepareHeader Error: The specified device handle is invalid.");
|
|
break;
|
|
case MMSYSERR_INVALPARAM:
|
|
con_printf(CON_DEBUG, "midiOutPrepareHeader Error: The specified address is invalid or the given stream buffer is greater than 64K.");
|
|
break;
|
|
case MMSYSERR_NOMEM:
|
|
con_printf(CON_DEBUG, "midiOutPrepareHeader Error: The system is unable to allocate or lock memory.");
|
|
break;
|
|
default:
|
|
con_printf(CON_DEBUG, "midiOutPrepareHeader Error code %i",rval);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int channel = 0; channel < 16; channel++)
|
|
midiOutShortMsg(hmidi, static_cast<DWORD>(channel | MIDI_CONTROL_CHANGE << 4 | MIDI_VOLUME << 8 | (100 * midi_volume / MIDI_VOLUME_SCALE) << 16));
|
|
midiOutClose(hmidi);
|
|
}
|
|
#endif
|
|
|
|
// CONVERSION FROM HMP TO MIDI
|
|
|
|
static void hmptrk2mid(ubyte* data, int size, std::vector<uint8_t> &midbuf)
|
|
{
|
|
uint8_t *dptr = data;
|
|
ubyte lc1 = 0,last_com = 0;
|
|
uint d;
|
|
int n1;
|
|
|
|
while (data < dptr + size)
|
|
{
|
|
if (data[0] & 0x80) {
|
|
ubyte b = (data[0] & 0x7F);
|
|
midbuf.emplace_back(b);
|
|
}
|
|
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)
|
|
throw std::runtime_error("bad HMP");
|
|
}
|
|
for(int n2 = 0; n2 <= n1; n2++) {
|
|
ubyte b = (data[n1 - n2] & 0x7F);
|
|
|
|
if (n2 != n1)
|
|
b |= 0x80;
|
|
midbuf.emplace_back(b);
|
|
}
|
|
data += n1;
|
|
}
|
|
data++;
|
|
if (*data == 0xFF) { //meta?
|
|
midbuf.insert(midbuf.end(), data, data + 3 + data[2]);
|
|
if (data[1] == 0x2F)
|
|
break;
|
|
}
|
|
else {
|
|
lc1=data[0] ;
|
|
if ((lc1&0x80) == 0)
|
|
throw std::runtime_error("bad HMP");
|
|
switch (lc1 & 0xF0) {
|
|
case 0x80:
|
|
case 0x90:
|
|
case 0xA0:
|
|
case 0xB0:
|
|
case 0xE0:
|
|
if (lc1 != last_com)
|
|
{
|
|
midbuf.emplace_back(lc1);
|
|
}
|
|
midbuf.insert(midbuf.end(), data + 1, data + 3);
|
|
data += 3;
|
|
break;
|
|
case 0xC0:
|
|
case 0xD0:
|
|
if (lc1 != last_com)
|
|
{
|
|
midbuf.emplace_back(lc1);
|
|
}
|
|
midbuf.emplace_back(data[1]);
|
|
data += 2;
|
|
break;
|
|
default:
|
|
throw std::runtime_error("bad HMP");
|
|
}
|
|
last_com = lc1;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct be_bytebuffer_t : serial::writer::bytebuffer_t
|
|
{
|
|
be_bytebuffer_t(pointer u) : bytebuffer_t(u) {}
|
|
static uint16_t endian() { return big_endian; }
|
|
};
|
|
|
|
const array<uint8_t, 10> magic_header{{
|
|
'M', 'T', 'h', 'd',
|
|
0, 0, 0, 6,
|
|
0, 1,
|
|
}};
|
|
const array<uint8_t, 19> tempo{{'M','T','r','k',0,0,0,11,0,0xFF,0x51,0x03,0x18,0x80,0x00,0,0xFF,0x2F,0}};
|
|
const array<uint8_t, 8> track_header{{'M', 'T', 'r', 'k', 0, 0, 0, 0}};
|
|
|
|
struct midhdr
|
|
{
|
|
int16_t num_trks;
|
|
int16_t time_div;
|
|
midhdr(hmp_file *hmp) :
|
|
num_trks(hmp->num_trks), time_div(hmp->tempo*1.6)
|
|
{
|
|
}
|
|
};
|
|
|
|
DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(midhdr, m, (magic_header, m.num_trks, m.time_div, tempo));
|
|
|
|
void hmp2mid(const char *hmp_name, std::vector<uint8_t> &midbuf)
|
|
{
|
|
std::unique_ptr<hmp_file> hmp = hmp_open(hmp_name);
|
|
if (!hmp)
|
|
return;
|
|
|
|
const midhdr mh(hmp.get());
|
|
// write MIDI-header
|
|
midbuf.resize(serial::message_type<decltype(mh)>::maximum_size);
|
|
be_bytebuffer_t bb(&midbuf[0]);
|
|
serial::process_buffer(bb, mh);
|
|
|
|
// tracks
|
|
for (int i = 1; i < hmp->num_trks; i++)
|
|
{
|
|
midbuf.insert(midbuf.end(), track_header.begin(), track_header.end());
|
|
auto size_before = midbuf.size();
|
|
auto midtrklenpos = midbuf.size() - 4;
|
|
hmptrk2mid(hmp->trks[i].data.get(), hmp->trks[i].len, midbuf);
|
|
auto size_after = midbuf.size();
|
|
be_bytebuffer_t bbmi(&midbuf[midtrklenpos]);
|
|
serial::process_buffer(bbmi, static_cast<int32_t>(size_after - size_before));
|
|
}
|
|
}
|
|
|
|
}
|