mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 07:05:40 +12:00
Add audio interpolation helpers
This commit is contained in:
parent
9be353a9b4
commit
69e8e1c2c4
6 changed files with 188 additions and 43 deletions
|
@ -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(
|
||||||
|
|
58
include/audio/audio_interpolation.hpp
Normal file
58
include/audio/audio_interpolation.hpp
Normal 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
|
|
@ -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
|
||||||
|
|
50
include/audio/hle_mixer.hpp
Normal file
50
include/audio/hle_mixer.hpp
Normal 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
|
73
src/core/audio/audio_interpolation.cpp
Normal file
73
src/core/audio/audio_interpolation.cpp
Normal 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
|
|
@ -732,6 +732,7 @@ namespace Audio {
|
||||||
rateMultiplier = 1.f;
|
rateMultiplier = 1.f;
|
||||||
|
|
||||||
buffers = {};
|
buffers = {};
|
||||||
|
interpolationState = {};
|
||||||
currentSamples.clear();
|
currentSamples.clear();
|
||||||
|
|
||||||
gains.fill({});
|
gains.fill({});
|
||||||
|
|
Loading…
Add table
Reference in a new issue