Skip to content

Commit

Permalink
GBA: Fix ADPCM encoding issues (#474)
Browse files Browse the repository at this point in the history
* ADPCM: Add 8bit debugging, fix limit cycles

* ADPCM: Increase output gain
  • Loading branch information
Aikku93 authored Sep 10, 2023
1 parent d2e363d commit 931079c
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 15 deletions.
48 changes: 34 additions & 14 deletions src/platform/gba/packer/ad4/AD4.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

//! Uncommenting this line writes the output to Debug.sb as 8bit signed PCM
//#define DEBUG_OUTPUT

#include <stdint.h>
#include <stdio.h>

struct AD4State_t {
int32_t zM1, zM2;
int32_t Tap;
int32_t Quant;
int32_t Output;
uint32_t MaxOutputLevel;
Expand All @@ -13,7 +16,6 @@ struct AD4State_t {
void AD4_Init(struct AD4State_t *State) {
State->zM1 = 0;
State->zM2 = 0;
State->Tap = 0;
State->Quant = 0x0800;
State->Output = 0;
State->MaxOutputLevel = 0;
Expand All @@ -28,39 +30,58 @@ uint32_t AD4_EncodeFrame(struct AD4State_t *State, const int16_t *Data) {
uint8_t n;
int32_t zM1 = State->zM1;
int32_t zM2 = State->zM2;
int32_t Tap = State->Tap;
int32_t Quant = State->Quant;
int32_t Output = State->Output;
uint32_t MaxOutputLevel = State->MaxOutputLevel;
uint32_t FrameData = 0;
#ifdef DEBUG_OUTPUT
static FILE *DebugFile = NULL;
if(!DebugFile) DebugFile = fopen("Debug.sb", "ab");
#endif
for(n=0;n<8;n++) {
//! Get input, compute prediction, and quantize residue
//! Note that we minimize error of Output rather than Y, which implies
//! applying the post-filter in the analysis equation to get the residue.
int32_t X = Data[n];
int32_t P = zM1 - zM2;
int32_t R = X - (P + (Tap - (Tap >> 3))); {
int32_t R = X - (P + Output - (Output >> 3)); {
#if 0 //! Lower RMSE, but sounds noisier
R = (2*R + ((R < 0) ? (-Quant) : (+Quant))) / (2*Quant); //! (R + Sign[R]*(Quant/2)) / Quant
#else
R /= Quant;
//! Round positive residues up, and negative residues towards 0.
//! I have no idea why, but this fixes limit cycles.
if(R > 0) R = (R + (Quant-1)) / Quant;
else R = (R + 0) / Quant;
#endif
if(R < -8) R = -8;
if(R > +7) R = +7;
}
int32_t Y = P + R*Quant;

//! Calculate output value and update maximum level
//! Calculate output value and apply limiting
//! Post-filter: Hpost(z) = 1 / Hpre(z) = 1 / (1 - (7/8)z^-1)
Output = Y + Output - (Output >> 3);
int32_t Y;
Output = Output - (Output >> 3);
for(;;) {
Y = P + R*Quant;
if(Output+Y < -32768) {
if(R < +7) R++;
else break;
} else if(Output+Y > +32767) {
if(R > -8) R--;
else break;
} else break;
}
Output += Y;

//! Update maximum level after attempted clipping
uint32_t Level = (uint32_t)((Output < 0) ? (-Output) : (+Output));
if(Level > MaxOutputLevel) MaxOutputLevel = Level;

//! Do the same, but for the encoding tap. This is needed to
//! avoid a limit oscillation on silence from round-off error.
//! Technically, it does mean a different output, but it should
//! be close enough to what we want that it shouldn't matter.
Tap = Y + Tap - ((Tap + 4 - (Tap < 0)) >> 3); //! Y + Round[Tap*7/8]
//! Write debug output
#ifdef DEBUG_OUTPUT
int8_t DebugSample = Output >> 8;
fwrite(&DebugSample, sizeof(int8_t), 1, DebugFile);
#endif

//! Update taps and push residue to frame
zM2 = zM1;
Expand All @@ -73,7 +94,6 @@ uint32_t AD4_EncodeFrame(struct AD4State_t *State, const int16_t *Data) {
}
State->zM1 = zM1;
State->zM2 = zM2;
State->Tap = Tap;
State->Quant = Quant;
State->Output = Output;
State->MaxOutputLevel = MaxOutputLevel;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/gba/packer/tracks/convert.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set DEMO_FOLDER=conv_demo\

for /r %IN_FOLDER% %%i in (*.wav) do (
ffmpeg -v panic -y -i %%i -ar 10512 -f s16le -acodec pcm_s16le temp.raw
ad4 temp.raw %OUT_FOLDER%%%~ni.ad4 -0.8
ad4 temp.raw %OUT_FOLDER%%%~ni.ad4 -0.5
)
del /f temp.raw

Expand Down

0 comments on commit 931079c

Please sign in to comment.