vgmstream/src/coding/ea_mt_decoder.c

207 lines
6.8 KiB
C

#include "coding.h"
#include "ea_mt_decoder_utk.h"
/* Decodes EA MicroTalk (speech codec) using utkencode lib (slightly modified for vgmstream).
* 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
*/
//#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)
#define UTK_BUFFER_SIZE 0x1000
struct ea_mt_codec_data {
STREAMFILE *streamfile;
uint8_t buffer[UTK_BUFFER_SIZE];
off_t offset;
off_t loop_offset;
int loop_sample;
int pcm_blocks;
int samples_filled;
int samples_used;
int samples_done;
int samples_discard;
void* utk_context;
};
static size_t ea_mt_read_callback(void *dest, int size, void *arg);
ea_mt_codec_data *init_ea_mt(int channels, int pcm_blocks) {
return init_ea_mt_loops(channels, pcm_blocks, 0, NULL);
}
ea_mt_codec_data *init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets) {
ea_mt_codec_data *data = NULL;
int i;
data = calloc(channels, sizeof(ea_mt_codec_data)); /* one decoder per channel */
if (!data) goto fail;
for (i = 0; i < channels; i++) {
data[i].utk_context = calloc(1, sizeof(UTKContext));
if (!data[i].utk_context) goto fail;
utk_init(data[i].utk_context);
data[i].pcm_blocks = pcm_blocks;
data[i].loop_sample = loop_sample;
if (loop_offsets)
data[i].loop_offset = loop_offsets[i];
utk_set_callback(data[i].utk_context, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
}
return data;
fail:
free_ea_mt(data, channels);
return NULL;
}
void decode_ea_mt(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t samples_to_do, int channel) {
int i;
ea_mt_codec_data *data = vgmstream->codec_data;
ea_mt_codec_data *ch_data = &data[channel];
UTKContext* ctx = ch_data->utk_context;
int samples_done = 0;
while (samples_done < samples_to_do) {
if (ch_data->samples_filled) {
/* consume current frame */
int samples_to_get = ch_data->samples_filled;
/* don't go past loop, to reset decoder */
if (ch_data->loop_sample > 0 && ch_data->samples_done < ch_data->loop_sample &&
ch_data->samples_done + samples_to_get > ch_data->loop_sample)
samples_to_get = ch_data->loop_sample - ch_data->samples_done;
if (ch_data->samples_discard) {
/* discard samples for looping */
if (samples_to_get > ch_data->samples_discard)
samples_to_get = ch_data->samples_discard;
ch_data->samples_discard -= samples_to_get;
}
else {
/* get max samples and copy */
if (samples_to_get > samples_to_do - samples_done)
samples_to_get = samples_to_do - samples_done;
for (i = ch_data->samples_used; i < ch_data->samples_used + samples_to_get; i++) {
int pcm = UTK_ROUND(ctx->decompressed_frame[i]);
outbuf[0] = (int16_t)UTK_CLAMP(pcm, -32768, 32767);
outbuf += channelspacing;
}
samples_done += samples_to_get;
}
/* mark consumed samples */
ch_data->samples_used += samples_to_get;
ch_data->samples_filled -= samples_to_get;
ch_data->samples_done += samples_to_get;
/* Loops in EA-MT are done with fully separate intro/loop substreams. We must
* notify the decoder when a new substream begins (even with looping disabled). */
if (ch_data->loop_sample > 0 && ch_data->samples_done == ch_data->loop_sample) {
ch_data->samples_filled = 0;
ch_data->samples_discard = 0;
/* offset is usually at loop_offset here, but not always (ex. loop_sample < 432) */
ch_data->offset = ch_data->loop_offset;
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
utk_reset(ctx); /* decoder init (all fields must be reset, for some edge cases) */
}
}
else {
/* new frame */
if (ch_data->pcm_blocks)
utk_rev3_decode_frame(ctx);
else
utk_decode_frame(ctx);
ch_data->samples_used = 0;
ch_data->samples_filled = 432;
}
}
}
static void flush_ea_mt_offsets(VGMSTREAM *vgmstream, int is_start, int samples_discard) {
ea_mt_codec_data *data = vgmstream->codec_data;
int i;
if (!data) return;
/* EA-MT frames are VBR (not byte-aligned?), so utk_decoder reads new buffer data automatically.
* 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. */
for (i = 0; i < vgmstream->channels; i++) {
UTKContext* ctx = data[i].utk_context;
data[i].streamfile = vgmstream->ch[i].streamfile; /* maybe should keep its own STREAMFILE? */
if (is_start)
data[i].offset = vgmstream->ch[i].channel_start_offset;
else
data[i].offset = vgmstream->ch[i].offset;
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
if (is_start) {
utk_reset(ctx);
ctx->parsed_header = 0;
data[i].samples_done = 0;
}
data[i].samples_filled = 0;
data[i].samples_discard = samples_discard;
}
}
void flush_ea_mt(VGMSTREAM *vgmstream) {
flush_ea_mt_offsets(vgmstream, 0, 0);
}
void reset_ea_mt(VGMSTREAM *vgmstream) {
flush_ea_mt_offsets(vgmstream, 1, 0);
}
void seek_ea_mt(VGMSTREAM * vgmstream, int32_t num_sample) {
flush_ea_mt_offsets(vgmstream, 1, num_sample);
}
void free_ea_mt(ea_mt_codec_data *data, int channels) {
int i;
if (!data)
return;
for (i = 0; i < channels; i++) {
free(data[i].utk_context);
}
free(data);
}
/* ********************** */
static size_t ea_mt_read_callback(void *dest, int size, void *arg) {
ea_mt_codec_data *ch_data = arg;
int bytes_read;
bytes_read = read_streamfile(dest,ch_data->offset,size,ch_data->streamfile);
ch_data->offset += bytes_read;
return bytes_read;
}