Add audio interpolation helpers

This commit is contained in:
wheremyfoodat 2024-11-09 23:11:19 +02:00
parent 9be353a9b4
commit 69e8e1c2c4
6 changed files with 188 additions and 43 deletions

View file

@ -294,6 +294,7 @@ set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selecto
) )
set(AUDIO_SOURCE_FILES src/core/audio/dsp_core.cpp src/core/audio/null_core.cpp src/core/audio/teakra_core.cpp set(AUDIO_SOURCE_FILES src/core/audio/dsp_core.cpp src/core/audio/null_core.cpp src/core/audio/teakra_core.cpp
src/core/audio/miniaudio_device.cpp src/core/audio/hle_core.cpp src/core/audio/aac_decoder.cpp src/core/audio/miniaudio_device.cpp src/core/audio/hle_core.cpp src/core/audio/aac_decoder.cpp
src/core/audio/audio_interpolation.cpp
) )
set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
@ -334,6 +335,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp
include/PICA/pica_frag_uniforms.hpp include/PICA/shader_gen_types.hpp include/PICA/shader_decompiler.hpp include/PICA/pica_frag_uniforms.hpp include/PICA/shader_gen_types.hpp include/PICA/shader_decompiler.hpp
include/PICA/pica_vert_config.hpp include/sdl_sensors.hpp include/PICA/draw_acceleration.hpp include/renderdoc.hpp include/PICA/pica_vert_config.hpp include/sdl_sensors.hpp include/PICA/draw_acceleration.hpp include/renderdoc.hpp
include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp include/align.hpp include/audio/aac_decoder.hpp include/PICA/pica_simd.hpp include/services/fonts.hpp
include/audio/audio_interpolation.hpp include/audio/hle_mixer.hpp
) )
cmrc_add_resource_library( cmrc_add_resource_library(

View file

@ -0,0 +1,58 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <deque>
#include "audio/hle_mixer.hpp"
#include "helpers.hpp"
namespace Audio::Interpolation {
// A variable length buffer of signed PCM16 stereo samples.
using StereoBuffer16 = std::deque<std::array<s16, 2>>;
using StereoFrame16 = Audio::DSPMixer::StereoFrame<s16>;
struct State {
// Two historical samples.
std::array<s16, 2> xn1 = {}; //< x[n-1]
std::array<s16, 2> xn2 = {}; //< x[n-2]
// Current fractional position.
u64 fposition = 0;
};
/**
* No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
* @param state Interpolation state.
* @param input Input buffer.
* @param rate Stretch factor. Must be a positive non-zero value.
* rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
* @param output The resampled audio buffer.
* @param outputi The index of output to start writing to.
*/
void none(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi);
/**
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
* @param state Interpolation state.
* @param input Input buffer.
* @param rate Stretch factor. Must be a positive non-zero value.
* rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
* @param output The resampled audio buffer.
* @param outputi The index of output to start writing to.
*/
void linear(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi);
/**
* Polyphase interpolation. This is currently stubbed to just perform linear interpolation
* @param state Interpolation state.
* @param input Input buffer.
* @param rate Stretch factor. Must be a positive non-zero value.
* rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
* @param output The resampled audio buffer.
* @param outputi The index of output to start writing to.
*/
void polyphase(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi);
} // namespace Audio::Interpolation

View file

@ -8,54 +8,13 @@
#include "audio/aac.hpp" #include "audio/aac.hpp"
#include "audio/aac_decoder.hpp" #include "audio/aac_decoder.hpp"
#include "audio/audio_interpolation.hpp"
#include "audio/dsp_core.hpp" #include "audio/dsp_core.hpp"
#include "audio/dsp_shared_mem.hpp" #include "audio/dsp_shared_mem.hpp"
#include "audio/hle_mixer.hpp"
#include "memory.hpp" #include "memory.hpp"
namespace Audio { namespace Audio {
using SampleFormat = HLE::SourceConfiguration::Configuration::Format;
using SourceType = HLE::SourceConfiguration::Configuration::MonoOrStereo;
class DSPMixer {
public:
template <typename T, usize channelCount = 1>
using Sample = std::array<T, channelCount>;
template <typename T, usize channelCount>
using Frame = std::array<Sample<T, channelCount>, 160>;
template <typename T>
using MonoFrame = Frame<T, 1>;
template <typename T>
using StereoFrame = Frame<T, 2>;
template <typename T>
using QuadFrame = Frame<T, 4>;
// Internally the DSP uses four channels when mixing.
// Neatly, QuadFrame<s32> means that every sample is a uint32x4 value, which is particularly nice for SIMD mixing
using IntermediateMix = QuadFrame<s32>;
private:
using ChannelFormat = HLE::DspConfiguration::OutputFormat;
// The audio from each DSP voice is converted to quadraphonic and then fed into 3 intermediate mixing stages
// Two of these intermediate mixers (second and third) are used for effects, including custom effects done on the CPU
static constexpr usize mixerStageCount = 3;
public:
ChannelFormat channelFormat = ChannelFormat::Stereo;
std::array<float, mixerStageCount> volumes;
std::array<bool, 2> enableAuxStages;
void reset() {
channelFormat = ChannelFormat::Stereo;
volumes.fill(0.0);
enableAuxStages.fill(false);
}
};
struct DSPSource { struct DSPSource {
// Audio buffer information // Audio buffer information
// https://www.3dbrew.org/wiki/DSP_Memory_Region // https://www.3dbrew.org/wiki/DSP_Memory_Region
@ -89,6 +48,7 @@ namespace Audio {
using SampleBuffer = std::deque<std::array<s16, 2>>; using SampleBuffer = std::deque<std::array<s16, 2>>;
using BufferQueue = std::priority_queue<Buffer>; using BufferQueue = std::priority_queue<Buffer>;
using InterpolationMode = HLE::SourceConfiguration::Configuration::InterpolationMode; using InterpolationMode = HLE::SourceConfiguration::Configuration::InterpolationMode;
using InterpolationState = Audio::Interpolation::State;
DSPMixer::StereoFrame<s16> currentFrame; DSPMixer::StereoFrame<s16> currentFrame;
BufferQueue buffers; BufferQueue buffers;
@ -96,6 +56,7 @@ namespace Audio {
SampleFormat sampleFormat = SampleFormat::ADPCM; SampleFormat sampleFormat = SampleFormat::ADPCM;
SourceType sourceType = SourceType::Stereo; SourceType sourceType = SourceType::Stereo;
InterpolationMode interpolationMode = InterpolationMode::Linear; InterpolationMode interpolationMode = InterpolationMode::Linear;
InterpolationState interpolationState;
// There's one gain configuration for each of the 3 intermediate mixing stages // There's one gain configuration for each of the 3 intermediate mixing stages
// And each gain configuration is composed of 4 gain values, one for each sample in a quad-channel sample // And each gain configuration is composed of 4 gain values, one for each sample in a quad-channel sample

View file

@ -0,0 +1,50 @@
#pragma once
#include <array>
#include "audio/dsp_shared_mem.hpp"
#include "helpers.hpp"
namespace Audio {
using SampleFormat = HLE::SourceConfiguration::Configuration::Format;
using SourceType = HLE::SourceConfiguration::Configuration::MonoOrStereo;
class DSPMixer {
public:
template <typename T, usize channelCount = 1>
using Sample = std::array<T, channelCount>;
template <typename T, usize channelCount>
using Frame = std::array<Sample<T, channelCount>, 160>;
template <typename T>
using MonoFrame = Frame<T, 1>;
template <typename T>
using StereoFrame = Frame<T, 2>;
template <typename T>
using QuadFrame = Frame<T, 4>;
// Internally the DSP uses four channels when mixing.
// Neatly, QuadFrame<s32> means that every sample is a uint32x4 value, which is particularly nice for SIMD mixing
using IntermediateMix = QuadFrame<s32>;
private:
using ChannelFormat = HLE::DspConfiguration::OutputFormat;
// The audio from each DSP voice is converted to quadraphonic and then fed into 3 intermediate mixing stages
// Two of these intermediate mixers (second and third) are used for effects, including custom effects done on the CPU
static constexpr usize mixerStageCount = 3;
public:
ChannelFormat channelFormat = ChannelFormat::Stereo;
std::array<float, mixerStageCount> volumes;
std::array<bool, 2> enableAuxStages;
void reset() {
channelFormat = ChannelFormat::Stereo;
volumes.fill(0.0);
enableAuxStages.fill(false);
}
};
} // namespace Audio

View file

@ -0,0 +1,73 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio/audio_interpolation.hpp"
#include <algorithm>
#include "helpers.hpp"
namespace Audio::Interpolation {
// Calculations are done in fixed point with 24 fractional bits.
// (This is not verified. This was chosen for minimal error.)
static constexpr u64 scaleFactor = 1 << 24;
static constexpr u64 scaleMask = scaleFactor - 1;
/// Here we step over the input in steps of rate, until we consume all of the input.
/// Three adjacent samples are passed to fn each step.
template <typename Function>
static void stepOverSamples(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi, Function fn) {
if (input.empty()) {
return;
}
input.insert(input.begin(), {state.xn2, state.xn1});
const u64 step_size = static_cast<u64>(rate * scaleFactor);
u64 fposition = state.fposition;
usize inputi = 0;
while (outputi < output.size()) {
inputi = static_cast<usize>(fposition / scaleFactor);
if (inputi + 2 >= input.size()) {
inputi = input.size() - 2;
break;
}
u64 fraction = fposition & scaleMask;
output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]);
fposition += step_size;
}
state.xn2 = input[inputi];
state.xn1 = input[inputi + 1];
state.fposition = fposition - inputi * scaleFactor;
input.erase(input.begin(), std::next(input.begin(), inputi + 2));
}
void none(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi) {
stepOverSamples(state, input, rate, output, outputi, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
}
void linear(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi) {
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
stepOverSamples(state, input, rate, output, outputi, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
// This is a saturated subtraction. (Verified by black-box fuzzing.)
s64 delta0 = std::clamp<s64>(x1[0] - x0[0], -32768, 32767);
s64 delta1 = std::clamp<s64>(x1[1] - x0[1], -32768, 32767);
return std::array<s16, 2>{
static_cast<s16>(x0[0] + fraction * delta0 / scaleFactor),
static_cast<s16>(x0[1] + fraction * delta1 / scaleFactor),
};
});
}
void polyphase(State& state, StereoBuffer16& input, float rate, StereoFrame16& output, usize& outputi) {
linear(state, input, rate, output, outputi);
}
} // namespace Audio::Interpolation

View file

@ -732,6 +732,7 @@ namespace Audio {
rateMultiplier = 1.f; rateMultiplier = 1.f;
buffers = {}; buffers = {};
interpolationState = {};
currentSamples.clear(); currentSamples.clear();
gains.fill({}); gains.fill({});