From 5190b362a9c796c642031c13ef2f7e72515265ca Mon Sep 17 00:00:00 2001 From: NicknineTheEagle Date: Wed, 9 Jan 2019 22:30:15 +0300 Subject: [PATCH] Added 4-bit PCM decoder --- src/coding/coding.h | 2 ++ src/coding/pcm_decoder.c | 48 +++++++++++++++++++++++++++++++++++++++- src/formats.c | 6 +++-- src/meta/genh.c | 10 +++++++++ src/meta/txth.c | 16 ++++++++++++++ src/vgmstream.c | 12 ++++++++++ src/vgmstream.h | 2 ++ 7 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index 676a313f..3744232a 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -71,6 +71,8 @@ void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_ulaw_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); diff --git a/src/coding/pcm_decoder.c b/src/coding/pcm_decoder.c index d958d4fd..1a3cc0de 100644 --- a/src/coding/pcm_decoder.c +++ b/src/coding/pcm_decoder.c @@ -78,6 +78,52 @@ void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } } +void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { + int i, nibble_shift, is_high_first, is_stereo; + int32_t sample_count; + uint16_t sample; + off_t byte_offset; + + is_high_first = (vgmstream->codec_config & 1); + is_stereo = (vgmstream->channels != 1); + + for (i=first_sample,sample_count=0; ioffset + i : /* stereo: one nibble per channel (assumed, not sure if stereo version actually exists) */ + stream->offset + i/2; /* mono: consecutive nibbles */ + nibble_shift = is_high_first ? + is_stereo ? (!(channel&1) ? 4:0) : (!(i&1) ? 4:0) : /* even = high, odd = low */ + is_stereo ? (!(channel&1) ? 0:4) : (!(i&1) ? 0:4); /* even = low, odd = high */ + + sample = (uint16_t)read_8bit(byte_offset, stream->streamfile); + sample = (sample >> nibble_shift) & 0x0F; + outbuf[sample_count] = sample*0x11*0x100; + } +} + +void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { + int i, nibble_shift, is_high_first, is_stereo; + int32_t sample_count; + uint16_t sample; + off_t byte_offset; + + is_high_first = (vgmstream->codec_config & 1); + is_stereo = (vgmstream->channels != 1); + + for (i=first_sample,sample_count=0; ioffset + i : /* stereo: one nibble per channel (assumed, not sure if stereo version actually exists) */ + stream->offset + i/2; /* mono: consecutive nibbles */ + nibble_shift = is_high_first ? + is_stereo ? (!(channel&1) ? 4:0) : (!(i&1) ? 4:0) : /* even = high, odd = low */ + is_stereo ? (!(channel&1) ? 0:4) : (!(i&1) ? 0:4); /* even = low, odd = high */ + + sample = (uint16_t)read_8bit(byte_offset, stream->streamfile); + sample = (sample >> nibble_shift) & 0x0F; + outbuf[sample_count] = sample*0x11*0x100 - 0x8000; + } +} + static int expand_ulaw(uint8_t ulawbyte) { int sign, segment, quantization, new_sample; const int bias = 0x84; @@ -175,5 +221,5 @@ void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac } size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample) { - return bytes / channels / (bits_per_sample/8); + return (bytes * 8) / channels / bits_per_sample; } diff --git a/src/formats.c b/src/formats.c index cabcf782..89416187 100644 --- a/src/formats.c +++ b/src/formats.c @@ -551,11 +551,13 @@ static const coding_info coding_info_list[] = { {coding_PCM16LE, "Little Endian 16-bit PCM"}, {coding_PCM16BE, "Big Endian 16-bit PCM"}, {coding_PCM16_int, "16-bit PCM with 2 byte interleave (block)"}, - {coding_PCM8, "8-bit PCM"}, - {coding_PCM8_int, "8-bit PCM with 1 byte interleave (block)"}, + {coding_PCM8, "8-bit signed PCM"}, + {coding_PCM8_int, "8-bit signed PCM with 1 byte interleave (block)"}, {coding_PCM8_U, "8-bit unsigned PCM"}, {coding_PCM8_U_int, "8-bit unsigned PCM with 1 byte interleave (block)"}, {coding_PCM8_SB, "8-bit PCM with sign bit"}, + {coding_PCM4, "4-bit signed PCM"}, + {coding_PCM4_U, "4-bit unsigned PCM"}, {coding_ULAW, "8-bit u-Law"}, {coding_ULAW_int, "8-bit u-Law with 1 byte interleave (block)"}, {coding_ALAW, "8-bit a-Law"}, diff --git a/src/meta/genh.c b/src/meta/genh.c index acdf9d01..9daaf61d 100644 --- a/src/meta/genh.c +++ b/src/meta/genh.c @@ -31,6 +31,8 @@ typedef enum { FFMPEG = 22, /* any headered FFmpeg format */ AC3 = 23, /* AC3/SPDIF */ PCFX = 24, /* PC-FX ADPCM */ + PCM4 = 25, /* 4bit signed PCM */ + PCM4_U = 26, /* 4bit unsigned PCM */ } genh_type; typedef struct { @@ -114,6 +116,8 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case FFMPEG: coding = coding_FFmpeg; break; #endif case PCFX: coding = coding_PCFX; break; + case PCM4: coding = coding_PCM4; break; + case PCM4_U: coding = coding_PCM4_U; break; default: goto fail; } @@ -137,6 +141,8 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case coding_PCM16BE: case coding_PCM8: case coding_PCM8_U: + case coding_PCM4: + case coding_PCM4_U: case coding_SDX2: case coding_PSX: case coding_PSX_badflags: @@ -185,6 +191,10 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { } } + if (coding == coding_PCM4 || coding == coding_PCM4_U) { + /* high nibble or low nibble first */ + vgmstream->codec_config = genh.codec_mode; + } break; case coding_PCFX: diff --git a/src/meta/txth.c b/src/meta/txth.c index 4dbb72a7..0a5d8819 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -31,6 +31,8 @@ typedef enum { FFMPEG = 22, /* any headered FFmpeg format */ AC3 = 23, /* AC3/SPDIF */ PCFX = 24, /* PC-FX ADPCM */ + PCM4 = 25, /* 4bit signed PCM */ + PCM4_U = 26, /* 4bit unsigned PCM */ } txth_type; typedef struct { @@ -176,6 +178,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case FFMPEG: coding = coding_FFmpeg; break; #endif case PCFX: coding = coding_PCFX; break; + case PCM4: coding = coding_PCM4; break; + case PCM4_U: coding = coding_PCM4_U; break; default: goto fail; } @@ -212,6 +216,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case coding_PCM16BE: case coding_PCM8: case coding_PCM8_U: + case coding_PCM4: + case coding_PCM4_U: case coding_SDX2: case coding_PSX: case coding_PSX_badflags: @@ -260,6 +266,11 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { vgmstream->ch[i].adpcm_step_index = 0x7f; } } + + if (coding == coding_PCM4 || coding == coding_PCM4_U) { + /* high nibble or low nibble first */ + vgmstream->codec_config = txth.codec_mode; + } break; case coding_PCFX: @@ -583,6 +594,8 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char else if (0==strcmp(val,"FFMPEG")) txth->codec = FFMPEG; else if (0==strcmp(val,"AC3")) txth->codec = AC3; else if (0==strcmp(val,"PCFX")) txth->codec = PCFX; + else if (0==strcmp(val,"PCM4")) txth->codec = PCM4; + else if (0==strcmp(val,"PCM4_U")) txth->codec = PCM4_U; else goto fail; } else if (0==strcmp(key,"codec_mode")) { @@ -913,6 +926,9 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { case PCM8_U_int: case PCM8_U: return pcm_bytes_to_samples(bytes, txth->channels, 8); + case PCM4: + case PCM4_U: + return pcm_bytes_to_samples(bytes, txth->channels, 4); case MSADPCM: if (!txth->interleave) return 0; return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels); diff --git a/src/vgmstream.c b/src/vgmstream.c index 8e365381..a71fa9f0 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1550,6 +1550,18 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->channels,vgmstream->samples_into_block,samples_to_do); } break; + case coding_PCM4: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_pcm4(vgmstream,&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do,ch); + } + break; + case coding_PCM4_U: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_pcm4_unsigned(vgmstream, &vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do,ch); + } + break; case coding_ULAW: for (ch = 0; ch < vgmstream->channels; ch++) { diff --git a/src/vgmstream.h b/src/vgmstream.h index 412371dd..5d9bb87b 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -73,6 +73,8 @@ typedef enum { coding_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */ coding_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave (for blocks) */ coding_PCM8_SB, /* 8-bit PCM, sign bit (others are 2's complement) */ + coding_PCM4, /* 4-bit PCM, signed */ + coding_PCM4_U, /* 4-bit PCM, unsigned */ coding_ULAW, /* 8-bit u-Law (non-linear PCM) */ coding_ULAW_int, /* 8-bit u-Law (non-linear PCM) with sample-level interleave (for blocks) */