From 6072d17f506f4f3a73427fbdb02f239cb947951e Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 3 Dec 2017 13:44:55 +0100 Subject: [PATCH] Add EA MicroTalk decoder [FIFA 2001 PS2, Ultima IX voices] --- README.md | 3 +- src/coding/coding.h | 8 + src/coding/ea_mt_decoder.c | 577 +++++++++++++++++++++++++++++++ src/formats.c | 2 + src/layout/blocked_ea_schl.c | 21 +- src/libvgmstream.vcproj | 4 + src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 + src/meta/ea_schl.c | 25 +- src/meta/flx.c | 13 +- src/vgmstream.c | 38 +- src/vgmstream.h | 8 + 12 files changed, 670 insertions(+), 33 deletions(-) create mode 100644 src/coding/ea_mt_decoder.c diff --git a/README.md b/README.md index ea127731..e9f05baf 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,10 @@ are used in few games. - InterPlay ACM - Visual Art's NWA - CRI HCA +- Electronic Arts MicroTalk - Xiph Vorbis (Ogg, FSB5, Wwise, OGL, Silicon Knights) - MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, etc) -- Electronic Arts EALayer3 v1 +- Electronic Arts EALayer3 - ITU-T G.722.1 (Polycom Siren 7) - ITU-T G.722.1 annex C (Polycom Siren 14) - ITU G.719 annex B (Polycom Siren 22) diff --git a/src/coding/coding.h b/src/coding/coding.h index 46c45fd1..0d701e27 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -135,6 +135,14 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* mc3_decoder */ void decode_mc3(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +/* ea_mt_decoder*/ +ea_mt_codec_data *init_ea_mt(int channel_count, int type); +void decode_ea_mt(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void reset_ea_mt(VGMSTREAM * vgmstream); +void flush_ea_mt(VGMSTREAM *vgmstream); +void seek_ea_mt(VGMSTREAM * vgmstream, int32_t num_sample); +void free_ea_mt(ea_mt_codec_data *data); + /* hca_decoder */ void decode_hca(hca_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels); void reset_hca(VGMSTREAM *vgmstream); diff --git a/src/coding/ea_mt_decoder.c b/src/coding/ea_mt_decoder.c new file mode 100644 index 00000000..fa02d8c5 --- /dev/null +++ b/src/coding/ea_mt_decoder.c @@ -0,0 +1,577 @@ +#include "coding.h" + +/* Decodes EA MicroTalk (speech codec) using a copied utkencode lib. + * EA separates MT10:1 and MT5:1 (bigger frames), but apparently are the same + * with different encoding parameters. Later revisions may have PCM blocks (rare). + * + * Decoder by Andrew D'Addesio: https://github.com/daddesio/utkencode + * Info: http://wiki.niotso.org/UTK + * + * The following tries to follow the original code as close as possible, with minimal changes for vgmstream + */ + +/* ************************************************************************************************* */ +#define UTK_BUFFER_SIZE 0x4000 + +//#define UTK_MAKE_U32(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24)) +#define UTK_ROUND(x) ((x) >= 0.0f ? ((x)+0.5f) : ((x)-0.5f)) +#define UTK_MIN(x,y) ((x)<(y)?(x):(y)) +#define UTK_MAX(x,y) ((x)>(y)?(x):(y)) +#define UTK_CLAMP(x,min,max) UTK_MIN(UTK_MAX(x,min),max) + + +/* Note: This struct assumes a member alignment of 4 bytes. +** This matters when pitch_lag > 216 on the first subframe of any given frame. */ +typedef struct UTKContext { + uint8_t buffer[UTK_BUFFER_SIZE]; //vgmstream extra + STREAMFILE * streamfile; //vgmstream extra + off_t offset; //vgmstream extra + int samples_filled; //vgmstream extra + //FILE *fp; //vgmstream extra + const uint8_t *ptr, *end; + int parsed_header; + unsigned int bits_value; + int bits_count; + int reduced_bw; + int multipulse_thresh; + float fixed_gains[64]; + float rc[12]; + float synth_history[12]; + float adapt_cb[324]; + float decompressed_frame[432]; +} UTKContext; + +enum { + MDL_NORMAL = 0, + MDL_LARGEPULSE = 1 +}; + +static const float utk_rc_table[64] = { + 0.0f, + -.99677598476409912109375f, -.99032700061798095703125f, -.983879029750823974609375f, -.977430999279022216796875f, + -.970982015132904052734375f, -.964533984661102294921875f, -.958085000514984130859375f, -.9516370296478271484375f, + -.930754005908966064453125f, -.904959976673126220703125f, -.879167020320892333984375f, -.853372991085052490234375f, + -.827579021453857421875f, -.801786005496978759765625f, -.775991976261138916015625f, -.75019800662994384765625f, + -.724404990673065185546875f, -.6986110210418701171875f, -.6706349849700927734375f, -.61904799938201904296875f, + -.567460000514984130859375f, -.515873014926910400390625f, -.4642859995365142822265625f, -.4126980006694793701171875f, + -.361110985279083251953125f, -.309523999691009521484375f, -.257937014102935791015625f, -.20634900033473968505859375f, + -.1547619998455047607421875f, -.10317499935626983642578125f, -.05158700048923492431640625f, + 0.0f, + +.05158700048923492431640625f, +.10317499935626983642578125f, +.1547619998455047607421875f, +.20634900033473968505859375f, + +.257937014102935791015625f, +.309523999691009521484375f, +.361110985279083251953125f, +.4126980006694793701171875f, + +.4642859995365142822265625f, +.515873014926910400390625f, +.567460000514984130859375f, +.61904799938201904296875f, + +.6706349849700927734375f, +.6986110210418701171875f, +.724404990673065185546875f, +.75019800662994384765625f, + +.775991976261138916015625f, +.801786005496978759765625f, +.827579021453857421875f, +.853372991085052490234375f, + +.879167020320892333984375f, +.904959976673126220703125f, +.930754005908966064453125f, +.9516370296478271484375f, + +.958085000514984130859375f, +.964533984661102294921875f, +.970982015132904052734375f, +.977430999279022216796875f, + +.983879029750823974609375f, +.99032700061798095703125f, +.99677598476409912109375 +}; + +static const uint8_t utk_codebooks[2][256] = { + { /* normal model */ + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22, + 4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18, + 4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2 + }, { /* large-pulse model */ + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28, + 4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24, + 4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3 + } +}; + +static const struct { + int next_model; + int code_size; + float pulse_value; +} utk_commands[29] = { + {MDL_LARGEPULSE, 8, 0.0f}, + {MDL_LARGEPULSE, 7, 0.0f}, + {MDL_NORMAL, 8, 0.0f}, + {MDL_NORMAL, 7, 0.0f}, + {MDL_NORMAL, 2, 0.0f}, + {MDL_NORMAL, 2, -1.0f}, + {MDL_NORMAL, 2, +1.0f}, + {MDL_NORMAL, 3, -1.0f}, + {MDL_NORMAL, 3, +1.0f}, + {MDL_LARGEPULSE, 4, -2.0f}, + {MDL_LARGEPULSE, 4, +2.0f}, + {MDL_LARGEPULSE, 3, -2.0f}, + {MDL_LARGEPULSE, 3, +2.0f}, + {MDL_LARGEPULSE, 5, -3.0f}, + {MDL_LARGEPULSE, 5, +3.0f}, + {MDL_LARGEPULSE, 4, -3.0f}, + {MDL_LARGEPULSE, 4, +3.0f}, + {MDL_LARGEPULSE, 6, -4.0f}, + {MDL_LARGEPULSE, 6, +4.0f}, + {MDL_LARGEPULSE, 5, -4.0f}, + {MDL_LARGEPULSE, 5, +4.0f}, + {MDL_LARGEPULSE, 7, -5.0f}, + {MDL_LARGEPULSE, 7, +5.0f}, + {MDL_LARGEPULSE, 6, -5.0f}, + {MDL_LARGEPULSE, 6, +5.0f}, + {MDL_LARGEPULSE, 8, -6.0f}, + {MDL_LARGEPULSE, 8, +6.0f}, + {MDL_LARGEPULSE, 7, -6.0f}, + {MDL_LARGEPULSE, 7, +6.0f} +}; + +static int utk_read_byte(UTKContext *ctx) +{ + if (ctx->ptr < ctx->end) + return *ctx->ptr++; + + //vgmstream extra: this reads from FILE if static buffer was exhausted, now from a context buffer and STREAMFILE instead + if (ctx->streamfile) { //if (ctx->fp) { + //static uint8_t buffer[4096]; + //size_t bytes_copied = fread(buffer, 1, sizeof(buffer), ctx->fp); + size_t bytes_copied = read_streamfile(ctx->buffer, ctx->offset, sizeof(ctx->buffer), ctx->streamfile); + + ctx->offset += bytes_copied; + if (bytes_copied > 0 && bytes_copied <= sizeof(ctx->buffer)) { + ctx->ptr = ctx->buffer; + ctx->end = ctx->buffer + bytes_copied; + return *ctx->ptr++; + } + } + + return 0; +} + +static int16_t utk_read_i16(UTKContext *ctx) +{ + int x = utk_read_byte(ctx); + x = (x << 8) | utk_read_byte(ctx); + return x; +} + +static int utk_read_bits(UTKContext *ctx, int count) +{ + int ret = ctx->bits_value & ((1 << count) - 1); + ctx->bits_value >>= count; + ctx->bits_count -= count; + + if (ctx->bits_count < 8) { + /* read another byte */ + ctx->bits_value |= utk_read_byte(ctx) << ctx->bits_count; + ctx->bits_count += 8; + } + + return ret; +} + +static void utk_parse_header(UTKContext *ctx) +{ + int i; + float multiplier; + + ctx->reduced_bw = utk_read_bits(ctx, 1); + ctx->multipulse_thresh = 32 - utk_read_bits(ctx, 4); + ctx->fixed_gains[0] = 8.0f * (1 + utk_read_bits(ctx, 4)); + multiplier = 1.04f + utk_read_bits(ctx, 6)*0.001f; + + for (i = 1; i < 64; i++) + ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier; +} + +static void utk_decode_excitation(UTKContext *ctx, int use_multipulse, float *out, int stride) +{ + int i; + + if (use_multipulse) { + /* multi-pulse model: n pulses are coded explicitly; the rest are zero */ + int model, cmd; + model = 0; + i = 0; + while (i < 108) { + cmd = utk_codebooks[model][ctx->bits_value & 0xff]; + model = utk_commands[cmd].next_model; + utk_read_bits(ctx, utk_commands[cmd].code_size); + + if (cmd > 3) { + /* insert a pulse with magnitude <= 6.0f */ + out[i] = utk_commands[cmd].pulse_value; + i += stride; + } else if (cmd > 1) { + /* insert between 7 and 70 zeros */ + int count = 7 + utk_read_bits(ctx, 6); + if (i + count * stride > 108) + count = (108 - i)/stride; + + while (count > 0) { + out[i] = 0.0f; + i += stride; + count--; + } + } else { + /* insert a pulse with magnitude >= 7.0f */ + int x = 7; + + while (utk_read_bits(ctx, 1)) + x++; + + if (!utk_read_bits(ctx, 1)) + x *= -1; + + out[i] = (float)x; + i += stride; + } + } + } else { + /* RELP model: entire residual (excitation) signal is coded explicitly */ + i = 0; + while (i < 108) { + if (!utk_read_bits(ctx, 1)) + out[i] = 0.0f; + else if (!utk_read_bits(ctx, 1)) + out[i] = -2.0f; + else + out[i] = 2.0f; + + i += stride; + } + } +} + +static void rc_to_lpc(const float *rc, float *lpc) +{ + int i, j; + float tmp1[12]; + float tmp2[12]; + + for (i = 10; i >= 0; i--) + tmp2[1+i] = rc[i]; + + tmp2[0] = 1.0f; + + for (i = 0; i < 12; i++) { + float x = -tmp2[11] * rc[11]; + + for (j = 10; j >= 0; j--) { + x -= tmp2[j] * rc[j]; + tmp2[j+1] = x * rc[j] + tmp2[j]; + } + + tmp1[i] = tmp2[0] = x; + + for (j = 0; j < i; j++) + x -= tmp1[i-1-j] * lpc[j]; + + lpc[i] = x; + } +} + +static void utk_lp_synthesis_filter(UTKContext *ctx, int offset, int num_blocks) +{ + int i, j, k; + float lpc[12]; + float *ptr = &ctx->decompressed_frame[offset]; + + rc_to_lpc(ctx->rc, lpc); + + for (i = 0; i < num_blocks; i++) { + for (j = 0; j < 12; j++) { + float x = *ptr; + + for (k = 0; k < j; k++) + x += lpc[k] * ctx->synth_history[k-j+12]; + for (; k < 12; k++) + x += lpc[k] * ctx->synth_history[k-j]; + + ctx->synth_history[11-j] = x; + *ptr++ = x; + } + } +} + +/* +** Public functions. +*/ + +static void utk_decode_frame(UTKContext *ctx) +{ + int i, j; + int use_multipulse = 0; + float excitation[5+108+5]; + float rc_delta[12]; + + if (!ctx->bits_count) { + ctx->bits_value = utk_read_byte(ctx); + ctx->bits_count = 8; + } + + if (!ctx->parsed_header) { + utk_parse_header(ctx); + ctx->parsed_header = 1; + } + + memset(&excitation[0], 0, 5*sizeof(float)); + memset(&excitation[5+108], 0, 5*sizeof(float)); + + /* read the reflection coefficients */ + for (i = 0; i < 12; i++) { + int idx; + if (i == 0) { + idx = utk_read_bits(ctx, 6); + if (idx < ctx->multipulse_thresh) + use_multipulse = 1; + } else if (i < 4) { + idx = utk_read_bits(ctx, 6); + } else { + idx = 16 + utk_read_bits(ctx, 5); + } + + rc_delta[i] = (utk_rc_table[idx] - ctx->rc[i])*0.25f; + } + + /* decode four subframes */ + for (i = 0; i < 4; i++) { + int pitch_lag = utk_read_bits(ctx, 8); + float pitch_gain = (float)utk_read_bits(ctx, 4)/15.0f; + float fixed_gain = ctx->fixed_gains[utk_read_bits(ctx, 6)]; + + if (!ctx->reduced_bw) { + utk_decode_excitation(ctx, use_multipulse, &excitation[5], 1); + } else { + /* residual (excitation) signal is encoded at reduced bandwidth */ + int align = utk_read_bits(ctx, 1); + int zero = utk_read_bits(ctx, 1); + + utk_decode_excitation(ctx, use_multipulse, &excitation[5+align], 2); + + if (zero) { + /* fill the remaining samples with zero + ** (spectrum is duplicated into high frequencies) */ + for (j = 0; j < 54; j++) + excitation[5+(1-align)+2*j] = 0.0f; + } else { + /* interpolate the remaining samples + ** (spectrum is low-pass filtered) */ + float *ptr = &excitation[5+(1-align)]; + for (j = 0; j < 108; j += 2) + ptr[j] = ptr[j-5] * 0.01803267933428287506103515625f + - ptr[j-3] * 0.114591561257839202880859375f + + ptr[j-1] * 0.597385942935943603515625f + + ptr[j+1] * 0.597385942935943603515625f + - ptr[j+3] * 0.114591561257839202880859375f + + ptr[j+5] * 0.01803267933428287506103515625f; + + /* scale by 0.5f to give the sinc impulse response unit energy */ + fixed_gain *= 0.5f; + } + } + + for (j = 0; j < 108; j++) + ctx->decompressed_frame[108*i+j] = fixed_gain * excitation[5+j] + + pitch_gain * ctx->adapt_cb[108*i+216-pitch_lag+j]; + } + + for (i = 0; i < 324; i++) + ctx->adapt_cb[i] = ctx->decompressed_frame[108+i]; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 12; j++) + ctx->rc[j] += rc_delta[j]; + + utk_lp_synthesis_filter(ctx, 12*i, i < 3 ? 1 : 33); + } +} + +static void utk_init(UTKContext *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); +} + +#if 0 //vgmstream extra: see flush_ea_mt +static void utk_set_fp(UTKContext *ctx, FILE *fp) +{ + ctx->fp = fp; + + /* reset the bit reader */ + ctx->bits_count = 0; +} + +static void utk_set_ptr(UTKContext *ctx, const uint8_t *ptr, const uint8_t *end) +{ + ctx->ptr = ptr; + ctx->end = end; + + /* reset the bit reader */ + ctx->bits_count = 0; +} +#endif + +/* +** MicroTalk Revision 3 decoding function. +*/ + +static void utk_rev3_decode_frame(UTKContext *ctx) +{ + int pcm_data_present = (utk_read_byte(ctx) == 0xee); + int i; + + utk_decode_frame(ctx); + + /* unread the last 8 bits and reset the bit reader */ + ctx->ptr--; + ctx->bits_count = 0; + + if (pcm_data_present) { + /* Overwrite n samples at a given offset in the decoded frame with + ** raw PCM data. */ + int offset = utk_read_i16(ctx); + int count = utk_read_i16(ctx); + + /* sx.exe does not do any bounds checking or clamping of these two + ** fields (see 004274D1 in sx.exe v3.01.01), which means a specially + ** crafted MT5:1 file can crash sx.exe. + ** We will throw an error instead. */ + if (offset < 0 || offset > 432) { + //fprintf(stderr, "error: invalid PCM offset %d\n", offset); + //exit(EXIT_FAILURE); + return; //vgmstream extra + } + if (count < 0 || count > 432 - offset) { + //fprintf(stderr, "error: invalid PCM count %d\n", count); + //exit(EXIT_FAILURE); + return; //vgmstream extra + } + + for (i = 0; i < count; i++) + ctx->decompressed_frame[offset+i] = (float)utk_read_i16(ctx); + } +} + +/* ************************************************************************************************* */ + +ea_mt_codec_data *init_ea_mt(int channel_count, int pcm_blocks) { + ea_mt_codec_data *data = NULL; + int i; + + data = calloc(channel_count, sizeof(ea_mt_codec_data)); + if (!data) goto fail; + + data->pcm_blocks = pcm_blocks; + data->utk_context_size = channel_count; + + data->utk_context = calloc(channel_count, sizeof(UTKContext*)); + if (!data->utk_context) goto fail; + + for (i = 0; i < channel_count; i++) { + data->utk_context[i] = calloc(1, sizeof(UTKContext)); + if (!data->utk_context[i]) goto fail; + utk_init(data->utk_context[i]); + } + + return data; + +fail: + free_ea_mt(data); + return NULL; +} + +void decode_ea_mt(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { + ea_mt_codec_data *data = vgmstream->codec_data; + int i, sample_count = 0, frame_samples; + UTKContext* ctx = data->utk_context[channel]; + + /* Use the above decoder, which expects pointers to read data. Since EA-MT frames aren't + * byte-aligned, reading new buffer data is decided by the decoder. When decoding starts + * or a SCHl block changes flush_ea_mt must be called to reset the state. + * A bit hacky but would need some restructuring otherwise. */ + + frame_samples = 432; + first_sample = first_sample % frame_samples; + + /* don't decode again if we didn't consume the current frame. + * UTKContext saves the sample buffer, and can't re-decode a frame */ + if (!ctx->samples_filled) { + if (data->pcm_blocks) + utk_rev3_decode_frame(ctx); + else + utk_decode_frame(ctx); + ctx->samples_filled = 1; + } + + /* copy samples */ + for (i = first_sample; i < first_sample+samples_to_do; i++) { + int x = UTK_ROUND(ctx->decompressed_frame[i]); + outbuf[sample_count] = (int16_t)UTK_CLAMP(x, -32768, 32767); + sample_count += channelspacing; + } + + if (i == frame_samples) + ctx->samples_filled = 0; +} + +static void flush_ea_mt_internal(VGMSTREAM *vgmstream, int is_start) { + ea_mt_codec_data *data = vgmstream->codec_data; + int i; + size_t bytes; + + /* the decoder needs to be notified when offsets change */ + for (i = 0; i < vgmstream->channels; i++) { + UTKContext *ctx = data->utk_context[i]; + + ctx->streamfile = vgmstream->ch[i].streamfile; + ctx->offset = is_start ? vgmstream->ch[i].channel_start_offset : vgmstream->ch[i].offset; + ctx->samples_filled = 0; + + bytes = read_streamfile(ctx->buffer,ctx->offset,sizeof(ctx->buffer),ctx->streamfile); + ctx->offset += sizeof(ctx->buffer); + ctx->ptr = ctx->buffer; + ctx->end = ctx->buffer + bytes; + ctx->bits_count = 0; + } +} + +void flush_ea_mt(VGMSTREAM *vgmstream) { + flush_ea_mt_internal(vgmstream, 0); +} + +void reset_ea_mt(VGMSTREAM *vgmstream) { + flush_ea_mt_internal(vgmstream, 1); +} + +void seek_ea_mt(VGMSTREAM * vgmstream, int32_t num_sample) { + flush_ea_mt_internal(vgmstream, 1); + //todo discard loop (though this should be adecuate as probably only uses full loops, if at all) +} + +void free_ea_mt(ea_mt_codec_data *data) { + int i; + + if (!data) + return; + + for (i = 0; i < data->utk_context_size; i++) { + free(data->utk_context[i]); + } + free(data->utk_context); + free(data); +} diff --git a/src/formats.c b/src/formats.c index 515ee983..1d6be599 100644 --- a/src/formats.c +++ b/src/formats.c @@ -489,6 +489,8 @@ static const coding_info coding_info_list[] = { {coding_NWA4, "NWA DPCM Level 4"}, {coding_NWA5, "NWA DPCM Level 5"}, + {coding_EA_MT, "Electronic Arts MicroTalk"}, + {coding_CRI_HCA, "CRI HCA"}, #ifdef VGM_USE_VORBIS diff --git a/src/layout/blocked_ea_schl.c b/src/layout/blocked_ea_schl.c index dc35e087..39c8f264 100644 --- a/src/layout/blocked_ea_schl.c +++ b/src/layout/blocked_ea_schl.c @@ -119,6 +119,17 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { break; + /* id, size, samples, offsets-per-channel, flag (0x01 = data start), data */ + case coding_EA_MT: + for (i = 0; i < vgmstream->channels; i++) { + off_t channel_start = read_32bit(block_offset + 0x0C + (0x04*i),streamFile); + vgmstream->ch[i].offset = block_offset + 0x0C + (0x04*vgmstream->channels) + channel_start + 0x01; + } + + /* flush decoder in every block change */ + flush_ea_mt(vgmstream); + break; + #ifdef VGM_USE_MPEG /* id, size, samples, offset?, unknown (null for MP2, some constant for all blocks for EALayer3) */ case coding_MPEG_custom: @@ -161,14 +172,4 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { vgmstream->next_block_offset = block_offset + block_size; vgmstream->current_block_samples = block_samples; vgmstream->current_block_size = 0; /* uses current_block_samples instead */ - - - /* reset channel sub offset for codecs using it */ - if (vgmstream->coding_type == coding_EA_XA - || vgmstream->coding_type == coding_EA_XA_int - || vgmstream->coding_type == coding_EA_XA_V2) { - for(i=0;ichannels;i++) { - vgmstream->ch[i].channel_start_offset=0; - } - } } diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 921d1306..5f3a4b67 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1397,6 +1397,10 @@ + + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index d0c364e0..a9576019 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -823,6 +823,9 @@ coding\Source Files + + coding\Source Files + coding\Source Files diff --git a/src/meta/ea_schl.c b/src/meta/ea_schl.c index ae23cc02..5c409da9 100644 --- a/src/meta/ea_schl.c +++ b/src/meta/ea_schl.c @@ -140,7 +140,7 @@ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { goto fail; /* use header size as endianness flag */ - if ((uint32_t)read_32bitLE(0x08,streamFile) > 0x00F00000) { + if ((uint32_t)read_32bitLE(0x08,streamFile) > 0x000F0000) { /* todo not very accurate */ read_32bit = read_32bitBE; read_16bit = read_16bitBE; } else { @@ -311,14 +311,12 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ #ifdef VGM_USE_MPEG case EA_CODEC2_LAYER2: /* MPEG Layer II, aka MP2 */ case EA_CODEC2_LAYER3: { /* MPEG Layer III, aka MP3 */ - mpeg_custom_config cfg; + mpeg_custom_config cfg = {0}; off_t mpeg_start_offset = is_bnk ? start_offset : get_ea_stream_mpeg_start_offset(streamFile, start_offset, ea); if (!mpeg_start_offset) goto fail; - memset(&cfg, 0, sizeof(mpeg_custom_config)); - /* layout is still blocks, but should work fine with the custom mpeg decoder */ vgmstream->codec_data = init_mpeg_custom_codec_data(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EA, &cfg); if (!vgmstream->codec_data) goto fail; @@ -326,14 +324,12 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ } case EA_CODEC2_EALAYER3: { /* MP3 variant */ - mpeg_custom_config cfg; + mpeg_custom_config cfg = {0}; off_t mpeg_start_offset = is_bnk ? start_offset : get_ea_stream_mpeg_start_offset(streamFile, start_offset, ea); if (!mpeg_start_offset) goto fail; - memset(&cfg, 0, sizeof(mpeg_custom_config)); - /* layout is still blocks, but should work fine with the custom mpeg decoder */ vgmstream->codec_data = init_mpeg_custom_codec_data(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EAL31, &cfg); if (!vgmstream->codec_data) goto fail; @@ -343,6 +339,12 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ case EA_CODEC2_MT10: /* MicroTalk (10:1 compression) */ case EA_CODEC2_MT5: /* MicroTalk (5:1 compression) */ + vgmstream->coding_type = coding_EA_MT; + VGM_LOG("mt: codec=%x, cv=%x, v=%x\n", ea->codec2, ea->codec_version, ea->version); + vgmstream->codec_data = init_ea_mt(vgmstream->channels, ea->version == EA_VERSION_V3); + if (!vgmstream->codec_data) goto fail; + break; + case EA_CODEC2_ATRAC3PLUS: /* regular ATRAC3plus chunked in SCxx blocks, including RIFF header */ default: VGM_LOG("EA SCHl: unknown codec2 0x%02x for platform 0x%02x\n", ea->codec2, ea->platform); @@ -401,15 +403,6 @@ static VGMSTREAM * init_vgmstream_ea_variable_header(STREAMFILE *streamFile, ea_ } break; } - - /* reset channel sub offset for codecs using it */ - if (vgmstream->coding_type == coding_EA_XA - || vgmstream->coding_type == coding_EA_XA_int - || vgmstream->coding_type == coding_EA_XA_V2) { - for(i=0;ichannels;i++) { - vgmstream->ch[i].channel_start_offset = 0; - } - } } else { /* setup first block to update offsets */ diff --git a/src/meta/flx.c b/src/meta/flx.c index acf07ac8..ab28a322 100644 --- a/src/meta/flx.c +++ b/src/meta/flx.c @@ -61,7 +61,7 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE *streamFile) { vgmstream->meta_type = meta_PC_FLX; switch(codec) { - case 0x00: /* PCM */ + case 0x00: /* PCM (sfx) */ vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; @@ -69,7 +69,7 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE *streamFile) { vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, 16); break; - case 0x01: /* EA-XA (music) */ + case 0x01: /* EA-XA (music, sfx) */ vgmstream->coding_type = channel_count > 1 ? coding_EA_XA : coding_EA_XA_int; vgmstream->layout_type = layout_none; @@ -79,6 +79,14 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE *streamFile) { break; case 0x02: /* EA-MT (voices) */ + vgmstream->coding_type = coding_EA_MT; + vgmstream->codec_data = init_ea_mt(vgmstream->channels, 0); + if (!vgmstream->codec_data) goto fail; + + vgmstream->num_samples = read_32bitLE(start_offset,streamFile); + start_offset += 0x04; + break; + default: VGM_LOG("FLX: unknown codec 0x%x\n", codec); goto fail; @@ -90,6 +98,7 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE *streamFile) { /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; + return vgmstream; fail: diff --git a/src/vgmstream.c b/src/vgmstream.c index 48f25cc4..dbd0e076 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -522,6 +522,10 @@ void reset_vgmstream(VGMSTREAM * vgmstream) { reset_hca(vgmstream); } + if (vgmstream->coding_type==coding_EA_MT) { + reset_ea_mt(vgmstream); + } + #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) if (vgmstream->coding_type==coding_MP4_AAC) { reset_mp4_aac(vgmstream); @@ -702,6 +706,11 @@ void close_vgmstream(VGMSTREAM * vgmstream) { vgmstream->codec_data = NULL; } + if (vgmstream->coding_type==coding_EA_MT) { + free_ea_mt(vgmstream->codec_data); + vgmstream->codec_data = NULL; + } + #ifdef VGM_USE_FFMPEG if (vgmstream->coding_type==coding_FFmpeg) { free_ffmpeg(vgmstream->codec_data); @@ -1110,11 +1119,13 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { case coding_LSF: return 54; case coding_MTAF: - return 0x80*2; + return 128*2; case coding_MTA2: - return 0x80*2; + return 128*2; case coding_MC3: return 10; + case coding_EA_MT: + return 432; case coding_CRI_HCA: return clHCA_samplesPerBlock; #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) @@ -1194,6 +1205,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_G721: case coding_SNDS_IMA: case coding_OTNS_IMA: + return 0; case coding_UBI_IMA: /* variable (PCM then IMA) */ return 0; case coding_NGC_AFC: @@ -1219,7 +1231,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_MAXIS_XA: return 0x0F*vgmstream->channels; case coding_EA_XA_V2: - return 1; /* the frame is variant in size (ADPCM frames of 0x0F or PCM frames) */ + return 0; /* variable (ADPCM frames of 0x0f or PCM frames of 0x3d) */ case coding_EA_XAS: return 0x4c*vgmstream->channels; case coding_WS: @@ -1255,7 +1267,9 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_MTA2: return 0x90; case coding_MC3: - return 4; + return 0x04; + case coding_EA_MT: + return 0; /* variable (frames of bit counts or PCM frames) */ default: return 0; } @@ -1847,6 +1861,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to chan); } break; + case coding_EA_MT: + for (chan=0;chanchannels;chan++) { + decode_ea_mt(vgmstream, buffer+samples_written*vgmstream->channels+chan, + vgmstream->channels, vgmstream->samples_into_block, samples_to_do, + chan); + } + break; default: break; } @@ -1922,6 +1943,10 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { loop_hca(vgmstream); } + if (vgmstream->coding_type==coding_EA_MT) { + seek_ea_mt(vgmstream, vgmstream->loop_start_sample); + } + #ifdef VGM_USE_VORBIS if (vgmstream->coding_type==coding_ogg_vorbis) { seek_ogg_vorbis(vgmstream, vgmstream->loop_sample); @@ -2518,6 +2543,11 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s } } + /* EA-MT decoder is a bit finicky and needs this when channel offsets change */ + if (vgmstream->coding_type == coding_EA_MT) { + flush_ea_mt(vgmstream); + } + return 1; fail: diff --git a/src/vgmstream.h b/src/vgmstream.h index 909d7056..32a378cb 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -161,6 +161,8 @@ typedef enum { coding_NWA4, coding_NWA5, + coding_EA_MT, /* Electronic Arts MicroTalk (linear-predictive speech codec) */ + coding_CRI_HCA, /* CRI High Compression Audio (MDCT-based) */ #ifdef VGM_USE_VORBIS @@ -1145,6 +1147,12 @@ typedef struct { #endif #endif +typedef struct { + int pcm_blocks; + int utk_context_size; + void** utk_context; +} ea_mt_codec_data; + /* -------------------------------------------------------------------------*/ /* vgmstream "public" API */