mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-20 10:25:53 +01:00
207 lines
6.8 KiB
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;
|
|
|
|
}
|