aboutsummaryrefslogtreecommitdiff
path: root/src/cvt_mus2mid.hpp
diff options
context:
space:
mode:
authorVitaly Novichkov <admin@wohlnet.ru>2018-06-21 04:23:14 +0300
committerVitaly Novichkov <admin@wohlnet.ru>2018-06-21 04:23:14 +0300
commit9f0f9e4e374798851da39335406ac4bafad81297 (patch)
tree28b744d0e52afc29c1d27f06b227e3101a8844a8 /src/cvt_mus2mid.hpp
parenta0fa0cded46bdc94293f02d4070a69bcae5478bb (diff)
downloadlibADLMIDI-9f0f9e4e374798851da39335406ac4bafad81297.tar.gz
libADLMIDI-9f0f9e4e374798851da39335406ac4bafad81297.tar.bz2
libADLMIDI-9f0f9e4e374798851da39335406ac4bafad81297.zip
Move MIDI sequencer into completely separated class
TODO: - implement C bindings for most of class functions - test it in work on any different synthesizer
Diffstat (limited to 'src/cvt_mus2mid.hpp')
-rw-r--r--src/cvt_mus2mid.hpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/cvt_mus2mid.hpp b/src/cvt_mus2mid.hpp
new file mode 100644
index 0000000..5a465c2
--- /dev/null
+++ b/src/cvt_mus2mid.hpp
@@ -0,0 +1,461 @@
+/*
+ * MUS2MIDI: MUS to MIDI Library
+ *
+ * Copyright (C) 2014 Bret Curtis
+ * Copyright (C) WildMIDI Developers 2015-2016
+ * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#ifdef __DJGPP__
+typedef signed char int8_t;
+typedef unsigned char uint8_t;
+typedef signed short int16_t;
+typedef unsigned short uint16_t;
+typedef signed long int32_t;
+typedef unsigned long uint32_t;
+#endif
+
+#define MUS_FREQUENCY 140 /* default Hz or BPM */
+
+#if 0 /* older units: */
+#define MUS_TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */
+#define MUS_DIVISION 0x0059 /* 89 -- used by many mus2midi converters */
+#endif
+
+#define MUS_TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */
+ /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */
+
+#define MUS_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 MUS_MIDI_MAXCHANNELS 16
+
+static char MUS_ID[] = { 'M', 'U', 'S', 0x1A };
+
+static uint8_t mus_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 {
+ 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 mus2mid_resize_dst(struct mus_ctx *ctx) {
+ uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst);
+ ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + DST_CHUNK);
+ ctx->dstsize += DST_CHUNK;
+ ctx->dstrem += DST_CHUNK;
+ ctx->dst_ptr = ctx->dst + pos;
+}
+
+static void mus2mid_write1(struct mus_ctx *ctx, uint32_t val)
+{
+ if (ctx->dstrem < 1)
+ mus2mid_resize_dst(ctx);
+ *ctx->dst_ptr++ = val & 0xff;
+ ctx->dstrem--;
+}
+
+static void mus2mid_write2(struct mus_ctx *ctx, uint32_t val)
+{
+ if (ctx->dstrem < 2)
+ mus2mid_resize_dst(ctx);
+ *ctx->dst_ptr++ = (val>>8) & 0xff;
+ *ctx->dst_ptr++ = val & 0xff;
+ ctx->dstrem -= 2;
+}
+
+static void mus2mid_write4(struct mus_ctx *ctx, uint32_t val)
+{
+ if (ctx->dstrem < 4)
+ mus2mid_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 mus2mid_seekdst(struct mus_ctx *ctx, uint32_t pos) {
+ ctx->dst_ptr = ctx->dst + pos;
+ while (ctx->dstsize < pos)
+ mus2mid_resize_dst(ctx);
+ ctx->dstrem = ctx->dstsize - pos;
+}
+
+static void mus2mid_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)
+ mus2mid_resize_dst(ctx);
+ ctx->dstrem = (uint32_t)(ctx->dstsize - newpos);
+}
+
+static uint32_t mus2mid_getdstpos(struct mus_ctx *ctx) {
+ return (uint32_t)(ctx->dst_ptr - ctx->dst);
+}
+
+/* writes a variable length integer to a buffer, and returns bytes written */
+static int32_t mus2mid_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 MUS_READ_INT16(b) ((b)[0] | ((b)[1] << 8))
+#define MUS_READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24))
+
+static int Convert_mus2midi(uint8_t *in, uint32_t insize,
+ uint8_t **out, uint32_t *outsize,
+ uint16_t frequency)
+{
+ struct mus_ctx ctx;
+ MUSHeader header;
+ 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[MUS_MIDI_MAXCHANNELS];
+ int channelMap[MUS_MIDI_MAXCHANNELS], currentChannel;
+
+ if (insize < MUS_HEADERSIZE) {
+ /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/
+ return (-1);
+ }
+
+ if (!frequency)
+ frequency = MUS_FREQUENCY;
+
+ /* read the MUS header and set our location */
+ memcpy(header.ID, in, 4);
+ header.scoreLen = MUS_READ_INT16(&in[4]);
+ header.scoreStart = MUS_READ_INT16(&in[6]);
+ header.channels = MUS_READ_INT16(&in[8]);
+ header.sec_channels = MUS_READ_INT16(&in[10]);
+ header.instrCnt = MUS_READ_INT16(&in[12]);
+
+ if (memcmp(header.ID, MUS_ID, 4)) {
+ /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, NULL, 0);*/
+ return (-1);
+ }
+ if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) {
+ /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/
+ return (-1);
+ }
+ /* channel #15 should be excluded in the numchannels field: */
+ if (header.channels > MUS_MIDI_MAXCHANNELS - 1) {
+ /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/
+ return (-1);
+ }
+
+ memset(&ctx, 0, sizeof(struct mus_ctx));
+ ctx.src = ctx.src_ptr = in;
+ ctx.srcsize = insize;
+
+ ctx.dst = (uint8_t*)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 < MUS_MIDI_MAXCHANNELS; ++temp) {
+ channelMap[temp] = -1;
+ channel_volume[temp] = 0x40;
+ }
+ channelMap[15] = 9;
+
+ /* Header is 14 bytes long and add the rest as well */
+ mus2mid_write1(&ctx, 'M');
+ mus2mid_write1(&ctx, 'T');
+ mus2mid_write1(&ctx, 'h');
+ mus2mid_write1(&ctx, 'd');
+ mus2mid_write4(&ctx, 6); /* length of header */
+ mus2mid_write2(&ctx, 0); /* MIDI type (always 0) */
+ mus2mid_write2(&ctx, 1); /* MUS files only have 1 track */
+ mus2mid_write2(&ctx, MUS_DIVISION); /* division */
+
+ /* Write out track header and track length position for later */
+ begin_track_pos = mus2mid_getdstpos(&ctx);
+ mus2mid_write1(&ctx, 'M');
+ mus2mid_write1(&ctx, 'T');
+ mus2mid_write1(&ctx, 'r');
+ mus2mid_write1(&ctx, 'k');
+ track_size_pos = mus2mid_getdstpos(&ctx);
+ mus2mid_skipdst(&ctx, 4);
+
+ /* write tempo: microseconds per quarter note */
+ mus2mid_write1(&ctx, 0x00); /* delta time */
+ mus2mid_write1(&ctx, 0xff); /* sys command */
+ mus2mid_write2(&ctx, 0x5103); /* command - set tempo */
+ mus2mid_write1(&ctx, MUS_TEMPO & 0x000000ff);
+ mus2mid_write1(&ctx, (MUS_TEMPO & 0x0000ff00) >> 8);
+ mus2mid_write1(&ctx, (MUS_TEMPO & 0x00ff0000) >> 16);
+
+ /* Percussions channel starts out at full volume */
+ mus2mid_write1(&ctx, 0x00);
+ mus2mid_write1(&ctx, 0xB9);
+ mus2mid_write1(&ctx, 0x07);
+ mus2mid_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){
+ /*printf("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 += mus2mid_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++;
+ 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(mus_midimap) / sizeof(mus_midimap[0])) {
+ /*_WM_ERROR_NEW("%s:%i: can't map %u to midi",
+ __FUNCTION__, __LINE__, *cur);*/
+ goto _end;
+ }
+ bit1 = mus_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(mus_midimap) / sizeof(mus_midimap[0])) {
+ /*_WM_ERROR_NEW("%s:%i: can't map %u to midi",
+ __FUNCTION__, __LINE__, *cur);*/
+ goto _end;
+ }
+ bit1 = mus_midimap[*cur++];
+ bit2 = *cur++;
+ }
+ break;
+ case MUSEVENT_END: /* End */
+ status = 0xff;
+ bit1 = 0x2f;
+ bit2 = 0x00;
+ if (cur != end) { /* should we error here or report-only? */
+ /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes",
+ __FUNCTION__, __LINE__, (long)(cur - end));*/
+ }
+ break;
+ case 5:/* Unknown */
+ case 7:/* Unknown */
+ default:/* shouldn't happen */
+ /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)",
+ __FUNCTION__, __LINE__, 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))
+ mus2mid_resize_dst(&ctx);
+
+ memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer);
+ ctx.dst_ptr += out_local - temp_buffer;
+ ctx.dstrem -= (uint32_t)(out_local - temp_buffer);
+ }
+
+ if (event & 128) {
+ delta_time = 0;
+ do {
+ delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency));
+ } while ((*cur++ & 128));
+ } else {
+ delta_time = 0;
+ }
+ }
+
+ /* write out track length */
+ current_pos = mus2mid_getdstpos(&ctx);
+ mus2mid_seekdst(&ctx, track_size_pos);
+ mus2mid_write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE);
+ mus2mid_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);
+}
+