Add LucasArts VIMA + .IMX/IMS [Curse of Monkey Island, Grim Fandango]

This commit is contained in:
bnnm 2020-05-01 09:15:01 +02:00
parent 1ff6bcebed
commit 12c0908667
10 changed files with 881 additions and 4 deletions

View File

@ -205,6 +205,14 @@ void seek_ubi_adpcm(ubi_adpcm_codec_data *data, int32_t num_sample);
void free_ubi_adpcm(ubi_adpcm_codec_data *data);
int ubi_adpcm_get_samples(ubi_adpcm_codec_data *data);
/* imuse_decoder */
typedef struct imuse_codec_data imuse_codec_data;
imuse_codec_data *init_imuse(STREAMFILE* sf, int channels);
void decode_imuse(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do);
void reset_imuse(imuse_codec_data* data);
void seek_imuse(imuse_codec_data* data, int32_t num_sample);
void free_imuse(imuse_codec_data* data);
/* ea_mt_decoder*/
ea_mt_codec_data *init_ea_mt(int channels, int type);
ea_mt_codec_data *init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets);

662
src/coding/imuse_decoder.c Normal file
View File

@ -0,0 +1,662 @@
#include "coding.h"
/* LucasArts' iMUSE decoder, mainly for VIMA (like IMA but with variable frame and code sizes).
* Reverse engineered from various .exe
*
* Info:
* - https://github.com/scummvm/scummvm/blob/master/engines/scumm/imuse_digi/dimuse_codecs.cpp (V1)
* - https://wiki.multimedia.cx/index.php/VIMA (V2)
* - https://github.com/residualvm/residualvm/tree/master/engines/grim/imuse
* https://github.com/residualvm/residualvm/tree/master/engines/grim/movie/codecs (V2)
*/
static const int16_t step_table[] = { /* same as IMA */
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31,
34, 37, 41, 45, 50, 55, 60, 66,
73, 80, 88, 97, 107, 118, 130, 143,
157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411,
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442,11487,12635,13899,
15289,16818,18500,20350,22385,24623,27086,29794,
32767,
};
/* pre-calculated in V1:
for (i = 0; i < 89; i++) {
int counter = (4 * step_table[i] / 7) >> 1;
int size = 1;
while (counter > 0) {
size++;
counter >>= 1;
}
code_size_table[i] = clamp(size, 3, 8) - 1
}
*/
static const uint8_t code_size_table_v1[89] = {
2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6,
6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
};
static const uint8_t code_size_table_v2[89] = {
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
};
static const int8_t index_table2b[4] = {
-1, 4,
-1, 4,
};
static const int8_t index_table3b_v1[8] = {
-1,-1, 2, 8,
-1,-1, 2, 8,
};
static const int8_t index_table3b_v2[8] = {
-1,-1, 2, 6,
-1,-1, 2, 6,
};
static const int8_t index_table4b[16] = {
-1,-1,-1,-1, 1, 2, 4, 6,
-1,-1,-1,-1, 1, 2, 4, 6,
};
static const int8_t index_table5b_v1[32] = {
-1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,12,16,32,
-1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,12,16,32,
};
static const int8_t index_table5b_v2[32] = {
-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 2, 2, 4, 5, 6,
-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 2, 2, 4, 5, 6,
};
static const int8_t index_table6b_v1[64] = {
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,10,12,14, 16,18,20,22,24,26,28,32,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 2, 4, 6, 8,10,12,14, 16,18,20,22,24,26,28,32,
};
static const int8_t index_table6b_v2[64] = {
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 5, 5, 6, 6,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 5, 5, 6, 6,
};
static const int8_t index_table7b_v1[128] = {
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24, 25,26,27,28,29,30,31,32,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24, 25,26,27,28,29,30,31,32,
};
static const int8_t index_table7b_v2[128] = {
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6,
};
static const int8_t* index_tables_v1[8] = {
NULL,
NULL,
index_table2b,
index_table3b_v1,
index_table4b,
index_table5b_v1,
index_table6b_v1,
index_table7b_v1,
};
/* seems V2 doesn't actually use <4b, nor mirrored parts, even though they are defined */
static const int8_t* index_tables_v2[8] = {
NULL,
NULL,
index_table2b,
index_table3b_v2,
index_table4b,
index_table5b_v2,
index_table6b_v2,
index_table7b_v2,
};
#define MAX_CHANNELS 2
#define MAX_BLOCK_SIZE 0x2000
#define MAX_BLOCK_COUNT 0x10000 /* arbitrary max */
/* ************************** */
typedef struct {
/*const*/ int16_t* samples;
int filled;
int channels;
//todo may be more useful with filled/full? use 2 mark methods?
} sbuf_t;
/* copy, move and mark consumed samples */
static void sbuf_consume(sample_t** p_outbuf, int32_t* p_samples_to_do, sbuf_t* sbuf) {
int samples_consume;
samples_consume = *p_samples_to_do;
if (samples_consume > sbuf->filled)
samples_consume = sbuf->filled;
/* memcpy is safe when filled/samples_copy is 0 (but must pass non-NULL bufs) */
memcpy(*p_outbuf, sbuf->samples, samples_consume * sbuf->channels * sizeof(int16_t));
sbuf->samples += samples_consume * sbuf->channels;
sbuf->filled -= samples_consume;
*p_outbuf += samples_consume * sbuf->channels;
*p_samples_to_do -= samples_consume;
}
static int clamp_s32(int val, int min, int max) {
if (val > max)
return max;
else if (val < min)
return min;
return val;
}
/* ************************** */
typedef enum { COMP, MCMP } imuse_type_t;
struct imuse_codec_data {
/* config */
imuse_type_t type;
int channels;
size_t block_count;
struct block_entry_t {
uint32_t offset; /* from file start */
uint32_t size;
uint32_t flags;
uint32_t data;
} *block_table;
uint16_t adpcm_table[64 * 89];
/* state */
sbuf_t sbuf;
int current_block;
int16_t samples[MAX_BLOCK_SIZE / sizeof(int16_t) * MAX_CHANNELS];
};
imuse_codec_data* init_imuse(STREAMFILE* sf, int channels) {
int i, j;
off_t offset, data_offset;
imuse_codec_data* data = NULL;
if (channels > MAX_CHANNELS)
goto fail;
data = calloc(1, sizeof(struct imuse_codec_data));
if (!data) goto fail;
data->channels = channels;
/* read index table */
if (read_u32be(0x00,sf) == 0x434F4D50) { /* "COMP" */
data->block_count = read_u32be(0x04,sf);
if (data->block_count > MAX_BLOCK_COUNT) goto fail;
/* 08: base codec? */
/* 0c: some size? */
data->block_table = calloc(data->block_count, sizeof(struct block_entry_t));
if (!data->block_table) goto fail;
offset = 0x10;
for (i = 0; i < data->block_count; i++) {
struct block_entry_t* entry = &data->block_table[i];
entry->offset = read_u32be(offset + 0x00, sf);
entry->size = read_u32be(offset + 0x04, sf);
entry->flags = read_u32be(offset + 0x08, sf);
/* 0x0c: null */
entry->data = MAX_BLOCK_SIZE;
/* blocks decode into fixed size, that may include header */
if (entry->size > MAX_BLOCK_SIZE) {
VGM_LOG("IMUSE: block size too big\n");
goto fail;
}
if (entry->flags != 0x0D && entry->flags != 0x0F) { /* VIMA */
VGM_LOG("IMUSE: unknown codec\n");
goto fail; /* others are bunch of mini-codecs (ex. The Dig) */
}
offset += 0x10;
}
/* detect type */
{
uint32_t id = read_u32be(data->block_table[0].offset + 0x02, sf);
if (id == 0x694D5553) { /* "iMUS" header [The Curse of Monkey Island (PC)] */
data->type = COMP;
} else {
goto fail; /* no header [The Dig (PC)] */
}
}
}
else if (read_u32be(0x00,sf) == 0x4D434D50) { /* "MCMP" */
data->block_count = read_u16be(0x04,sf);
if (data->block_count > MAX_BLOCK_COUNT) goto fail;
data->block_table = calloc(data->block_count, sizeof(struct block_entry_t));
if (!data->block_table) goto fail;
/* pre-calculate for simpler logic */
data_offset = 0x06 + data->block_count * 0x09;
data_offset += 0x02 + read_u16be(data_offset + 0x00, sf); /* mini text header */
offset = 0x06;
for (i = 0; i < data->block_count; i++) {
struct block_entry_t* entry = &data->block_table[i];
entry->flags = read_u8 (offset + 0x00, sf);
entry->data = read_u32be(offset + 0x01, sf);
entry->size = read_u32be(offset + 0x05, sf);
entry->offset = data_offset;
/* blocks of data and audio are separate */
if (entry->data > MAX_BLOCK_SIZE || entry->size > MAX_BLOCK_SIZE) {
VGM_LOG("IMUSE: block size too big\n");
goto fail;
}
if (entry->flags != 0x00 && entry->flags != 0x01) { /* data or VIMA */
VGM_LOG("IMUSE: unknown codec\n");
goto fail;
}
offset += 0x09;
data_offset += entry->size;
}
data->type = MCMP; /* with header [Grim Fandango (multi)] */
/* there are iMUS or RIFF headers but affect parser */
}
else {
goto fail;
}
/* iMUSE pre-calculates main decode ops as a table, looks similar to standard IMA expand */
for (i = 0; i < 64; i++) {
for (j = 0; j < 89; j++) {
int counter = 32;
int value = 0;
int step = step_table[j];
while (counter > 0) {
if (counter & i)
value += step;
step >>= 1;
counter >>= 1;
}
data->adpcm_table[i + j * 64] = value; /* non sequential: all 64 [0]s, [1]s ... [88]s */
}
}
reset_imuse(data);
return data;
fail:
free_imuse(data);
return NULL;
}
/* **************************************** */
static void decode_vima1(STREAMFILE* sf, sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block_num, uint16_t* adpcm_table) {
int ch, i, j, s;
int bitpos;
int adpcm_history[MAX_CHANNELS] = {0};
int adpcm_step_index[MAX_CHANNELS] = {0};
int chs = sbuf->channels;
/* read header (outside decode in original code) */
{
int pos = 0;
size_t copy_size;
/* decodes BLOCK_SIZE bytes (not samples), including copy_size if exists, but not first 16b
* or ADPCM headers. ADPCM setup must be set to 0 if headers weren't read. */
copy_size = get_u16be(buf + pos);
pos += 0x02;
if (block_num == 0 && copy_size > 0) {
/* iMUS header (always in first block) */
pos += copy_size;
data_left -= copy_size;
}
else if (copy_size > 0) {
/* presumably PCM data (not seen) */
for (i = 0, j = pos; i < copy_size / sizeof(sample_t); i++, j += 2) {
sbuf->samples[i] = get_s16le(buf + j);
}
sbuf->filled += copy_size / chs / sizeof(sample_t);
pos += copy_size;
data_left -= copy_size;
VGM_LOG("IMUS: found PCM block %i\n", block_num);
}
else {
/* ADPCM header (never in first block) */
for (i = 0; i < chs; i++) {
adpcm_step_index[i] = get_u8 (buf + pos + 0x00);
//adpcm_step[i] = get_s32be(buf + pos + 0x01); /* same as step_table[step_index] */
adpcm_history[i] = get_s32be(buf + pos + 0x05);
pos += 0x09;
adpcm_step_index[i] = clamp_s32(adpcm_step_index[i], 0, 88); /* not originally */
}
}
bitpos = pos * 8;
}
/* decode ADPCM data after header
* (stereo layout: all samples from L, then all for R) */
for (ch = 0; ch < chs; ch++) {
int sample, step_index;
int samples_to_do;
int samples_left = data_left / sizeof(int16_t);
int first_sample = sbuf->filled * chs + ch;
if (chs == 1) {
samples_to_do = samples_left;
} else {
/* L has +1 code for aligment in first block, must be read to reach R (code seems empty).
* Not sure if COMI uses decoded bytes or decoded samples (returns samples_left / channels)
* though but the latter makes more sense. */
if (ch == 0)
samples_to_do = (samples_left + 1) / chs;
else
samples_to_do = (samples_left + 0) / chs;
}
//;VGM_ASSERT((samples_left + 1) / 2 != (samples_left + 0) / 2, "IMUSE: sample diffs\n");
step_index = adpcm_step_index[ch];
sample = adpcm_history[ch];
for (i = 0, s = first_sample; i < samples_to_do; i++, s += chs) {
int code_size, code, sign_mask, data_mask, delta;
if (bitpos >= MAX_BLOCK_SIZE * 8) {
VGM_LOG("IMUSE: wrong bit offset\n");
break;
}
code_size = code_size_table_v1[step_index];
/* get bit thing from COMI (reads closest 16b then masks + shifts as needed), BE layout */
code = get_u16be(buf + (bitpos >> 3)); //ok
code = (code << (bitpos & 7)) & 0xFFFF;
code = code >> (16 - code_size);
bitpos += code_size;
sign_mask = (1 << (code_size - 1));
data_mask = sign_mask - 1; /* done with a LUT in COMI */
delta = adpcm_table[(step_index * 64) + (((code & data_mask) << (7 - code_size)))];
delta += step_table[step_index] >> (code_size - 1);
if (code & sign_mask)
delta = -delta;
sample += delta;
sample = clamp16(sample);
sbuf->samples[s] = sample;
step_index += index_tables_v1[code_size][code];
step_index = clamp_s32(step_index, 0, 88);
}
}
sbuf->filled += data_left / sizeof(int16_t) / chs;
}
static int decode_block1(STREAMFILE* sf, imuse_codec_data* data, uint8_t* block, size_t data_left) {
int block_num = data->current_block;
switch(data->block_table[block_num].flags) {
case 0x0D:
case 0x0F:
decode_vima1(sf, &data->sbuf, block, data_left, block_num, data->adpcm_table);
break;
default:
return 0;
}
return 1;
}
static void decode_data2(STREAMFILE* sf, sbuf_t* sbuf, uint8_t* buf, size_t data_left, int block_num) {
int i, j;
int channels = sbuf->channels;
if (block_num == 0) {
/* iMUS header (always in first block, not shared with audio data unlike V1) */
sbuf->filled = 0;
}
else {
/* presumably PCM data (not seen) */
for (i = 0, j = 0; i < data_left / sizeof(sample_t); i++, j += 2) {
sbuf->samples[i] = get_s16le(buf + j);
}
sbuf->filled += data_left / channels / sizeof(sample_t);
VGM_LOG("IMUS: found PCM block %i\n", block_num);
}
}
static void decode_vima2(STREAMFILE* sf, sbuf_t* sbuf, uint8_t* buf, size_t data_left, uint16_t* adpcm_table) {
int ch, i, s;
int bitpos;
int adpcm_history[MAX_CHANNELS] = {0};
int adpcm_step_index[MAX_CHANNELS] = {0};
int chs = sbuf->channels;
uint16_t word;
int pos = 0;
/* read ADPCM header */
{
for (i = 0; i < chs; i++) {
adpcm_step_index[i] = get_u8 (buf + pos + 0x00);
adpcm_history[i] = get_s16be(buf + pos + 0x01);
pos += 0x03;
/* checked as < 0 and only for first index, means "stereo" */
if (adpcm_step_index[i] & 0x80) {
adpcm_step_index[i] = (~adpcm_step_index[i]) & 0xFF;
if (chs != 2) return;
}
/* not originally done but in case of garbage data */
adpcm_step_index[i] = clamp_s32(adpcm_step_index[i], 0, 88);
}
}
bitpos = 0;
word = get_u16be(buf + pos); /* originally with a rolling buf, use index to validate overflow */
pos += 0x02;
/* decode ADPCM data after header
* (stereo layout: all samples from L, then all for R) */
for (ch = 0; ch < chs; ch++) {
int sample, step_index;
int samples_to_do;
int samples_left = data_left / sizeof(int16_t);
int first_sample = sbuf->filled * chs + ch;
samples_to_do = samples_left / chs;
step_index = adpcm_step_index[ch];
sample = adpcm_history[ch];
for (i = 0, s = first_sample; i < samples_to_do; i++, s += chs) {
int code_size, code, sign_mask, data_mask, delta;
if (pos >= MAX_BLOCK_SIZE) {
VGM_LOG("IMUSE: wrong pos offset\n");
break;
}
code_size = code_size_table_v2[step_index];
sign_mask = (1 << (code_size - 1));
data_mask = (sign_mask - 1);
/* get bit thing, masks current code and moves 'upwards' word after reading 8 bits */
bitpos += code_size;
code = (word >> (16 - bitpos)) & (sign_mask | data_mask);
if (bitpos > 7) {
word = (word << 8) | buf[pos++];
bitpos -= 8;
}
/* clean sign stuff for next tests */
if (code & sign_mask)
code ^= sign_mask;
else
sign_mask = 0;
/* all bits set mean 'keyframe' = read next sample */
if (code == data_mask) {
sample = (int16_t)(word << bitpos);
word = (word << 8) | buf[pos++];
sample |= (word >> (8 - bitpos)) & 0xFF;
word = (word << 8) | buf[pos++];
}
else {
delta = adpcm_table[(step_index * 64) + ((code << (7 - code_size)))];
if (code)
delta += step_table[step_index] >> (code_size - 1);
if (sign_mask)
delta = -delta;
sample += delta;
sample = clamp16(sample);
}
sbuf->samples[s] = sample;
step_index += index_tables_v2[code_size][code];
step_index = clamp_s32(step_index, 0, 88);
}
}
sbuf->filled += data_left / sizeof(int16_t) / chs;
}
static int decode_block2(STREAMFILE* sf, imuse_codec_data* data, uint8_t* block, size_t data_left) {
int block_num = data->current_block;
switch(data->block_table[block_num].flags) {
case 0x00:
decode_data2(sf, &data->sbuf, block, data_left, block_num);
break;
case 0x01:
decode_vima2(sf, &data->sbuf, block, data_left, data->adpcm_table);
break;
default:
return 0;
}
return 1;
}
/* decodes a whole block into sample buffer, all at once due to L/R layout and VBR data */
static int decode_block(STREAMFILE* sf, imuse_codec_data* data) {
int ok;
uint8_t block[MAX_BLOCK_SIZE];
size_t data_left;
data->sbuf.samples = data->samples;
data->sbuf.channels = data->channels;
if (data->current_block >= data->block_count) {
return 0;
}
/* read block */
{
off_t offset = data->block_table[data->current_block].offset;
size_t size = data->block_table[data->current_block].size;
read_streamfile(block, offset, size, sf);
data_left = data->block_table[data->current_block].data;
}
switch(data->type) {
case COMP:
ok = decode_block1(sf, data, block, data_left);
break;
case MCMP:
ok = decode_block2(sf, data, block, data_left);
break;
default:
return 0;
}
/* block fully read */
data->current_block++;
return ok;
}
void decode_imuse(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do) {
imuse_codec_data* data = vgmstream->codec_data;
STREAMFILE* sf = vgmstream->ch[0].streamfile;
int ok;
while (samples_to_do > 0) {
sbuf_t* sbuf = &data->sbuf;
if (sbuf->filled == 0) {
ok = decode_block(sf, data);
if (!ok) goto fail;
}
sbuf_consume(&outbuf, &samples_to_do, sbuf);
}
return;
fail:
//todo fill silence
return;
}
void free_imuse(imuse_codec_data* data) {
if (!data) return;
free(data->block_table);
free(data);
}
void seek_imuse(imuse_codec_data* data, int32_t num_sample) {
if (!data) return;
//todo find closest block, set skip count
reset_imuse(data);
}
void reset_imuse(imuse_codec_data* data) {
if (!data) return;
data->current_block = 0;
data->sbuf.filled = 0;
}

View File

@ -222,6 +222,7 @@ static const char* extension_list[] = {
"ilv", //txth/reserved [Star Wars Episode III (PS2)]
"ima",
"imc",
"imx",
"int",
"is14",
"isb",
@ -764,6 +765,7 @@ static const coding_info coding_info_list[] = {
{coding_NWA, "VisualArt's NWA DPCM"},
{coding_CIRCUS_ADPCM, "Circus 8-bit ADPCM"},
{coding_UBI_ADPCM, "Ubisoft 4/6-bit ADPCM"},
{coding_IMUSE, "LucasArts iMUSE VIMA ADPCM"},
{coding_EA_MT, "Electronic Arts MicroTalk"},
{coding_CIRCUS_VQ, "Circus VQ"},
@ -1287,6 +1289,7 @@ static const meta_info meta_info_list[] = {
{meta_LRMD, "Sony LRMD header"},
{meta_WWISE_FX, "Audiokinetic Wwise FX header"},
{meta_DIVA, "DIVA header"},
{meta_IMUSE, "LucasArts iMUSE header"},
};

View File

@ -787,6 +787,10 @@
<File
RelativePath=".\meta\imc.c"
>
</File>
<File
RelativePath=".\meta\imuse.c"
>
</File>
<File
RelativePath=".\meta\ios_psnd.c"
@ -2074,10 +2078,14 @@
RelativePath=".\coding\hca_decoder.c"
>
</File>
<File
RelativePath=".\coding\ima_decoder.c"
>
</File>
<File
RelativePath=".\coding\ima_decoder.c"
>
</File>
<File
RelativePath=".\coding\imuse_decoder.c"
>
</File>
<File
RelativePath=".\coding\l5_555_decoder.c"
>

View File

@ -188,6 +188,7 @@
<ClCompile Include="meta\g1l.c" />
<ClCompile Include="meta\ima.c" />
<ClCompile Include="meta\imc.c" />
<ClCompile Include="meta\imuse.c" />
<ClCompile Include="meta\ios_psnd.c" />
<ClCompile Include="meta\ktss.c" />
<ClCompile Include="meta\kwb.c" />
@ -578,6 +579,7 @@
<ClCompile Include="coding\g7221_decoder_lib.c" />
<ClCompile Include="coding\hca_decoder.c" />
<ClCompile Include="coding\ima_decoder.c" />
<ClCompile Include="coding\imuse_decoder.c" />
<ClCompile Include="coding\l5_555_decoder.c" />
<ClCompile Include="coding\mc3_decoder.c" />
<ClCompile Include="coding\mpeg_custom_utils.c" />

View File

@ -1231,6 +1231,9 @@
<ClCompile Include="coding\ima_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="coding\imuse_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="coding\l5_555_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
@ -1543,6 +1546,9 @@
<ClCompile Include="meta\imc.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\imuse.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\pc_adp.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

162
src/meta/imuse.c Normal file
View File

@ -0,0 +1,162 @@
#include "meta.h"
#include "../coding/coding.h"
static int is_id4(const char* test, off_t offset, STREAMFILE* sf) {
uint8_t buf[4];
if (read_streamfile(buf, offset, sizeof(buf), sf) != sizeof(buf))
return 0;
return memcmp(buf, test, sizeof(buf)) == 0; /* memcmp to allow "AB\0\0" */
}
/* LucasArts iMUSE (Interactive Music Streaming Engine) formats */
VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset, name_offset = 0;
off_t head_offset, map_offset, offset;
size_t map_size, data_bytes;
int loop_flag, channels, sample_rate, num_samples;
/* checks */
/* .imx: The Curse of Monkey Island (PC)
* .imc: Grim Fandango (multi)
* .wav: Grim Fandango (multi) RIFF sfx */
if (!check_extensions(sf, "imx,imc,wav,lwav"))
goto fail;
/* base decoder block table */
if (is_id4("COMP", 0x00, sf)) { /* The Curse of Monkey Island (PC), The Dig (PC) */
int entries = read_u32be(0x04,sf);
head_offset = 0x10 + entries * 0x10 + 0x02; /* base header + table + header size */
}
else if (is_id4("MCMP", 0x00, sf)) { /* Grim Fandango (multi), Star Wars: X-Wing Alliance (PC) */
int entries = read_u16be(0x04,sf);
head_offset = 0x06 + entries * 0x09; /* base header + table */
head_offset += 0x02 + read_u16be(head_offset, sf); /* + mini text header */
}
else {
goto fail;
}
/* "offsets" below seem to count decoded data. Data is divided into variable-sized blocks that usually
* return 0x2000 bytes (starting from and including header). File starts with a block table to make
* this manageable. Most offsets don't seem to match block or data boundaries so not really sure. */
/* main header after table */
if (is_id4("iMUS", head_offset, sf)) { /* COMP/MCMP */
int header_found = 0;
/* 0x04: decompressed size (header size + pcm bytes) */
if (!is_id4("MAP ", head_offset + 0x08, sf))
goto fail;
map_size = read_u32be(head_offset + 0x0c, sf);
map_offset = head_offset + 0x10;
/* MAP table (commands for interactive use) */
offset = map_offset;
while (offset < map_offset + map_size) {
uint32_t type = read_u32be(offset + 0x00, sf);
uint32_t size = read_u32be(offset + 0x04, sf);
offset += 0x08;
switch(type) {
case 0x46524D54: /* "FRMT" (header, always first) */
if (header_found)
goto fail;
header_found = 1;
/* 00: data offset */
/* 04: 0? */
/* 08: sample size (16b) */
sample_rate = read_u32be(offset + 0x0c,sf);
channels = read_u32be(offset + 0x10,sf);
break;
case 0x54455854: /* "TEXT" (info) */
/* optional info usually before some REGN: "****"=start, "loop"=loop,
* use first TEXT as name, usually filename for music */
/* 00: offset */
if (!name_offset) /* */
name_offset = offset + 0x04; /* null terminated */
break;
/* - SYNC: 'lip' sync info
* 00 offset
* 04 sync commands until end?
* - REGN: section config (at least one?)
* 00 offset
* 04 size
* - JUMP: usually defines a loop, sometimes after a REGN
* 00 offset (from iMUS)
* 04 size?
* 08 number?
* 0c size?
* - STOP: last command (always?)
* 00 offset
*/
default: /* maybe set REGN as subsongs? + imuse_set_region(vgmstream->data, offset, size) */
break;
}
offset += size;
}
if (!header_found)
goto fail;
if (!is_id4("DATA", head_offset + 0x10 + map_size + 0x00, sf))
goto fail;
data_bytes = read_u32be(head_offset + 0x10 + map_size + 0x04, sf);
num_samples = data_bytes / channels / sizeof(int16_t);
//num_samples = (read_u32be(head_offset + 0x04,sf) - head_size) / channels / sizeof(int16_t); /* equivalent */
}
else if (is_id4("RIFF", head_offset, sf)) { /* MCMP voices */
/* standard (LE), with fake codec 1 and sizes also in decoded bytes (see above) */
if (!is_id4("fmt ", head_offset + 0x0c, sf))
goto fail;
offset = head_offset + 0x14;
channels = read_u16le(offset + 0x02,sf);
sample_rate = read_u32le(offset + 0x04,sf);
if (!is_id4("data", head_offset + 0x24, sf))
goto fail;
data_bytes = read_u32le(head_offset + 0x28, sf);
num_samples = data_bytes / channels / sizeof(int16_t);
}
else {
goto fail; /* The Dig (PC) has no header, detect? */
}
loop_flag = 0;
start_offset = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_IMUSE;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->coding_type = coding_IMUSE;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_imuse(sf, channels);
if (!vgmstream->codec_data) goto fail;
if (name_offset > 0)
read_string(vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf);
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -895,4 +895,6 @@ VGMSTREAM* init_vgmstream_encrypted(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_diva(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf);
#endif /*_META_H*/

View File

@ -493,6 +493,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_bkhd,
init_vgmstream_bkhd_fx,
init_vgmstream_diva,
init_vgmstream_imuse,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
@ -681,6 +682,10 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
reset_ubi_adpcm(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_IMUSE) {
reset_imuse(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_EA_MT) {
reset_ea_mt(vgmstream);
}
@ -860,6 +865,11 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
vgmstream->codec_data = NULL;
}
if (vgmstream->coding_type == coding_IMUSE) {
free_imuse(vgmstream->codec_data);
vgmstream->codec_data = NULL;
}
if (vgmstream->coding_type == coding_EA_MT) {
free_ea_mt(vgmstream->codec_data, vgmstream->channels);
vgmstream->codec_data = NULL;
@ -1301,6 +1311,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
return (vgmstream->interleave_block_size - 0x05)*2 + 2;
case coding_UBI_ADPCM:
return 0; /* varies per mode */
case coding_IMUSE:
return 0; /* varies per frame */
case coding_EA_MT:
return 0; /* 432, but variable in looped files */
case coding_CIRCUS_VQ:
@ -1493,6 +1505,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
return vgmstream->interleave_block_size;
case coding_UBI_ADPCM:
return 0; /* varies per mode? */
case coding_IMUSE:
return 0; /* varies per frame */
case coding_EA_MT:
return 0; /* variable (frames of bit counts or PCM frames) */
#ifdef VGM_USE_ATRAC9
@ -2170,6 +2184,10 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
decode_ubi_adpcm(vgmstream, buffer+samples_written*vgmstream->channels, samples_to_do);
break;
case coding_IMUSE:
decode_imuse(vgmstream, buffer+samples_written*vgmstream->channels, samples_to_do);
break;
case coding_EA_MT:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_ea_mt(vgmstream, buffer+samples_written*vgmstream->channels+ch,
@ -2263,6 +2281,10 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) {
seek_ubi_adpcm(vgmstream->codec_data, vgmstream->loop_sample);
}
if (vgmstream->coding_type == coding_IMUSE) {
seek_imuse(vgmstream->codec_data, vgmstream->loop_sample);
}
if (vgmstream->coding_type == coding_EA_MT) {
seek_ea_mt(vgmstream, vgmstream->loop_sample);
}

View File

@ -167,6 +167,7 @@ typedef enum {
coding_OKI16, /* OKI 4-bit ADPCM with 16-bit output and modified expand */
coding_OKI4S, /* OKI 4-bit ADPCM with 16-bit output and cuadruple step */
coding_PTADPCM, /* Platinum 4-bit ADPCM */
coding_IMUSE, /* LucasArts iMUSE Variable ADPCM */
/* others */
coding_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */
@ -727,6 +728,7 @@ typedef enum {
meta_LRMD,
meta_WWISE_FX,
meta_DIVA,
meta_IMUSE,
} meta_t;
/* standard WAVEFORMATEXTENSIBLE speaker positions */