1+ using Fmod5Sharp . FmodTypes ;
2+ using Fmod5Sharp . Util ;
3+ using NAudio . Wave ;
4+ using System ;
5+ using System . Buffers . Binary ;
6+ using System . IO ;
7+
8+ namespace Fmod5Sharp . CodecRebuilders ;
9+
10+ // Credits: https://github.com/vgmstream/vgmstream/blob/master/src/coding/fadpcm_decoder.c
11+ public class FmodFadPcmRebuilder
12+ {
13+ private static readonly short [ , ] FadpcmCoefs = {
14+ { 0 , 0 } ,
15+ { 60 , 0 } ,
16+ { 122 , 60 } ,
17+ { 115 , 52 } ,
18+ { 98 , 55 } ,
19+ { 0 , 0 } ,
20+ { 0 , 0 } ,
21+ { 0 , 0 }
22+ } ;
23+
24+ public static short [ ] DecodeFadpcm ( FmodSample sample )
25+ {
26+ const int FrameSize = 0x8C ;
27+ const int SamplesPerFrame = ( FrameSize - 0x0C ) * 2 ;
28+
29+ ReadOnlySpan < byte > sampleBytes = sample . SampleBytes ;
30+ int numChannels = sample . Metadata . NumChannels ;
31+ int totalFrames = sampleBytes . Length / FrameSize ;
32+
33+ // Total samples across all channels
34+ short [ ] outputBuffer = new short [ totalFrames * SamplesPerFrame ] ;
35+ Span < short > outputSpan = outputBuffer ;
36+
37+ int [ ] hist1 = new int [ numChannels ] ;
38+ int [ ] hist2 = new int [ numChannels ] ;
39+ for ( int f = 0 ; f < totalFrames ; f ++ )
40+ {
41+ int channel = f % numChannels ;
42+ int frameOffset = f * FrameSize ;
43+
44+ ReadOnlySpan < byte > frameSpan = sampleBytes . Slice ( frameOffset , FrameSize ) ;
45+
46+ // Parse Header
47+ uint coefsLookup = BinaryPrimitives . ReadUInt32LittleEndian ( frameSpan [ ..4 ] ) ;
48+ uint shiftsLookup = BinaryPrimitives . ReadUInt32LittleEndian ( frameSpan [ 0x04 ..] ) ;
49+ hist1 [ channel ] = BinaryPrimitives . ReadInt16LittleEndian ( frameSpan [ 0x08 ..] ) ;
50+ hist2 [ channel ] = BinaryPrimitives . ReadInt16LittleEndian ( frameSpan [ 0x0A ..] ) ;
51+
52+ int frameIndexInChannel = f / numChannels ;
53+ int frameBaseOutIndex = ( frameIndexInChannel * SamplesPerFrame * numChannels ) + channel ;
54+
55+ // Decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2
56+ for ( int i = 0 ; i < 8 ; i ++ )
57+ {
58+ // Each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2)
59+ int index = ( int ) ( ( coefsLookup >> ( i * 4 ) ) & 0x0F ) % 0x07 ;
60+ int shift = ( int ) ( ( shiftsLookup >> ( i * 4 ) ) & 0x0F ) ;
61+
62+ int coef1 = FadpcmCoefs [ index , 0 ] ;
63+ int coef2 = FadpcmCoefs [ index , 1 ] ;
64+ int finalShift = 22 - shift ; // Pre-adjust for 32b sign extend
65+
66+ for ( int j = 0 ; j < 4 ; j ++ )
67+ {
68+ uint nibbles = BinaryPrimitives . ReadUInt32LittleEndian ( frameSpan [ ( 0x0C + ( 0x10 * i ) + ( 0x04 * j ) ) ..] ) ;
69+
70+ for ( int k = 0 ; k < 8 ; k ++ )
71+ {
72+ int sampleValue = ( int ) ( ( nibbles >> ( k * 4 ) ) & 0x0F ) ;
73+ sampleValue = ( sampleValue << 28 ) >> finalShift ; // 32b sign extend + scale
74+ sampleValue = ( sampleValue - ( hist2 [ channel ] * coef2 ) + ( hist1 [ channel ] * coef1 ) ) >> 6 ;
75+
76+ short finalSample = Utils . ClampToShort ( sampleValue ) ;
77+
78+ int outIndex = frameBaseOutIndex + ( ( i * 32 + j * 8 + k ) * numChannels ) ;
79+
80+ if ( outIndex < outputSpan . Length )
81+ outputSpan [ outIndex ] = finalSample ;
82+
83+ hist2 [ channel ] = hist1 [ channel ] ;
84+ hist1 [ channel ] = finalSample ;
85+ }
86+ }
87+ }
88+ }
89+
90+ return outputBuffer ;
91+ }
92+
93+ public static byte [ ] Rebuild ( FmodSample sample )
94+ {
95+ var format = new WaveFormat ( sample . Metadata . Frequency , 16 , sample . Metadata . NumChannels ) ;
96+
97+ using var stream = new MemoryStream ( ) ;
98+ using ( var writer = new WaveFileWriter ( stream , format ) )
99+ {
100+ short [ ] pcmSamples = DecodeFadpcm ( sample ) ;
101+ writer . WriteSamples ( pcmSamples , 0 , pcmSamples . Length ) ;
102+ }
103+
104+ return stream . ToArray ( ) ;
105+ }
106+ }
0 commit comments