diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 1d685c69..95b98c2a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -37,6 +37,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/audio.cpp ${source_DIR}/skyline/audio/track.cpp ${source_DIR}/skyline/audio/resampler.cpp + ${source_DIR}/skyline/audio/adpcm_decoder.cpp ${source_DIR}/skyline/gpu.cpp ${source_DIR}/skyline/gpu/texture.cpp ${source_DIR}/skyline/os.cpp diff --git a/app/src/main/cpp/skyline/audio/adpcm_decoder.cpp b/app/src/main/cpp/skyline/audio/adpcm_decoder.cpp new file mode 100644 index 00000000..9947ce59 --- /dev/null +++ b/app/src/main/cpp/skyline/audio/adpcm_decoder.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include "common.h" +#include "adpcm_decoder.h" + +namespace skyline::audio { + AdpcmDecoder::AdpcmDecoder(const std::vector> &coefficients) : coefficients(coefficients) {} + + std::vector AdpcmDecoder::Decode(const std::vector &adpcmData) { + constexpr size_t BytesPerFrame = 0x8; //!< The number of bytes in a single frame + constexpr size_t SamplesPerFrame = 0xe; //!< The number of samples in a single frame + + size_t remainingSamples = (adpcmData.size() / BytesPerFrame) * SamplesPerFrame; + + std::vector output; + output.reserve(remainingSamples); + + size_t inputOffset{}; + + while (inputOffset < adpcmData.size()) { + FrameHeader header(adpcmData[inputOffset++]); + + size_t frameSamples = std::min(SamplesPerFrame, remainingSamples); + + i32 ctx{}; + + for (size_t index = 0; index < frameSamples; index++) { + i32 sample{}; + + if (index & 1) { + sample = (ctx << 28) >> 28; + } else { + ctx = adpcmData[inputOffset++]; + sample = (ctx << 24) >> 28; + } + + i32 prediction = history[0] * coefficients[header.coefficientIndex][0] + history[1] * coefficients[header.coefficientIndex][1]; + sample = (sample * (0x800 << header.scale) + prediction + 0x400) >> 11; + + auto saturated = audio::Saturate(sample); + output.push_back(saturated); + history[1] = history[0]; + history[0] = saturated; + } + + remainingSamples -= frameSamples; + } + + return output; + } +} diff --git a/app/src/main/cpp/skyline/audio/adpcm_decoder.h b/app/src/main/cpp/skyline/audio/adpcm_decoder.h new file mode 100644 index 00000000..0c9e2777 --- /dev/null +++ b/app/src/main/cpp/skyline/audio/adpcm_decoder.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include + +namespace skyline::audio { + /** + * @brief The AdpcmDecoder class handles decoding single channel adaptive differential PCM data + */ + class AdpcmDecoder { + private: + /** + * @brief This struct holds a single ADPCM frame header + */ + union FrameHeader { + struct { + u8 scale : 4; //!< The scale factor for this frame + u8 coefficientIndex : 3; //!< The index of the coeffcients to use for this frame + u8 _pad_ :1; + }; + u8 raw; //!< The raw value + + FrameHeader(u8 raw) : raw(raw) {} + }; + static_assert(sizeof(FrameHeader) == 0x1); + + std::array history{}; //!< This contains the history for decoding the ADPCM stream + std::vector> coefficients; //!< This contains the coefficients for decoding the ADPCM stream + + public: + AdpcmDecoder(const std::vector> &coefficients); + + /** + * @brief This decodes a buffer of ADPCM data into I16 PCM + * @param adpcmData A buffer containing the raw ADPCM data + * @return A buffer containing decoded single channel I16 PCM data + */ + std::vector Decode(const std::vector &adpcmData); + }; +} diff --git a/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.cpp b/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.cpp index a4727a74..a7f887d7 100644 --- a/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.cpp +++ b/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.cpp @@ -30,17 +30,24 @@ namespace skyline::service::audio::IAudioRenderer { return; if (input.firstUpdate) { - if (input.format != skyline::audio::AudioFormat::Int16) + if (input.format != skyline::audio::AudioFormat::Int16 && input.format != skyline::audio::AudioFormat::ADPCM) throw exception("Unsupported voice PCM format: {}", input.format); format = input.format; sampleRate = input.sampleRate; - if (input.channelCount > 2) + if (input.channelCount > (input.format == skyline::audio::AudioFormat::ADPCM ? 1 : 2)) throw exception("Unsupported voice channel count: {}", input.channelCount); channelCount = static_cast(input.channelCount); + if (input.format == skyline::audio::AudioFormat::ADPCM) { + std::vector> adpcmCoefficients(input.adpcmCoeffsSize / (sizeof(u16) * 2)); + state.process->ReadMemory(adpcmCoefficients.data(), input.adpcmCoeffsPosition, input.adpcmCoeffsSize); + + adpcmDecoder = skyline::audio::AdpcmDecoder(adpcmCoefficients); + } + SetWaveBufferIndex(static_cast(input.baseWaveBufferIndex)); } @@ -60,6 +67,12 @@ namespace skyline::service::audio::IAudioRenderer { samples.resize(currentBuffer.size / sizeof(i16)); state.process->ReadMemory(samples.data(), currentBuffer.address, currentBuffer.size); break; + case skyline::audio::AudioFormat::ADPCM: { + std::vector adpcmData(currentBuffer.size); + state.process->ReadMemory(adpcmData.data(), currentBuffer.address, currentBuffer.size); + samples = adpcmDecoder->Decode(adpcmData); + } + break; default: throw exception("Unsupported PCM format used by Voice: {}", format); } diff --git a/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.h b/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.h index 2bd0c821..4f51310e 100644 --- a/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.h +++ b/app/src/main/cpp/skyline/services/audio/IAudioRenderer/voice.h @@ -5,6 +5,7 @@ #include #include