diff --git a/src/coding/coding.h b/src/coding/coding.h index a430db4d..f29fd4e0 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -39,6 +39,6 @@ void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample * outbuf, int32_t sa void decode_sdx2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ws(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_ws(VGMSTREAM * vgmstream, int channel, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); #endif diff --git a/src/coding/ws_decoder.c b/src/coding/ws_decoder.c index 82d67a2f..b72193dc 100644 --- a/src/coding/ws_decoder.c +++ b/src/coding/ws_decoder.c @@ -4,17 +4,143 @@ /* Westwood Studios ADPCM */ -/* Based on Valery V. Ansimovsky's WS-AUD.txt */ +/* Based on Valery V. Anisimovsky's WS-AUD.txt */ -void decode_ws(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +static char WSTable2bit[4]={-2,-1,0,1}; +static char WSTable4bit[16]={-9,-8,-6,-5,-4,-3,-2,-1, + 0, 1, 2, 3, 4, 5 ,6, 8}; - int32_t hist = stream->adpcm_history1_32; +/* We pass in the VGMSTREAM here, unlike in other codings, because + the decoder has to know about the block structure. */ +void decode_ws(VGMSTREAM * vgmstream, int channel, sample * outbuf, int channelspacing, int32_t first_sample, + int32_t samples_to_do) { + + VGMSTREAMCHANNEL * stream = &(vgmstream->ch[channel]); + int16_t hist = stream->adpcm_history1_16; + off_t offset = stream->offset; + int samples_left_in_frame = stream->samples_left_in_frame; + off_t header_off = stream->frame_header_offset; int i; int32_t sample_count; - for (i=first_sample,sample_count=0; iadpcm_history1_32=hist; + if (vgmstream->ws_output_size == vgmstream->current_block_size) { + /* uncompressed, we just need to convert to 16-bit */ + for (i=first_sample,sample_count=0; istreamfile)-0x80)*0x100; + } + } else { + if (first_sample == 0) { + hist = 0x80; + samples_left_in_frame = 0; + } + /* actually decompress */ + for (i=first_sample,sample_count=0; istreamfile); + count = header & 0x3f; + switch (header>>6) { /* code */ + case 0: /* 2-bit ADPCM */ + if (samples_left_in_frame == 0) { + samples_left_in_frame = (count + 1)*4; + } + for (;samples_left_in_frame>0 && /* read this frame */ + istreamfile); + sample = (sample >> (twobit*2)) & 0x3; + hist += WSTable2bit[sample]; + if (hist < 0) hist = 0; + if (hist > 0xff) hist = 0xff; + outbuf[sample_count]=(hist-0x80)*0x100; + + if (twobit == 3) + offset++; /* done with that byte */ + } + break; + case 1: /* 4-bit ADPCM */ + if (samples_left_in_frame == 0) { + samples_left_in_frame = (count + 1)*2; + } + for (;samples_left_in_frame>0 && /* read this frame */ + istreamfile); + if (nibble == 0) + sample &= 0xf; + else + sample >>= 4; + hist += WSTable4bit[sample]; + if (hist < 0) hist = 0; + if (hist > 0xff) hist = 0xff; + outbuf[sample_count]=(hist-0x80)*0x100; + + if (nibble == 1) + offset++; /* done with that byte */ + } + break; + case 2: /* no compression */ + if (count & 0x20) { /* delta */ + /* Note no checks against samples_to_do here, + at the top of the for loop we can always do at + least one sample */ + /* low 5 bits are a signed delta */ + if (count & 0x10) { + hist -= ((count & 0xf)^0xf) + 1; + } else { + hist += count & 0xf; + } + + /* Valery doesn't specify this, but I will assume */ + if (hist < 0) hist = 0; + if (hist > 0xff) hist = 0xff; + + outbuf[sample_count]=(hist-0x80)*0x100; + sample_count+=channelspacing; + i++; + + /* just one, and we got it */ + samples_left_in_frame = 0; + } else { /* copy bytes verbatim */ + if (samples_left_in_frame == 0) + samples_left_in_frame=count+1; + for (;samples_left_in_frame>0 && /* read this frame */ + istreamfile))-0x80)*0x100; + } + } + break; + case 3: /* RLE */ + if (samples_left_in_frame == 0) + samples_left_in_frame=count+1; + for (;samples_left_in_frame>0 && /* read this frame */ + ioffset = offset; + stream->adpcm_history1_16 = hist; + stream->samples_left_in_frame = samples_left_in_frame; + stream->frame_header_offset = header_off; } diff --git a/src/layout/ws_aud_blocked.c b/src/layout/ws_aud_blocked.c index 982e2a2e..dfc824a5 100644 --- a/src/layout/ws_aud_blocked.c +++ b/src/layout/ws_aud_blocked.c @@ -12,8 +12,7 @@ void ws_aud_block_update(off_t block_offset, VGMSTREAM * vgmstream) { vgmstream->current_block_size + 8; if (vgmstream->coding_type == coding_WS) { - /* this only works if the output sample size is 8 bit */ - vgmstream->ws_output_samples = read_16bitLE( + vgmstream->ws_output_size = read_16bitLE( vgmstream->current_block_offset+2, vgmstream->ch[0].streamfile); } diff --git a/src/vgmstream.c b/src/vgmstream.c index bf6b0318..fce2a86f 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -313,7 +313,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { case coding_EAXA: return 28; case coding_WS: - return vgmstream->ws_output_samples; + /* only works if output sample size is 8 bit, which is always + is for WS ADPCM */ + return vgmstream->ws_output_size; default: return 0; } @@ -508,7 +510,7 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to break; case coding_WS: for (chan=0;chanchannels;chan++) { - decode_ws(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan, + decode_ws(vgmstream,chan,buffer+samples_written*vgmstream->channels+chan, vgmstream->channels,vgmstream->samples_into_block, samples_to_do); } @@ -709,7 +711,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { snprintf(temp,TEMPSIZE,"4-bit IMA ADPCM"); break; case coding_WS: - snprintf(temp,TEMPSIZE,"Westwood Studios ADPCM"); + snprintf(temp,TEMPSIZE,"Westwood Studios DPCM"); break; default: snprintf(temp,TEMPSIZE,"CANNOT DECODE"); diff --git a/src/vgmstream.h b/src/vgmstream.h index 019ccace..ef3e26bf 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -156,6 +156,9 @@ typedef struct { off_t channel_start_offset; /* where data for this channel begins */ off_t offset; /* current location in the file */ + off_t frame_header_offset; /* offset of the current frame header (for WS) */ + int samples_left_in_frame; /* for WS */ + /* format specific */ /* adpcm */ @@ -229,7 +232,7 @@ typedef struct { uint8_t ea_compression_version; uint8_t ea_platform; - int32_t ws_output_samples; /* output samples for this block */ + int32_t ws_output_size; /* output bytes for this block */ void * start_vgmstream; /* a copy of the VGMSTREAM as it was at the beginning of the stream */