2018-02-25 10:05:28 +01:00
|
|
|
#include "coding.h"
|
2018-03-08 22:55:50 +01:00
|
|
|
|
2018-02-25 10:05:28 +01:00
|
|
|
|
|
|
|
/* FADPCM table */
|
|
|
|
static const int8_t fadpcm_coefs[8][2] = {
|
|
|
|
{ 0 , 0 },
|
|
|
|
{ 60 , 0 },
|
|
|
|
{ 122 , 60 },
|
|
|
|
{ 115 , 52 },
|
|
|
|
{ 98 , 55 },
|
|
|
|
{ 0 , 0 },
|
|
|
|
{ 0 , 0 },
|
|
|
|
{ 0 , 0 },
|
|
|
|
};
|
|
|
|
|
|
|
|
/* FMOD's FADPCM, basically XA/PSX ADPCM with a fancy header layout.
|
|
|
|
* Code/layout could be simplified but tries to emulate FMOD's code.
|
2018-07-18 00:37:14 +02:00
|
|
|
* Algorithm and tables debugged from their PC DLLs (byte-accurate). */
|
2018-02-25 10:05:28 +01:00
|
|
|
void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
|
|
|
off_t frame_offset;
|
|
|
|
int i, j, k;
|
2018-07-18 00:37:14 +02:00
|
|
|
int block_samples, frames_in, samples_done = 0, sample_count = 0;
|
2018-03-08 22:55:50 +01:00
|
|
|
uint32_t coefs, shifts;
|
2018-02-25 10:05:28 +01:00
|
|
|
int32_t hist1; //= stream->adpcm_history1_32;
|
|
|
|
int32_t hist2; //= stream->adpcm_history2_32;
|
|
|
|
|
|
|
|
/* external interleave (fixed size), mono */
|
|
|
|
block_samples = (0x8c - 0xc) * 2;
|
2018-07-18 00:37:14 +02:00
|
|
|
frames_in = first_sample / block_samples;
|
2018-02-25 10:05:28 +01:00
|
|
|
first_sample = first_sample % block_samples;
|
|
|
|
|
2018-07-18 00:37:14 +02:00
|
|
|
frame_offset = stream->offset + 0x8c*frames_in;
|
2018-02-25 10:05:28 +01:00
|
|
|
|
|
|
|
|
2018-03-08 22:55:50 +01:00
|
|
|
/* parse 0xc header (header samples are not written to outbuf) */
|
|
|
|
coefs = read_32bitLE(frame_offset + 0x00, stream->streamfile);
|
|
|
|
shifts = read_32bitLE(frame_offset + 0x04, stream->streamfile);
|
|
|
|
hist1 = read_16bitLE(frame_offset + 0x08, stream->streamfile);
|
|
|
|
hist2 = read_16bitLE(frame_offset + 0x0a, stream->streamfile);
|
2018-02-25 10:05:28 +01:00
|
|
|
|
|
|
|
|
2018-03-08 22:55:50 +01:00
|
|
|
/* decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2 */
|
2018-02-25 10:05:28 +01:00
|
|
|
for (i = 0; i < 8; i++) {
|
2018-03-08 22:55:50 +01:00
|
|
|
int32_t coef1, coef2, shift, coef_index, shift_factor;
|
2018-02-25 10:05:28 +01:00
|
|
|
off_t group_offset = frame_offset + 0x0c + 0x10*i;
|
2018-03-08 22:55:50 +01:00
|
|
|
|
|
|
|
/* each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2) */
|
|
|
|
coef_index = ((coefs >> i*4) & 0x0f) % 0x07;
|
|
|
|
shift_factor = (shifts >> i*4) & 0x0f;
|
|
|
|
|
|
|
|
coef1 = fadpcm_coefs[coef_index][0];
|
|
|
|
coef2 = fadpcm_coefs[coef_index][1];
|
2018-07-18 00:37:14 +02:00
|
|
|
shift = 0x16 - shift_factor; /* pre-adjust for 32b sign extend */
|
2018-02-25 10:05:28 +01:00
|
|
|
|
|
|
|
for (j = 0; j < 4; j++) {
|
|
|
|
uint32_t nibbles = read_32bitLE(group_offset + 0x04*j, stream->streamfile);
|
|
|
|
|
|
|
|
for (k = 0; k < 8; k++) {
|
|
|
|
int32_t new_sample;
|
|
|
|
|
|
|
|
new_sample = (nibbles >> k*4) & 0x0f;
|
2018-07-18 00:37:14 +02:00
|
|
|
new_sample = (new_sample << 28) >> shift; /* 32b sign extend + scale */
|
2018-02-25 10:05:28 +01:00
|
|
|
new_sample = (new_sample - hist2*coef2 + hist1*coef1);
|
2018-07-18 00:37:14 +02:00
|
|
|
new_sample = new_sample >> 6;
|
2018-02-25 10:05:28 +01:00
|
|
|
new_sample = clamp16(new_sample);
|
|
|
|
|
|
|
|
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
|
|
|
outbuf[samples_done * channelspacing] = new_sample;
|
|
|
|
samples_done++;
|
|
|
|
}
|
|
|
|
sample_count++;
|
|
|
|
|
|
|
|
hist2 = hist1;
|
|
|
|
hist1 = new_sample;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//stream->adpcm_history1_32 = hist1;
|
|
|
|
//stream->adpcm_history2_32 = hist2;
|
|
|
|
}
|