ref: 677f65df02d856f72640a5cb4886342f5f565b1f
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); }