aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWohlstand <admin@wohlnet.ru>2017-02-15 16:19:08 +0300
committerWohlstand <admin@wohlnet.ru>2017-02-15 16:19:08 +0300
commit9cd892706bf782c8a0e47731bd71e629d505c5cf (patch)
tree5d83247581e0b9a513be0ea38135afa3c94b440f /src
parentba0b0c4d5c1e8c8926c6838dddb0f6abdbccd9f2 (diff)
downloadlibADLMIDI-9cd892706bf782c8a0e47731bd71e629d505c5cf.tar.gz
libADLMIDI-9cd892706bf782c8a0e47731bd71e629d505c5cf.tar.bz2
libADLMIDI-9cd892706bf782c8a0e47731bd71e629d505c5cf.zip
Fixed logarithmic volumes flag and added support for XMI and MUS formats
MUS playing was totally invalid: the MUS data are completely different from regular MIDI data. Now is added a right converter which results a MIDI data which can be played correctly.
Diffstat (limited to 'src')
-rw-r--r--src/adlmidi.cpp3
-rw-r--r--src/adlmidi_load.cpp136
-rw-r--r--src/adlmidi_mus2mid.c451
-rw-r--r--src/adlmidi_mus2mid.h40
-rw-r--r--src/adlmidi_opl3.cpp1
-rw-r--r--src/adlmidi_xmi2mid.c1101
-rw-r--r--src/adlmidi_xmi2mid.h51
7 files changed, 1750 insertions, 33 deletions
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp
index 4c7fcf7..d54ce9a 100644
--- a/src/adlmidi.cpp
+++ b/src/adlmidi.cpp
@@ -265,7 +265,8 @@ ADLMIDI_EXPORT void adl_close(ADL_MIDIPlayer *device)
ADLMIDI_EXPORT void adl_reset(ADL_MIDIPlayer *device)
{
- if(!device) return;
+ if(!device)
+ return;
device->stored_samples = 0;
device->backup_samples_size = 0;
diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp
index af7cf0d..1ef355d 100644
--- a/src/adlmidi_load.cpp
+++ b/src/adlmidi_load.cpp
@@ -23,6 +23,10 @@
#include "adlmidi_private.hpp"
+#include "adlmidi_mus2mid.h"
+#include "adlmidi_xmi2mid.h"
+
+#include <memory>
uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes)
{
@@ -97,9 +101,11 @@ bool MIDIplay::LoadMIDI(void *data, unsigned long size)
bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr)
{
-#define qqq(x) (void)x
+ #define qqq(x) (void)x
size_t fsize;
qqq(fsize);
+ //! Temp buffer for conversion
+ std::shared_ptr<uint8_t> cvt_buf;
//std::FILE* fr = std::fopen(filename.c_str(), "rb");
if(!fr.isValid())
@@ -108,19 +114,8 @@ bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr)
return false;
}
- const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14
- char HeaderBuf[HeaderSize] = "";
-riffskip:
- ;
- fsize = fr.read(HeaderBuf, 1, HeaderSize);
-
- if(std::memcmp(HeaderBuf, "RIFF", 4) == 0)
- {
- fr.seek(6l, SEEK_CUR);
- goto riffskip;
- }
+ /**** Set all properties BEFORE starting of actial file reading! ****/
- size_t DeltaTicks = 192, TrackCount = 1;
config->stored_samples = 0;
config->backup_samples_size = 0;
opl.AdlPercussionMode = config->AdlPercussionMode;
@@ -165,16 +160,30 @@ riffskip:
opl.NumFourOps = config->NumFourOps;
cmf_percussion_mode = false;
opl.Reset();
- trackStart = true;
- loopStart = true;
+
+ trackStart = true;
+ loopStart = true;
loopStart_passed = false;
- invalidLoop = false;
- loopStart_hit = false;
+ invalidLoop = false;
+ loopStart_hit = false;
+
bool is_GMF = false; // GMD/MUS files (ScummVM)
- bool is_MUS = false; // MUS/DMX files (Doom)
+ //bool is_MUS = false; // MUS/DMX files (Doom)
bool is_IMF = false; // IMF
bool is_CMF = false; // Creative Music format (CMF/CTMF)
- //std::vector<unsigned char> MUS_instrumentList;
+
+ const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14
+ char HeaderBuf[HeaderSize] = "";
+ size_t DeltaTicks = 192, TrackCount = 1;
+
+riffskip:
+ fsize = fr.read(HeaderBuf, 1, HeaderSize);
+
+ if(std::memcmp(HeaderBuf, "RIFF", 4) == 0)
+ {
+ fr.seek(6l, SEEK_CUR);
+ goto riffskip;
+ }
if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0)
{
@@ -185,9 +194,74 @@ riffskip:
else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0)
{
// MUS/DMX files (Doom)
- uint64_t start = ReadLEint(HeaderBuf + 6, 2);
- is_MUS = true;
- fr.seek(static_cast<long>(start), SEEK_SET);
+ //uint64_t start = ReadLEint(HeaderBuf + 6, 2);
+ //is_MUS = true;
+ fr.seek(0, SEEK_END);
+ size_t mus_len = fr.tell();
+ fr.seek(0, SEEK_SET);
+ uint8_t *mus = (uint8_t*)malloc(mus_len);
+ if(!mus)
+ {
+ ADLMIDI_ErrorString = "Out of memory!";
+ return false;
+ }
+ fr.read(mus, 1, mus_len);
+ //Close source stream
+ fr.close();
+
+ uint8_t *mid = NULL;
+ uint32_t mid_len = 0;
+ int m2mret = AdlMidi_mus2midi(mus, static_cast<uint32_t>(mus_len),
+ &mid, &mid_len, 0);
+ if(mus) free(mus);
+ if(m2mret < 0)
+ {
+ ADLMIDI_ErrorString = "Invalid MUS/DMX data format!";
+ return false;
+ }
+ cvt_buf.reset(mid, ::free);
+ //Open converted MIDI file
+ fr.openData(mid, static_cast<size_t>(mid_len));
+ //Re-Read header again!
+ goto riffskip;
+ }
+ else if(std::memcmp(HeaderBuf, "FORM", 4) == 0)
+ {
+ if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0)
+ {
+ fr.close();
+ ADLMIDI_ErrorString = fr._fileName + ": Invalid format\n";
+ return false;
+ }
+
+ fr.seek(0, SEEK_END);
+ size_t mus_len = fr.tell();
+ fr.seek(0, SEEK_SET);
+ uint8_t *mus = (uint8_t*)malloc(mus_len);
+ if(!mus)
+ {
+ ADLMIDI_ErrorString = "Out of memory!";
+ return false;
+ }
+ fr.read(mus, 1, mus_len);
+ //Close source stream
+ fr.close();
+
+ uint8_t *mid = NULL;
+ uint32_t mid_len = 0;
+ int m2mret = AdlMidi_xmi2midi(mus, static_cast<uint32_t>(mus_len),
+ &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION);
+ if(mus) free(mus);
+ if(m2mret < 0)
+ {
+ ADLMIDI_ErrorString = "Invalid XMI data format!";
+ return false;
+ }
+ cvt_buf.reset(mid, ::free);
+ //Open converted MIDI file
+ fr.openData(mid, static_cast<size_t>(mid_len));
+ //Re-Read header again!
+ goto riffskip;
}
else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0)
{
@@ -375,14 +449,14 @@ InvFmt:
TrackLength = fr.tell() - pos;
fr.seek(static_cast<long>(pos), SEEK_SET);
}
- else if(is_MUS) // Read TrackLength from file position 4
- {
- size_t pos = fr.tell();
- fr.seek(4, SEEK_SET);
- TrackLength = static_cast<size_t>(fr.getc());
- TrackLength += static_cast<size_t>(fr.getc() << 8);
- fr.seek(static_cast<long>(pos), SEEK_SET);
- }
+ //else if(is_MUS) // Read TrackLength from file position 4
+ //{
+ // size_t pos = fr.tell();
+ // fr.seek(4, SEEK_SET);
+ // TrackLength = static_cast<size_t>(fr.getc());
+ // TrackLength += static_cast<size_t>(fr.getc() << 8);
+ // fr.seek(static_cast<long>(pos), SEEK_SET);
+ //}
else
{
fsize = fr.read(HeaderBuf, 1, 8);
@@ -398,7 +472,7 @@ InvFmt:
fsize = fr.read(&TrackData[tk][0], 1, TrackLength);
totalGotten += fsize;
- if(is_GMF || is_MUS) // Note: CMF does include the track end tag.
+ if(is_GMF /*|| is_MUS*/) // Note: CMF does include the track end tag.
TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4);
bool ok = false;
diff --git a/src/adlmidi_mus2mid.c b/src/adlmidi_mus2mid.c
new file mode 100644
index 0000000..956510b
--- /dev/null
+++ b/src/adlmidi_mus2mid.c
@@ -0,0 +1,451 @@
+/*
+ * MUS2MIDI: MUS to MIDI Library
+ *
+ * Copyright (C) 2014 Bret Curtis
+ * Copyright (C) WildMIDI Developers 2015-2016
+ * ADLMIDI Library API: Copyright (c) 2017 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 "adlmidi_mus2mid.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 char MUS_ID[] = { 'M', 'U', 'S', 0x1A };
+
+static 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 {
+ 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 AdlMidi_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[MIDI_MAXCHANNELS];
+ int channelMap[MIDI_MAXCHANNELS], currentChannel;
+
+ if (insize < MUS_HEADERSIZE) {
+ //_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);
+ 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)) {
+ //_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 > 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 = 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){
+ /*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 += 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(midimap) / sizeof(midimap[0])) {
+ /*_WM_ERROR_NEW("%s:%i: can't map %u to midi",
+ __FUNCTION__, __LINE__, *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])) {
+ /*_WM_ERROR_NEW("%s:%i: can't map %u to midi",
+ __FUNCTION__, __LINE__, *cur);*/
+ goto _end;
+ }
+ bit1 = 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))
+ 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);
+}
+
diff --git a/src/adlmidi_mus2mid.h b/src/adlmidi_mus2mid.h
new file mode 100644
index 0000000..c56c359
--- /dev/null
+++ b/src/adlmidi_mus2mid.h
@@ -0,0 +1,40 @@
+/*
+ * MUS2MIDI: DMX (DOOM) MUS to MIDI Library Header
+ *
+ * Copyright (C) 2014-2016 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.
+ */
+
+#ifndef MUSLIB_H
+#define MUSLIB_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+int AdlMidi_mus2midi(uint8_t *in, uint32_t insize,
+ uint8_t **out, uint32_t *outsize,
+ uint16_t frequency);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MUSLIB_H */
diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp
index a94b553..27680f5 100644
--- a/src/adlmidi_opl3.cpp
+++ b/src/adlmidi_opl3.cpp
@@ -388,7 +388,6 @@ void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel)
void OPL3::Reset()
{
- LogarithmicVolumes = false;
#ifdef ADLMIDI_USE_DOSBOX_OPL
DBOPL::Handler emptyChip; //Constructors inside are will initialize necessary fields
#else
diff --git a/src/adlmidi_xmi2mid.c b/src/adlmidi_xmi2mid.c
new file mode 100644
index 0000000..5d8cedc
--- /dev/null
+++ b/src/adlmidi_xmi2mid.c
@@ -0,0 +1,1101 @@
+/*
+ * XMIDI: Miles XMIDI to MID Library
+ *
+ * Copyright (C) 2001 Ryan Nunn
+ * Copyright (C) 2014 Bret Curtis
+ * Copyright (C) WildMIDI Developers 2015-2016
+ * Copyright (c) 2017 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.
+ */
+
+/* XMIDI Converter */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "adlmidi_xmi2mid.h"
+
+/* Midi Status Bytes */
+#define MIDI_STATUS_NOTE_OFF 0x8
+#define MIDI_STATUS_NOTE_ON 0x9
+#define MIDI_STATUS_AFTERTOUCH 0xA
+#define MIDI_STATUS_CONTROLLER 0xB
+#define MIDI_STATUS_PROG_CHANGE 0xC
+#define MIDI_STATUS_PRESSURE 0xD
+#define MIDI_STATUS_PITCH_WHEEL 0xE
+#define MIDI_STATUS_SYSEX 0xF
+
+typedef struct _midi_event {
+ int32_t time;
+ uint8_t status;
+ uint8_t data[2];
+ uint32_t len;
+ uint8_t *buffer;
+ struct _midi_event *next;
+} midi_event;
+
+typedef struct {
+ uint16_t type;
+ uint16_t tracks;
+} midi_descriptor;
+
+struct xmi_ctx {
+ uint8_t *src, *src_ptr;
+ uint32_t srcsize;
+ uint32_t datastart;
+ uint8_t *dst, *dst_ptr;
+ uint32_t dstsize, dstrem;
+ uint32_t convert_type;
+ midi_descriptor info;
+ int bank127[16];
+ midi_event **events;
+ signed short *timing;
+ midi_event *list;
+ midi_event *current;
+};
+
+/* forward declarations of private functions */
+static void DeleteEventList(midi_event *mlist);
+static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time); /* List manipulation */
+static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant); /* Variable length quantity */
+static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant);/* Variable length quantity */
+static int PutVLQ(struct xmi_ctx *ctx, uint32_t value); /* Variable length quantity */
+static int ConvertEvent(struct xmi_ctx *ctx,
+ const int32_t time, const uint8_t status, const int size);
+static int32_t ConvertSystemMessage(struct xmi_ctx *ctx,
+ const int32_t time, const uint8_t status);
+static int32_t ConvertFiletoList(struct xmi_ctx *ctx);
+static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist);
+static int ParseXMI(struct xmi_ctx *ctx);
+static int ExtractTracks(struct xmi_ctx *ctx);
+static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx);
+
+static uint32_t read1(struct xmi_ctx *ctx)
+{
+ uint8_t b0;
+ b0 = *ctx->src_ptr++;
+ return (b0);
+}
+
+static uint32_t read2(struct xmi_ctx *ctx)
+{
+ uint8_t b0, b1;
+ b0 = *ctx->src_ptr++;
+ b1 = *ctx->src_ptr++;
+ return (b0 + (b1 << 8));
+}
+
+static uint32_t read4(struct xmi_ctx *ctx)
+{
+ uint8_t b0, b1, b2, b3;
+ b3 = *ctx->src_ptr++;
+ b2 = *ctx->src_ptr++;
+ b1 = *ctx->src_ptr++;
+ b0 = *ctx->src_ptr++;
+ return (b0 + (b1<<8) + (b2<<16) + (b3<<24));
+}
+
+static void copy(struct xmi_ctx *ctx, char *b, uint32_t len)
+{
+ memcpy(b, ctx->src_ptr, len);
+ ctx->src_ptr += len;
+}
+
+#define DST_CHUNK 8192
+static void resize_dst(struct xmi_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 xmi_ctx *ctx, uint32_t val)
+{
+ if (ctx->dstrem < 1)
+ resize_dst(ctx);
+ *ctx->dst_ptr++ = val & 0xff;
+ ctx->dstrem--;
+}
+
+static void write2(struct xmi_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 xmi_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 seeksrc(struct xmi_ctx *ctx, uint32_t pos) {
+ ctx->src_ptr = ctx->src + pos;
+}
+
+static void seekdst(struct xmi_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 skipsrc(struct xmi_ctx *ctx, int32_t pos) {
+ ctx->src_ptr += pos;
+}
+
+static void skipdst(struct xmi_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 getsrcsize(struct xmi_ctx *ctx) {
+ return (ctx->srcsize);
+}
+
+static uint32_t getsrcpos(struct xmi_ctx *ctx) {
+ return (ctx->src_ptr - ctx->src);
+}
+
+static uint32_t getdstpos(struct xmi_ctx *ctx) {
+ return (ctx->dst_ptr - ctx->dst);
+}
+
+/* This is a default set of patches to convert from MT32 to GM
+ * The index is the MT32 Patch number and the value is the GM Patch
+ * This is only suitable for music that doesn't do timbre changes
+ * XMIDIs that contain Timbre changes will not convert properly.
+ */
+static const char mt32asgm[128] = {
+ 0, /* 0 Piano 1 */
+ 1, /* 1 Piano 2 */
+ 2, /* 2 Piano 3 (synth) */
+ 4, /* 3 EPiano 1 */
+ 4, /* 4 EPiano 2 */
+ 5, /* 5 EPiano 3 */
+ 5, /* 6 EPiano 4 */
+ 3, /* 7 Honkytonk */
+ 16, /* 8 Organ 1 */
+ 17, /* 9 Organ 2 */
+ 18, /* 10 Organ 3 */
+ 16, /* 11 Organ 4 */
+ 19, /* 12 Pipe Organ 1 */
+ 19, /* 13 Pipe Organ 2 */
+ 19, /* 14 Pipe Organ 3 */
+ 21, /* 15 Accordion */
+ 6, /* 16 Harpsichord 1 */
+ 6, /* 17 Harpsichord 2 */
+ 6, /* 18 Harpsichord 3 */
+ 7, /* 19 Clavinet 1 */
+ 7, /* 20 Clavinet 2 */
+ 7, /* 21 Clavinet 3 */
+ 8, /* 22 Celesta 1 */
+ 8, /* 23 Celesta 2 */
+ 62, /* 24 Synthbrass 1 (62) */
+ 63, /* 25 Synthbrass 2 (63) */
+ 62, /* 26 Synthbrass 3 Bank 8 */
+ 63, /* 27 Synthbrass 4 Bank 8 */
+ 38, /* 28 Synthbass 1 */
+ 39, /* 29 Synthbass 2 */
+ 38, /* 30 Synthbass 3 Bank 8 */
+ 39, /* 31 Synthbass 4 Bank 8 */
+ 88, /* 32 Fantasy */
+ 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */
+ 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)??? */
+ 92, /* 35 Glass */
+ 97, /* 36 Soundtrack */
+ 99, /* 37 Atmosphere */
+ 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is! */
+ 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */
+ 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */
+ 96, /* 41 IceRain */
+ 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */
+ 95, /* 43 EchoPans, no equiv, setting to SweepPad */
+ 81, /* 44 DoctorSolo Bank 8 */
+ 87, /* 45 SchoolDaze, no real equiv */
+ 112,/* 46 Bell Singer */
+ 80, /* 47 SquareWave */
+ 48, /* 48 Strings 1 */
+ 48, /* 49 Strings 2 - should be 49 */
+ 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50 */
+ 45, /* 51 Pizzicato Strings */
+ 40, /* 52 Violin 1 */
+ 40, /* 53 Violin 2 ? Viola */
+ 42, /* 54 Cello 1 */
+ 42, /* 55 Cello 2 */
+ 43, /* 56 Contrabass */
+ 46, /* 57 Harp 1 */
+ 46, /* 58 Harp 2 */
+ 24, /* 59 Guitar 1 (Nylon) */
+ 25, /* 60 Guitar 2 (Steel) */
+ 26, /* 61 Elec Guitar 1 */
+ 27, /* 62 Elec Guitar 2 */
+ 104,/* 63 Sitar */
+ 32, /* 64 Acou Bass 1 */
+ 32, /* 65 Acou Bass 2 */
+ 33, /* 66 Elec Bass 1 */
+ 34, /* 67 Elec Bass 2 */
+ 36, /* 68 Slap Bass 1 */
+ 37, /* 69 Slap Bass 2 */
+ 35, /* 70 Fretless Bass 1 */
+ 35, /* 71 Fretless Bass 2 */
+ 73, /* 72 Flute 1 */
+ 73, /* 73 Flute 2 */
+ 72, /* 74 Piccolo 1 */
+ 72, /* 75 Piccolo 2 */
+ 74, /* 76 Recorder */
+ 75, /* 77 Pan Pipes */
+ 64, /* 78 Sax 1 */
+ 65, /* 79 Sax 2 */
+ 66, /* 80 Sax 3 */
+ 67, /* 81 Sax 4 */
+ 71, /* 82 Clarinet 1 */
+ 71, /* 83 Clarinet 2 */
+ 68, /* 84 Oboe */
+ 69, /* 85 English Horn (Cor Anglais) */
+ 70, /* 86 Bassoon */
+ 22, /* 87 Harmonica */
+ 56, /* 88 Trumpet 1 */
+ 56, /* 89 Trumpet 2 */
+ 57, /* 90 Trombone 1 */
+ 57, /* 91 Trombone 2 */
+ 60, /* 92 French Horn 1 */
+ 60, /* 93 French Horn 2 */
+ 58, /* 94 Tuba */
+ 61, /* 95 Brass Section 1 */
+ 61, /* 96 Brass Section 2 */
+ 11, /* 97 Vibes 1 */
+ 11, /* 98 Vibes 2 */
+ 99, /* 99 Syn Mallet Bank 1 */
+ 112,/* 100 WindBell no real equiv Set to TinkleBell(112) */
+ 9, /* 101 Glockenspiel */
+ 14, /* 102 Tubular Bells */
+ 13, /* 103 Xylophone */
+ 12, /* 104 Marimba */
+ 107,/* 105 Koto */
+ 111,/* 106 Sho?? set to Shanai(111) */
+ 77, /* 107 Shakauhachi */
+ 78, /* 108 Whistle 1 */
+ 78, /* 109 Whistle 2 */
+ 76, /* 110 Bottle Blow */
+ 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */
+ 47, /* 112 Timpani */
+ 117,/* 113 Melodic Tom */
+ 116,/* 114 Deap Snare no equiv, set to Taiko(116) */
+ 118,/* 115 Electric Perc 1 */
+ 118,/* 116 Electric Perc 2 */
+ 116,/* 117 Taiko */
+ 115,/* 118 Taiko Rim, no real equiv, set to Woodblock(115) */
+ 119,/* 119 Cymbal, no real equiv, set to reverse cymbal(119) */
+ 115,/* 120 Castanets, no real equiv, in GM set to Woodblock(115) */
+ 112,/* 121 Triangle, no real equiv, set to TinkleBell(112) */
+ 55, /* 122 Orchestral Hit */
+ 124,/* 123 Telephone */
+ 123,/* 124 BirdTweet */
+ 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */
+ 98, /* 126 Water Bell set to Crystal Pad(98) */
+ 121 /* 127 Jungle Tune set to Breath Noise */
+};
+
+/* Same as above, except include patch changes
+ * so GS instruments can be used */
+static const char mt32asgs[256] = {
+ 0, 0, /* 0 Piano 1 */
+ 1, 0, /* 1 Piano 2 */
+ 2, 0, /* 2 Piano 3 (synth) */
+ 4, 0, /* 3 EPiano 1 */
+ 4, 0, /* 4 EPiano 2 */
+ 5, 0, /* 5 EPiano 3 */
+ 5, 0, /* 6 EPiano 4 */
+ 3, 0, /* 7 Honkytonk */
+ 16, 0, /* 8 Organ 1 */
+ 17, 0, /* 9 Organ 2 */
+ 18, 0, /* 10 Organ 3 */
+ 16, 0, /* 11 Organ 4 */
+ 19, 0, /* 12 Pipe Organ 1 */
+ 19, 0, /* 13 Pipe Organ 2 */
+ 19, 0, /* 14 Pipe Organ 3 */
+ 21, 0, /* 15 Accordion */
+ 6, 0, /* 16 Harpsichord 1 */
+ 6, 0, /* 17 Harpsichord 2 */
+ 6, 0, /* 18 Harpsichord 3 */
+ 7, 0, /* 19 Clavinet 1 */
+ 7, 0, /* 20 Clavinet 2 */
+ 7, 0, /* 21 Clavinet 3 */
+ 8, 0, /* 22 Celesta 1 */
+ 8, 0, /* 23 Celesta 2 */
+ 62, 0, /* 24 Synthbrass 1 (62) */
+ 63, 0, /* 25 Synthbrass 2 (63) */
+ 62, 0, /* 26 Synthbrass 3 Bank 8 */
+ 63, 0, /* 27 Synthbrass 4 Bank 8 */
+ 38, 0, /* 28 Synthbass 1 */
+ 39, 0, /* 29 Synthbass 2 */
+ 38, 0, /* 30 Synthbass 3 Bank 8 */
+ 39, 0, /* 31 Synthbass 4 Bank 8 */
+ 88, 0, /* 32 Fantasy */
+ 90, 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */
+ 52, 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)??? */
+ 92, 0, /* 35 Glass */
+ 97, 0, /* 36 Soundtrack */
+ 99, 0, /* 37 Atmosphere */
+ 14, 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is! */
+ 54, 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */
+ 98, 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */
+ 96, 0, /* 41 IceRain */
+ 68, 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */
+ 95, 0, /* 43 EchoPans, no equiv, setting to SweepPad */
+ 81, 0, /* 44 DoctorSolo Bank 8 */
+ 87, 0, /* 45 SchoolDaze, no real equiv */
+ 112, 0, /* 46 Bell Singer */
+ 80, 0, /* 47 SquareWave */
+ 48, 0, /* 48 Strings 1 */
+ 48, 0, /* 49 Strings 2 - should be 49 */
+ 44, 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50 */
+ 45, 0, /* 51 Pizzicato Strings */
+ 40, 0, /* 52 Violin 1 */
+ 40, 0, /* 53 Violin 2 ? Viola */
+ 42, 0, /* 54 Cello 1 */
+ 42, 0, /* 55 Cello 2 */
+ 43, 0, /* 56 Contrabass */
+ 46, 0, /* 57 Harp 1 */
+ 46, 0, /* 58 Harp 2 */
+ 24, 0, /* 59 Guitar 1 (Nylon) */
+ 25, 0, /* 60 Guitar 2 (Steel) */
+ 26, 0, /* 61 Elec Guitar 1 */
+ 27, 0, /* 62 Elec Guitar 2 */
+ 104, 0, /* 63 Sitar */
+ 32, 0, /* 64 Acou Bass 1 */
+ 32, 0, /* 65 Acou Bass 2 */
+ 33, 0, /* 66 Elec Bass 1 */
+ 34, 0, /* 67 Elec Bass 2 */
+ 36, 0, /* 68 Slap Bass 1 */
+ 37, 0, /* 69 Slap Bass 2 */
+ 35, 0, /* 70 Fretless Bass 1 */
+ 35, 0, /* 71 Fretless Bass 2 */
+ 73, 0, /* 72 Flute 1 */
+ 73, 0, /* 73 Flute 2 */
+ 72, 0, /* 74 Piccolo 1 */
+ 72, 0, /* 75 Piccolo 2 */
+ 74, 0, /* 76 Recorder */
+ 75, 0, /* 77 Pan Pipes */
+ 64, 0, /* 78 Sax 1 */
+ 65, 0, /* 79 Sax 2 */
+ 66, 0, /* 80 Sax 3 */
+ 67, 0, /* 81 Sax 4 */
+ 71, 0, /* 82 Clarinet 1 */
+ 71, 0, /* 83 Clarinet 2 */
+ 68, 0, /* 84 Oboe */
+ 69, 0, /* 85 English Horn (Cor Anglais) */
+ 70, 0, /* 86 Bassoon */
+ 22, 0, /* 87 Harmonica */
+ 56, 0, /* 88 Trumpet 1 */
+ 56, 0, /* 89 Trumpet 2 */
+ 57, 0, /* 90 Trombone 1 */
+ 57, 0, /* 91 Trombone 2 */
+ 60, 0, /* 92 French Horn 1 */
+ 60, 0, /* 93 French Horn 2 */
+ 58, 0, /* 94 Tuba */
+ 61, 0, /* 95 Brass Section 1 */
+ 61, 0, /* 96 Brass Section 2 */
+ 11, 0, /* 97 Vibes 1 */
+ 11, 0, /* 98 Vibes 2 */
+ 99, 0, /* 99 Syn Mallet Bank 1 */
+ 112, 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */
+ 9, 0, /* 101 Glockenspiel */
+ 14, 0, /* 102 Tubular Bells */
+ 13, 0, /* 103 Xylophone */
+ 12, 0, /* 104 Marimba */
+ 107, 0, /* 105 Koto */
+ 111, 0, /* 106 Sho?? set to Shanai(111) */
+ 77, 0, /* 107 Shakauhachi */
+ 78, 0, /* 108 Whistle 1 */
+ 78, 0, /* 109 Whistle 2 */
+ 76, 0, /* 110 Bottle Blow */
+ 76, 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */
+ 47, 0, /* 112 Timpani */
+ 117, 0, /* 113 Melodic Tom */
+ 116, 0, /* 114 Deap Snare no equiv, set to Taiko(116) */
+ 118, 0, /* 115 Electric Perc 1 */
+ 118, 0, /* 116 Electric Perc 2 */
+ 116, 0, /* 117 Taiko */
+ 115, 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */
+ 119, 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */
+ 115, 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */
+ 112, 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */
+ 55, 0, /* 122 Orchestral Hit */
+ 124, 0, /* 123 Telephone */
+ 123, 0, /* 124 BirdTweet */
+ 94, 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */
+ 98, 0, /* 126 Water Bell set to Crystal Pad(98) */
+ 121, 0 /* 127 Jungle Tune set to Breath Noise */
+};
+
+int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize,
+ uint8_t **out, uint32_t *outsize,
+ uint32_t convert_type) {
+ struct xmi_ctx ctx;
+ unsigned int i;
+ int ret = -1;
+
+ if (convert_type > XMIDI_CONVERT_MT32_TO_GS) {
+ //_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);
+ return (ret);
+ }
+
+ memset(&ctx, 0, sizeof(struct xmi_ctx));
+ ctx.src = ctx.src_ptr = in;
+ ctx.srcsize = insize;
+ ctx.convert_type = convert_type;
+
+ if (ParseXMI(&ctx) < 0) {
+ //_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);
+ goto _end;
+ }
+
+ if (ExtractTracks(&ctx) < 0) {
+ //_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);
+ goto _end;
+ }
+
+ ctx.dst = malloc(DST_CHUNK);
+ ctx.dst_ptr = ctx.dst;
+ ctx.dstsize = DST_CHUNK;
+ ctx.dstrem = DST_CHUNK;
+
+ /* 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);
+
+ write2(&ctx, ctx.info.type);
+ write2(&ctx, ctx.info.tracks);
+ write2(&ctx, ctx.timing[0]);/* write divisions from track0 */
+
+ for (i = 0; i < ctx.info.tracks; i++)
+ ConvertListToMTrk(&ctx, ctx.events[i]);
+ *out = ctx.dst;
+ *outsize = ctx.dstsize - ctx.dstrem;
+ ret = 0;
+
+_end: /* cleanup */
+ if (ret < 0) {
+ free(ctx.dst);
+ *out = NULL;
+ *outsize = 0;
+ }
+ if (ctx.events) {
+ for (i = 0; i < ctx.info.tracks; i++)
+ DeleteEventList(ctx.events[i]);
+ free(ctx.events);
+ }
+ free(ctx.timing);
+
+ return (ret);
+}
+
+static void DeleteEventList(midi_event *mlist) {
+ midi_event *event;
+ midi_event *next;
+
+ next = mlist;
+
+ while ((event = next) != NULL) {
+ next = event->next;
+ free(event->buffer);
+ free(event);
+ }
+}
+
+/* Sets current to the new event and updates list */
+static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time) {
+ if (!ctx->list) {
+ ctx->list = ctx->current = calloc(1, sizeof(midi_event));
+ ctx->current->time = (time < 0)? 0 : time;
+ return;
+ }
+
+ if (time < 0) {
+ midi_event *event = calloc(1, sizeof(midi_event));
+ event->next = ctx->list;
+ ctx->list = ctx->current = event;
+ return;
+ }
+
+ if (ctx->current->time > time)
+ ctx->current = ctx->list;
+
+ while (ctx->current->next) {
+ if (ctx->current->next->time > time) {
+ midi_event *event = calloc(1, sizeof(midi_event));
+ event->next = ctx->current->next;
+ ctx->current->next = event;
+ ctx->current = event;
+ ctx->current->time = time;
+ return;
+ }
+
+ ctx->current = ctx->current->next;
+ }
+
+ ctx->current->next = calloc(1, sizeof(midi_event));
+ ctx->current = ctx->current->next;
+ ctx->current->time = time;
+}
+
+/* Conventional Variable Length Quantity */
+static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant) {
+ int i;
+ uint32_t data;
+
+ *quant = 0;
+ for (i = 0; i < 4; i++) {
+ data = read1(ctx);
+ *quant <<= 7;
+ *quant |= data & 0x7F;
+
+ if (!(data & 0x80)) {
+ i++;
+ break;
+ }
+ }
+ return (i);
+}
+
+/* XMIDI Delta Variable Length Quantity */
+static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant) {
+ int i;
+ int32_t data;
+
+ *quant = 0;
+ for (i = 0; i < 4; i++) {
+ data = read1(ctx);
+ if (data & 0x80) {
+ skipsrc(ctx, -1);
+ break;
+ }
+ *quant += data;
+ }
+ return (i);
+}
+
+static int PutVLQ(struct xmi_ctx *ctx, uint32_t value) {
+ int32_t buffer;
+ int i = 1, j;
+ buffer = value & 0x7F;
+ while (value >>= 7) {
+ buffer <<= 8;
+ buffer |= ((value & 0x7F) | 0x80);
+ i++;
+ }
+ for (j = 0; j < i; j++) {
+ write1(ctx, buffer & 0xFF);
+ buffer >>= 8;
+ }
+
+ return (i);
+}
+
+/* Converts Events
+ *
+ * Source is at the first data byte
+ * size 1 is single data byte
+ * size 2 is dual data byte
+ * size 3 is XMI Note on
+ * Returns bytes converted */
+static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time,
+ const uint8_t status, const int size) {
+ uint32_t delta = 0;
+ int32_t data;
+ midi_event *prev;
+ int i;
+
+ data = read1(ctx);
+
+ /* Bank changes are handled here */
+ if ((status >> 4) == 0xB && data == 0) {
+ data = read1(ctx);
+
+ ctx->bank127[status & 0xF] = 0;
+
+ if ( ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM ||
+ ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS ||
+ ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127 ||
+ (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM
+ && (status & 0xF) == 9) )
+ return (2);
+
+ CreateNewEvent(ctx, time);
+ ctx->current->status = status;
+ ctx->current->data[0] = 0;
+ ctx->current->data[1] = data;
+
+ if (ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && data == 127)
+ ctx->bank127[status & 0xF] = 1;
+
+ return (2);
+ }
+
+ /* Handling for patch change mt32 conversion, probably should go elsewhere */
+ if ((status >> 4) == 0xC && (status&0xF) != 9
+ && ctx->convert_type != XMIDI_CONVERT_NOCONVERSION)
+ {
+ if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM)
+ {
+ data = mt32asgm[data];
+ }
+ else if ((ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && ctx->bank127[status&0xF]) ||
+ ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS ||
+ ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM)
+ {
+ CreateNewEvent (ctx, time);
+ ctx->current->status = 0xB0 | (status&0xF);
+ ctx->current->data[0] = 0;
+ ctx->current->data[1] = mt32asgs[data*2+1];
+
+ data = mt32asgs[data*2];
+ }
+ else if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127)
+ {
+ CreateNewEvent (ctx, time);
+ ctx->current->status = 0xB0 | (status&0xF);
+ ctx->current->data[0] = 0;
+ ctx->current->data[1] = 127;
+ }
+ }
+ /* Drum track handling */
+ else if ((status >> 4) == 0xC && (status&0xF) == 9 &&
+ (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127))
+ {
+ CreateNewEvent (ctx, time);
+ ctx->current->status = 0xB9;
+ ctx->current->data[0] = 0;
+ ctx->current->data[1] = 127;
+ }
+
+ CreateNewEvent(ctx, time);
+ ctx->current->status = status;
+
+ ctx->current->data[0] = data;
+
+ if (size == 1)
+ return (1);
+
+ ctx->current->data[1] = read1(ctx);
+
+ if (size == 2)
+ return (2);
+
+ /* XMI Note On handling */
+ prev = ctx->current;
+ i = GetVLQ(ctx, &delta);
+ CreateNewEvent(ctx, time + delta * 3);
+
+ ctx->current->status = status;
+ ctx->current->data[0] = data;
+ ctx->current->data[1] = 0;
+ ctx->current = prev;
+
+ return (i + 2);
+}
+
+/* Simple routine to convert system messages */
+static int32_t ConvertSystemMessage(struct xmi_ctx *ctx, const int32_t time,
+ const uint8_t status) {
+ int32_t i = 0;
+
+ CreateNewEvent(ctx, time);
+ ctx->current->status = status;
+
+ /* Handling of Meta events */
+ if (status == 0xFF) {
+ ctx->current->data[0] = read1(ctx);
+ i++;
+ }
+
+ i += GetVLQ(ctx, &ctx->current->len);
+
+ if (!ctx->current->len)
+ return (i);
+
+ ctx->current->buffer = malloc(sizeof(uint8_t)*ctx->current->len);
+ copy(ctx, (char *) ctx->current->buffer, ctx->current->len);
+
+ return (i + ctx->current->len);
+}
+
+/* XMIDI and Midi to List
+ * Returns XMIDI PPQN */
+static int32_t ConvertFiletoList(struct xmi_ctx *ctx) {
+ int32_t time = 0;
+ uint32_t data;
+ int32_t end = 0;
+ int32_t tempo = 500000;
+ int32_t tempo_set = 0;
+ uint32_t status = 0;
+ uint32_t file_size = getsrcsize(ctx);
+
+ /* Set Drum track to correct setting if required */
+ if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) {
+ CreateNewEvent(ctx, 0);
+ ctx->current->status = 0xB9;
+ ctx->current->data[0] = 0;
+ ctx->current->data[1] = 127;
+ }
+
+ while (!end && getsrcpos(ctx) < file_size) {
+ GetVLQ2(ctx, &data);
+ time += data * 3;
+
+ status = read1(ctx);
+
+ switch (status >> 4) {
+ case MIDI_STATUS_NOTE_ON:
+ ConvertEvent(ctx, time, status, 3);
+ break;
+
+ /* 2 byte data */
+ case MIDI_STATUS_NOTE_OFF:
+ case MIDI_STATUS_AFTERTOUCH:
+ case MIDI_STATUS_CONTROLLER:
+ case MIDI_STATUS_PITCH_WHEEL:
+ ConvertEvent(ctx, time, status, 2);
+ break;
+
+ /* 1 byte data */
+ case MIDI_STATUS_PROG_CHANGE:
+ case MIDI_STATUS_PRESSURE:
+ ConvertEvent(ctx, time, status, 1);
+ break;
+
+ case MIDI_STATUS_SYSEX:
+ if (status == 0xFF) {
+ int32_t pos = getsrcpos(ctx);
+ uint32_t dat = read1(ctx);
+
+ if (dat == 0x2F) /* End */
+ end = 1;
+ else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */
+ {
+ skipsrc(ctx, 1);
+ tempo = read1(ctx) << 16;
+ tempo += read1(ctx) << 8;
+ tempo += read1(ctx);
+ tempo *= 3;
+ tempo_set = 1;
+ } else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */
+ {
+ GetVLQ(ctx, &dat);
+ skipsrc(ctx, dat);
+ break;
+ }
+
+ seeksrc(ctx, pos);
+ }
+ ConvertSystemMessage(ctx, time, status);
+ break;
+
+ default:
+ break;
+ }
+ }
+ return ((tempo * 3) / 25000);
+}
+
+/* Converts and event list to a MTrk
+ * Returns bytes of the array
+ * buf can be NULL */
+static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) {
+ int32_t time = 0;
+ midi_event *event;
+ uint32_t delta;
+ uint8_t last_status = 0;
+ uint32_t i = 8;
+ uint32_t j;
+ uint32_t size_pos, cur_pos;
+ int end = 0;
+
+ write1(ctx, 'M');
+ write1(ctx, 'T');
+ write1(ctx, 'r');
+ write1(ctx, 'k');
+
+ size_pos = getdstpos(ctx);
+ skipdst(ctx, 4);
+
+ for (event = mlist; event && !end; event = event->next) {
+ delta = (event->time - time);
+ time = event->time;
+
+ i += PutVLQ(ctx, delta);
+
+ if ((event->status != last_status) || (event->status >= 0xF0)) {
+ write1(ctx, event->status);
+ i++;
+ }
+
+ last_status = event->status;
+
+ switch (event->status >> 4) {
+ /* 2 bytes data
+ * Note off, Note on, Aftertouch, Controller and Pitch Wheel */
+ case 0x8:
+ case 0x9:
+ case 0xA:
+ case 0xB:
+ case 0xE:
+ write1(ctx, event->data[0]);
+ write1(ctx, event->data[1]);
+ i += 2;
+ break;
+
+ /* 1 bytes data
+ * Program Change and Channel Pressure */
+ case 0xC:
+ case 0xD:
+ write1(ctx, event->data[0]);
+ i++;
+ break;
+
+ /* Variable length
+ * SysEx */
+ case 0xF:
+ if (event->status == 0xFF) {
+ if (event->data[0] == 0x2f)
+ end = 1;
+ write1(ctx, event->data[0]);
+ i++;
+ }
+ i += PutVLQ(ctx, event->len);
+ if (event->len) {
+ for (j = 0; j < event->len; j++) {
+ write1(ctx, event->buffer[j]);
+ i++;
+ }
+ }
+ break;
+
+ /* Never occur */
+ default:
+ //_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);
+ break;
+ }
+ }
+
+ cur_pos = getdstpos(ctx);
+ seekdst(ctx, size_pos);
+ write4(ctx, i - 8);
+ seekdst(ctx, cur_pos);
+
+ return (i);
+}
+
+/* Assumes correct xmidi */
+static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx) {
+ uint32_t num = 0;
+ signed short ppqn;
+ uint32_t len = 0;
+ int32_t begin;
+ char buf[32];
+
+ while (getsrcpos(ctx) < getsrcsize(ctx) && num != ctx->info.tracks) {
+ /* Read first 4 bytes of name */
+ copy(ctx, buf, 4);
+ len = read4(ctx);
+
+ /* Skip the FORM entries */
+ if (!memcmp(buf, "FORM", 4)) {
+ skipsrc(ctx, 4);
+ copy(ctx, buf, 4);
+ len = read4(ctx);
+ }
+
+ if (memcmp(buf, "EVNT", 4)) {
+ skipsrc(ctx, (len + 1) & ~1);
+ continue;
+ }
+
+ ctx->list = NULL;
+ begin = getsrcpos(ctx);
+
+ /* Convert it */
+ if (!(ppqn = ConvertFiletoList(ctx))) {
+ //_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, NULL, 0);
+ break;
+ }
+ ctx->timing[num] = ppqn;
+ ctx->events[num] = ctx->list;
+
+ /* Increment Counter */
+ num++;
+
+ /* go to start of next track */
+ seeksrc(ctx, begin + ((len + 1) & ~1));
+ }
+
+ /* Return how many were converted */
+ return (num);
+}
+
+static int ParseXMI(struct xmi_ctx *ctx) {
+ uint32_t i;
+ uint32_t start;
+ uint32_t len;
+ uint32_t chunk_len;
+ uint32_t file_size;
+ char buf[32];
+
+ file_size = getsrcsize(ctx);
+ if (getsrcpos(ctx) + 8 > file_size) {
+badfile: //_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);
+ return (-1);
+ }
+
+ /* Read first 4 bytes of header */
+ copy(ctx, buf, 4);
+
+ /* Could be XMIDI */
+ if (!memcmp(buf, "FORM", 4)) {
+ /* Read length of */
+ len = read4(ctx);
+
+ start = getsrcpos(ctx);
+ if (start + 4 > file_size)
+ goto badfile;
+
+ /* Read 4 bytes of type */
+ copy(ctx, buf, 4);
+
+ /* XDIRless XMIDI, we can handle them here. */
+ if (!memcmp(buf, "XMID", 4)) {
+ //_WM_DEBUG_MSG("Warning: XMIDI without XDIR");
+ ctx->info.tracks = 1;
+ }
+ /* Not an XMIDI that we recognise */
+ else if (memcmp(buf, "XDIR", 4)) {
+ goto badfile;
+ }
+ else { /* Seems Valid */
+ ctx->info.tracks = 0;
+
+ for (i = 4; i < len; i++) {
+ /* check too short files */
+ if (getsrcpos(ctx) + 10 > file_size)
+ break;
+
+ /* Read 4 bytes of type */
+ copy(ctx, buf, 4);
+
+ /* Read length of chunk */
+ chunk_len = read4(ctx);
+
+ /* Add eight bytes */
+ i += 8;
+
+ if (memcmp(buf, "INFO", 4)) {
+ /* Must align */
+ skipsrc(ctx, (chunk_len + 1) & ~1);
+ i += (chunk_len + 1) & ~1;
+ continue;
+ }
+
+ /* Must be at least 2 bytes long */
+ if (chunk_len < 2)
+ break;
+
+ ctx->info.tracks = read2(ctx);
+ break;
+ }
+
+ /* Didn't get to fill the header */
+ if (ctx->info.tracks == 0) {
+ goto badfile;
+ }
+
+ /* Ok now to start part 2
+ * Goto the right place */
+ seeksrc(ctx, start + ((len + 1) & ~1));
+ if (getsrcpos(ctx) + 12 > file_size)
+ goto badfile;
+
+ /* Read 4 bytes of type */
+ copy(ctx, buf, 4);
+
+ if (memcmp(buf, "CAT ", 4)) {
+ //_WM_ERROR_NEW("XMI error: expected \"CAT \", found \"%c%c%c%c\".",
+ // buf[0], buf[1], buf[2], buf[3]);
+ return (-1);
+ }
+
+ /* Now read length of this track */
+ read4(ctx);
+
+ /* Read 4 bytes of type */
+ copy(ctx, buf, 4);
+
+ if (memcmp(buf, "XMID", 4)) {
+ //_WM_ERROR_NEW("XMI error: expected \"XMID\", found \"%c%c%c%c\".",
+ // buf[0], buf[1], buf[2], buf[3]);
+ return (-1);
+ }
+
+ /* Valid XMID */
+ ctx->datastart = getsrcpos(ctx);
+ return (0);
+ }
+ }
+
+ return (-1);
+}
+
+static int ExtractTracks(struct xmi_ctx *ctx) {
+ uint32_t i;
+
+ ctx->events = calloc(ctx->info.tracks, sizeof(midi_event*));
+ ctx->timing = calloc(ctx->info.tracks, sizeof(int16_t));
+ /* type-2 for multi-tracks, type-0 otherwise */
+ ctx->info.type = (ctx->info.tracks > 1)? 2 : 0;
+
+ seeksrc(ctx, ctx->datastart);
+ i = ExtractTracksFromXmi(ctx);
+
+ if (i != ctx->info.tracks) {
+ //_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from XMIDI",
+ // ctx->info.tracks, i);
+ return (-1);
+ }
+
+ return (0);
+}
+
diff --git a/src/adlmidi_xmi2mid.h b/src/adlmidi_xmi2mid.h
new file mode 100644
index 0000000..523e8d7
--- /dev/null
+++ b/src/adlmidi_xmi2mid.h
@@ -0,0 +1,51 @@
+/*
+ * XMIDI: Miles XMIDI to MID Library Header
+ *
+ * Copyright (C) 2001 Ryan Nunn
+ * Copyright (C) 2014-2016 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.
+ */
+
+/* XMIDI Converter */
+
+#ifndef XMIDILIB_H
+#define XMIDILIB_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Conversion types for Midi files */
+#define XMIDI_CONVERT_NOCONVERSION 0x00
+#define XMIDI_CONVERT_MT32_TO_GM 0x01
+#define XMIDI_CONVERT_MT32_TO_GS 0x02
+#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */
+#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */
+#define XMIDI_CONVERT_GS127_TO_GS 0x05
+
+int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize,
+ uint8_t **out, uint32_t *outsize,
+ uint32_t convert_type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* XMIDILIB_H */