diff --git a/CMakeLists.txt b/CMakeLists.txt index ceebbc1818..25b78d60b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,9 @@ add_library(game_debug STATIC ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} src/dusk/audio/DuskAudioSystem.cpp src/dusk/audio/JASCriticalSection.cpp src/dusk/audio/DuskDsp.hpp - src/dusk/audio/DuskDsp.cpp) + src/dusk/audio/DuskDsp.cpp + src/dusk/audio/Adpcm.cpp + src/dusk/audio/Adpcm.hpp) target_compile_definitions(game_debug PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 $<$:DEBUG=1>) # Make these properties PUBLIC so that the regular game target also sees them. diff --git a/src/dusk/audio/Adpcm.cpp b/src/dusk/audio/Adpcm.cpp new file mode 100644 index 0000000000..f4318d5d59 --- /dev/null +++ b/src/dusk/audio/Adpcm.cpp @@ -0,0 +1,60 @@ +#include "Adpcm.hpp" + +#include +#include + +#include "JSystem/JAudio2/JASAramStream.h" + +// https://github.com/magcius/vgmtrans/blob/8e34ddc2fb43948dc1e1a8759c739a0c1c7b62d7/src/main/formats/JaiSeqScanner.cpp#L489-L531 +// https://github.com/XAYRGA/JaiSeqX/blob/f29c024ec3663503f506aa02bcd503ada6e7d8aa/JaiSeqXLJA/DSP/JAIDSPADPCM4.cs#L86-L87 + +static constexpr u16 Coefficient0[] = { + 0,0x0800,0,0x0400, + 0x1000,0x0e00,0x0c00,0x1200, + 0x1068,0x12c0,0x1400,0x0800, + 0x0400,0xfc00,0xfc00,0xf800 +}; + +static constexpr u16 Coefficient1[] = { + 0,0,0x0800,0x0400,0xf800, + 0xfa00,0xfc00,0xf600,0xf738, + 0xf704,0xf400,0xf800,0xfc00, + 0x0400,0,0, +}; + +constexpr int AdpcmFrameSize = 9; + +static s16 Clamp16(s32 value) { + if (value > 0x7FFF) return 0x7FFF; + if (value < -0x8000) return -0x8000; + return value; +} + +void dusk::audio::Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist2, s16& hist1) { + assert (adpcmLength % AdpcmFrameSize == 0 && "ADPCM must be divisible by frame size"); + + const auto expectedPcmLength = (adpcmLength / AdpcmFrameSize) * 16; + assert (expectedPcmLength <= pcmLength && "PCM output buffer is too small!"); + + for (int i = 0; i < adpcmLength; i += AdpcmFrameSize) { + u8 header = adpcm[i]; + + s32 scale = 1 << (header >> 4); + u8 coefIndex = header & 0xF; + + s16 coef0 = (s16)Coefficient0[coefIndex]; + s16 coef1 = (s16)Coefficient1[coefIndex]; + + for (int sampleIdx = 0; sampleIdx < 16; sampleIdx++) { + u8 adpcmValue = adpcm[i + 1 + sampleIdx / 2]; + u8 unsignedNibble = sampleIdx % 2 == 0 ? adpcmValue >> 4 : adpcmValue & 0xF; + s8 signedNibble = ((s8)(unsignedNibble << 4)) >> 4; + s16 sample = Clamp16((((signedNibble * scale) << 11) + (coef0 * hist1 + coef1 * hist2)) >> 11); + + hist2 = hist1; + hist1 = sample; + + *pcm++ = sample; + } + } +} diff --git a/src/dusk/audio/Adpcm.hpp b/src/dusk/audio/Adpcm.hpp new file mode 100644 index 0000000000..e893d54791 --- /dev/null +++ b/src/dusk/audio/Adpcm.hpp @@ -0,0 +1,14 @@ +#ifndef DUSK_ADPCM_HPP +#define DUSK_ADPCM_HPP + +#include + +namespace dusk::audio { + constexpr u32 Adpcm4FrameSize = 9; + constexpr u32 AdpcmSampleCount = 16; + constexpr u32 Adpcm2FrameSize = 5; + + void Adpcm4ToPcm16(const u8* adpcm, size_t adpcmLength, s16* pcm, size_t pcmLength, s16& hist1, s16& hist0); +} + +#endif // DUSK_ADPCM_HPP