diff options
author | Wohlstand <admin@wohlnet.ru> | 2017-02-15 16:19:08 +0300 |
---|---|---|
committer | Wohlstand <admin@wohlnet.ru> | 2017-02-15 16:19:08 +0300 |
commit | 9cd892706bf782c8a0e47731bd71e629d505c5cf (patch) | |
tree | 5d83247581e0b9a513be0ea38135afa3c94b440f /src | |
parent | ba0b0c4d5c1e8c8926c6838dddb0f6abdbccd9f2 (diff) | |
download | libADLMIDI-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.cpp | 3 | ||||
-rw-r--r-- | src/adlmidi_load.cpp | 136 | ||||
-rw-r--r-- | src/adlmidi_mus2mid.c | 451 | ||||
-rw-r--r-- | src/adlmidi_mus2mid.h | 40 | ||||
-rw-r--r-- | src/adlmidi_opl3.cpp | 1 | ||||
-rw-r--r-- | src/adlmidi_xmi2mid.c | 1101 | ||||
-rw-r--r-- | src/adlmidi_xmi2mid.h | 51 |
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 */ |