ref: 3191e80bf7fda36bf0c951b0363cd52fa6ceac6b
dir: /mus.c/
/*
MUS2MIDI: MUS to MIDI Library
Copyright (C) 2014 Bret Curtis
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <stddef.h>
#include <stdio.h> /* fprintf */
#include <stdlib.h>
#include <string.h>
#include "mus.h"
#define FREQUENCY 140 /* default Hz or BPM */
#if 0 /* older units: */
#define TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */
#define DIVISION 0x0059 /* 89 -- used by many mus2midi converters */
#endif
#define TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */
/* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */
#define DIVISION 0x0101 /* 257 for 140Hz files with a 140MPQN */
/* 0x0088 -> 136 for 70Hz files with a 140MPQN */
/* 0x010B -> 267 for 70hz files with a 70MPQN */
/* 0x01F9 -> 505 for 140hz files with a 70MPQN */
/* New
* QLS: MPQN/1000000 = 0.428571
* TDPS: QLS/PPQN = 0.428571/136 = 0.003151257
* PPQN: 136
*
* QLS: MPQN/1000000 = 0.428571
* TDPS: QLS/PPQN = 0.428571/257 = 0.001667591
* PPQN: 257
*
* QLS: MPQN/1000000 = 0.857142
* TDPS: QLS/PPQN = 0.857142/267 = 0.00321027
* PPQN: 267
*
* QLS: MPQN/1000000 = 0.857142
* TDPS: QLS/PPQN = 0.857142/505 = 0.001697311
* PPQN: 505
*
* Old
* QLS: MPQN/1000000 = 1.745673
* TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick)
* PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN = 1.745673) (PPQN = 89.000001682)
*/
#define MUSEVENT_KEYOFF 0
#define MUSEVENT_KEYON 1
#define MUSEVENT_PITCHWHEEL 2
#define MUSEVENT_CHANNELMODE 3
#define MUSEVENT_CONTROLLERCHANGE 4
#define MUSEVENT_END 6
#define MIDI_MAXCHANNELS 16
static const char MUS_ID[] = { 'M', 'U', 'S', 0x1A };
static const uint8_t midimap[] =
{/* MIDI Number Description */
0, /* 0 program change */
0, /* 1 bank selection */
0x01, /* 2 Modulation pot (frequency vibrato depth) */
0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */
0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */
0x0B, /* 5 Expression pot */
0x5B, /* 6 Reverb depth */
0x5D, /* 7 Chorus depth */
0x40, /* 8 Sustain pedal */
0x43, /* 9 Soft pedal */
0x78, /* 10 All sounds off */
0x7B, /* 11 All notes off */
0x7E, /* 12 Mono (use numchannels + 1) */
0x7F, /* 13 Poly */
0x79, /* 14 reset all controllers */
};
typedef struct MUSHeader {
char ID[4]; /* identifier: "MUS" 0x1A */
uint16_t scoreLen;
uint16_t scoreStart;
uint16_t channels; /* count of primary channels */
uint16_t sec_channels; /* count of secondary channels */
uint16_t instrCnt;
} MUSHeader ;
#define MUS_HEADERSIZE 14
typedef struct MidiHeaderChunk {
char name[4];
int32_t length;
int16_t format; /* make 0 */
int16_t ntracks;/* make 1 */
int16_t division; /* 0xe250 ?? */
} MidiHeaderChunk;
#define MIDI_HEADERSIZE 14
typedef struct MidiTrackChunk {
char name[4];
int32_t length;
} MidiTrackChunk;
#define TRK_CHUNKSIZE 8
struct mus_ctx {
const uint8_t *src, *src_ptr;
uint32_t srcsize;
uint32_t datastart;
uint8_t *dst, *dst_ptr;
uint32_t dstsize, dstrem;
};
#define DST_CHUNK 8192
static void resize_dst(struct mus_ctx *ctx) {
uint32_t pos = ctx->dst_ptr - ctx->dst;
ctx->dst = realloc(ctx->dst, ctx->dstsize + DST_CHUNK);
ctx->dstsize += DST_CHUNK;
ctx->dstrem += DST_CHUNK;
ctx->dst_ptr = ctx->dst + pos;
}
static void write1(struct mus_ctx *ctx, uint32_t val)
{
if (ctx->dstrem < 1)
resize_dst(ctx);
*ctx->dst_ptr++ = val & 0xff;
ctx->dstrem--;
}
static void write2(struct mus_ctx *ctx, uint32_t val)
{
if (ctx->dstrem < 2)
resize_dst(ctx);
*ctx->dst_ptr++ = (val>>8) & 0xff;
*ctx->dst_ptr++ = val & 0xff;
ctx->dstrem -= 2;
}
static void write4(struct mus_ctx *ctx, uint32_t val)
{
if (ctx->dstrem < 4)
resize_dst(ctx);
*ctx->dst_ptr++ = (val>>24)&0xff;
*ctx->dst_ptr++ = (val>>16)&0xff;
*ctx->dst_ptr++ = (val>>8) & 0xff;
*ctx->dst_ptr++ = val & 0xff;
ctx->dstrem -= 4;
}
static void seekdst(struct mus_ctx *ctx, uint32_t pos) {
ctx->dst_ptr = ctx->dst + pos;
while (ctx->dstsize < pos)
resize_dst(ctx);
ctx->dstrem = ctx->dstsize - pos;
}
static void skipdst(struct mus_ctx *ctx, int32_t pos) {
size_t newpos;
ctx->dst_ptr += pos;
newpos = ctx->dst_ptr - ctx->dst;
while (ctx->dstsize < newpos)
resize_dst(ctx);
ctx->dstrem = ctx->dstsize - newpos;
}
static uint32_t getdstpos(struct mus_ctx *ctx) {
return (ctx->dst_ptr - ctx->dst);
}
/* writes a variable length integer to a buffer, and returns bytes written */
static int32_t writevarlen(int32_t value, uint8_t *out)
{
int32_t buffer, count = 0;
buffer = value & 0x7f;
while ((value >>= 7) > 0) {
buffer <<= 8;
buffer += 0x80;
buffer += (value & 0x7f);
}
while (1) {
++count;
*out = (uint8_t)buffer;
++out;
if (buffer & 0x80)
buffer >>= 8;
else
break;
}
return (count);
}
#define READ_INT16(b) ((b)[0] | ((b)[1] << 8))
#define READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24))
int mus2midi(const uint8_t *in, uint32_t insize,
uint8_t **out, uint32_t *outsize,
uint16_t frequency) {
struct mus_ctx ctx;
MUSHeader header;
const uint8_t *cur, *end;
uint32_t track_size_pos, begin_track_pos, current_pos;
int32_t delta_time; /* Delta time for midi event */
int temp, ret = -1;
int channel_volume[MIDI_MAXCHANNELS];
int channelMap[MIDI_MAXCHANNELS], currentChannel;
if (insize < MUS_HEADERSIZE) {
fprintf(stderr, "mus2midi: file too short\n");
return (-1);
}
if (!frequency)
frequency = FREQUENCY;
/* read the MUS header and set our location */
memcpy(header.ID, in, 4);
header.scoreLen = READ_INT16(&in[4]);
header.scoreStart = READ_INT16(&in[6]);
header.channels = READ_INT16(&in[8]);
header.sec_channels = READ_INT16(&in[10]);
header.instrCnt = READ_INT16(&in[12]);
if (memcmp(header.ID, MUS_ID, 4)) {
fprintf(stderr, "mus2midi: not a MUS file\n");
return (-1);
}
if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) {
fprintf(stderr, "mus2midi: file too short\n");
return (-1);
}
/* channel #15 should be excluded in the numchannels field: */
if (header.channels > MIDI_MAXCHANNELS - 1) {
fprintf(stderr, "mus2midi: bad MUS file\n");
return (-1);
}
memset(&ctx, 0, sizeof(struct mus_ctx));
ctx.src = ctx.src_ptr = in;
ctx.srcsize = insize;
ctx.dst = calloc(DST_CHUNK, sizeof(uint8_t));
ctx.dst_ptr = ctx.dst;
ctx.dstsize = DST_CHUNK;
ctx.dstrem = DST_CHUNK;
/* Map channel 15 to 9 (percussions) */
for (temp = 0; temp < MIDI_MAXCHANNELS; ++temp) {
channelMap[temp] = -1;
channel_volume[temp] = 0x40;
}
channelMap[15] = 9;
/* Header is 14 bytes long and add the rest as well */
write1(&ctx, 'M');
write1(&ctx, 'T');
write1(&ctx, 'h');
write1(&ctx, 'd');
write4(&ctx, 6); /* length of header */
write2(&ctx, 0); /* MIDI type (always 0) */
write2(&ctx, 1); /* MUS files only have 1 track */
write2(&ctx, DIVISION); /* division */
/* Write out track header and track length position for later */
begin_track_pos = getdstpos(&ctx);
write1(&ctx, 'M');
write1(&ctx, 'T');
write1(&ctx, 'r');
write1(&ctx, 'k');
track_size_pos = getdstpos(&ctx);
skipdst(&ctx, 4);
/* write tempo: microseconds per quarter note */
write1(&ctx, 0x00); /* delta time */
write1(&ctx, 0xff); /* sys command */
write2(&ctx, 0x5103); /* command - set tempo */
write1(&ctx, TEMPO & 0x000000ff);
write1(&ctx, (TEMPO & 0x0000ff00) >> 8);
write1(&ctx, (TEMPO & 0x00ff0000) >> 16);
/* Percussions channel starts out at full volume */
write1(&ctx, 0x00);
write1(&ctx, 0xB9);
write1(&ctx, 0x07);
write1(&ctx, 127);
/* get current position in source, and end of position */
cur = in + header.scoreStart;
end = cur + header.scoreLen;
currentChannel = 0;
delta_time = 0;
/* main loop */
while(cur < end){
/*fprintf(stderr, "LOOP DEBUG: %d\r\n",iterator++);*/
uint8_t channel;
uint8_t event;
uint8_t temp_buffer[32]; /* temp buffer for current iterator */
uint8_t *out_local = temp_buffer;
uint8_t status, bit1, bit2, bitc = 2;
/* read in current bit */
event = *cur++;
channel = (event & 15); /* current channel */
/* write variable length delta time */
out_local += writevarlen(delta_time, out_local);
/* set all channels to 127 (max) volume */
if (channelMap[channel] < 0) {
*out_local++ = 0xB0 + currentChannel;
*out_local++ = 0x07;
*out_local++ = 127;
*out_local++ = 0x00;
channelMap[channel] = currentChannel++;
if (currentChannel == 9)
++currentChannel;
}
status = channelMap[channel];
/* handle events */
switch ((event & 122) >> 4){
case MUSEVENT_KEYOFF:
status |= 0x80;
bit1 = *cur++;
bit2 = 0x40;
break;
case MUSEVENT_KEYON:
status |= 0x90;
bit1 = *cur & 127;
if (*cur++ & 128) { /* volume bit? */
channel_volume[channelMap[channel]] = *cur++;
/* The maximum volume is 127, but it is encoded as
a byte. Some songs erroneously use values higher
than 127, so we have to clamp them down.
https://github.com/Mindwerks/wildmidi/pull/226 */
if (channel_volume[channelMap[channel]] > 127) {
channel_volume[channelMap[channel]] = 127;
}
}
bit2 = channel_volume[channelMap[channel]];
break;
case MUSEVENT_PITCHWHEEL:
status |= 0xE0;
bit1 = (*cur & 1) >> 6;
bit2 = (*cur++ >> 1) & 127;
break;
case MUSEVENT_CHANNELMODE:
status |= 0xB0;
if (*cur >= sizeof(midimap) / sizeof(midimap[0])) {
fprintf(stderr, "mus2midi: can't map %u to midi\n", *cur);
goto _end;
}
bit1 = midimap[*cur++];
bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00;
break;
case MUSEVENT_CONTROLLERCHANGE:
if (*cur == 0) {
cur++;
status |= 0xC0;
bit1 = *cur++;
bit2 = 0; /* silence bogus warnings */
bitc = 1;
} else {
status |= 0xB0;
if (*cur >= sizeof(midimap) / sizeof(midimap[0])) {
fprintf(stderr, "mus2midi: can't map %u to midi\n", *cur);
goto _end;
}
bit1 = midimap[*cur++];
bit2 = *cur++;
/* The maximum volume is 127, but it is encoded as
a byte. Some songs erroneously use values higher
than 127, so we have to clamp them down.
https://github.com/Mindwerks/wildmidi/pull/226 */
if (bit1 == 0x07 && bit2 > 127) bit2 = 127;
}
break;
case MUSEVENT_END: /* End */
status = 0xff;
bit1 = 0x2f;
bit2 = 0x00;
if (cur != end) { /* should we error here or report-only? */
fprintf(stderr, "mus2midi: MUS buffer off by %ld bytes\n",
(long)(cur - end));
}
break;
case 5:/* Unknown */
case 7:/* Unknown */
default:/* shouldn't happen */
fprintf(stderr, "mus2midi: unrecognized event (%u)\n", event);
goto _end;
}
/* write it out */
*out_local++ = status;
*out_local++ = bit1;
if (bitc == 2)
*out_local++ = bit2;
/* write out our temp buffer */
if (out_local != temp_buffer)
{
if (ctx.dstrem < sizeof(temp_buffer))
resize_dst(&ctx);
memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer);
ctx.dst_ptr += out_local - temp_buffer;
ctx.dstrem -= out_local - temp_buffer;
}
if (event & 128) {
delta_time = 0;
do {
delta_time = (delta_time * 128 + (*cur & 127)) * (140.0 / frequency);
} while ((*cur++ & 128));
} else {
delta_time = 0;
}
}
/* write out track length */
current_pos = getdstpos(&ctx);
seekdst(&ctx, track_size_pos);
write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE);
seekdst(&ctx, current_pos); /* reseek to end position */
*out = ctx.dst;
*outsize = ctx.dstsize - ctx.dstrem;
ret = 0;
_end: /* cleanup */
if (ret < 0) {
free(ctx.dst);
*out = NULL;
*outsize = 0;
}
return (ret);
}