interpolate: Interpolate on a frame-by-frame basis

This commit is contained in:
MerryMage 2017-08-03 12:22:51 +01:00
parent 035716d57b
commit 933508e2a2
3 changed files with 74 additions and 88 deletions

View file

@ -244,17 +244,27 @@ void Source::GenerateFrame() {
break; break;
} }
const size_t size_to_copy = switch (state.interpolation_mode) {
std::min(state.current_buffer.size(), current_frame.size() - frame_position); case InterpolationMode::None:
AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier,
std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, current_frame, frame_position);
current_frame.begin() + frame_position); break;
state.current_buffer.erase(state.current_buffer.begin(), case InterpolationMode::Linear:
state.current_buffer.begin() + size_to_copy); AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
current_frame, frame_position);
frame_position += size_to_copy; break;
state.next_sample_number += static_cast<u32>(size_to_copy); case InterpolationMode::Polyphase:
// TODO(merry): Implement polyphase interpolation
LOG_DEBUG(Audio_DSP, "Polyphase interpolation unimplemented; falling back to linear");
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
current_frame, frame_position);
break;
default:
UNIMPLEMENTED();
break;
}
} }
state.next_sample_number += frame_position;
state.filters.ProcessFrame(current_frame); state.filters.ProcessFrame(current_frame);
} }
@ -305,25 +315,6 @@ bool Source::DequeueBuffer() {
return true; return true;
} }
switch (state.interpolation_mode) {
case InterpolationMode::None:
state.current_buffer =
AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
break;
case InterpolationMode::Linear:
state.current_buffer =
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
break;
case InterpolationMode::Polyphase:
// TODO(merry): Implement polyphase interpolation
state.current_buffer =
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
break;
default:
UNIMPLEMENTED();
break;
}
// the first playthrough starts at play_position, loops start at the beginning of the buffer // the first playthrough starts at play_position, loops start at the beginning of the buffer
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
state.next_sample_number = state.current_sample_number; state.next_sample_number = state.current_sample_number;

View file

@ -13,74 +13,64 @@ namespace AudioInterp {
constexpr u64 scale_factor = 1 << 24; constexpr u64 scale_factor = 1 << 24;
constexpr u64 scale_mask = scale_factor - 1; constexpr u64 scale_mask = scale_factor - 1;
/// Here we step over the input in steps of rate_multiplier, until we consume all of the input. /// 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. /// Three adjacent samples are passed to fn each step.
template <typename Function> template <typename Function>
static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, static void StepOverSamples(State& state, StereoBuffer16& input, float rate,
float rate_multiplier, Function fn) { DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) {
ASSERT(rate_multiplier > 0); ASSERT(rate > 0);
if (input.size() < 2) if (input.empty())
return {}; return;
StereoBuffer16 output; input.insert(input.begin(), {state.xn2, state.xn1});
output.reserve(static_cast<size_t>(input.size() / rate_multiplier));
u64 step_size = static_cast<u64>(rate_multiplier * scale_factor); const u64 step_size = static_cast<u64>(rate * scale_factor);
u64 fposition = state.fposition;
size_t inputi = 0;
u64 fposition = 0; while (outputi < output.size()) {
const u64 max_fposition = input.size() * scale_factor; inputi = static_cast<size_t>(fposition / scale_factor);
if (inputi + 2 >= input.size()) {
inputi = input.size() - 2;
break;
}
while (fposition < 1 * scale_factor) {
u64 fraction = fposition & scale_mask; u64 fraction = fposition & scale_mask;
output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]);
output.push_back(fn(fraction, state.xn2, state.xn1, input[0]));
fposition += step_size; fposition += step_size;
} }
while (fposition < 2 * scale_factor) { state.xn2 = input[inputi];
u64 fraction = fposition & scale_mask; state.xn1 = input[inputi + 1];
state.fposition = fposition - inputi * scale_factor;
output.push_back(fn(fraction, state.xn1, input[0], input[1])); input.erase(input.begin(), input.begin() + inputi + 2);
fposition += step_size;
}
while (fposition < max_fposition) {
u64 fraction = fposition & scale_mask;
size_t index = static_cast<size_t>(fposition / scale_factor);
output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index]));
fposition += step_size;
}
state.xn2 = input[input.size() - 2];
state.xn1 = input[input.size() - 1];
return output;
} }
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) { void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
return StepOverSamples( size_t& outputi) {
state, input, rate_multiplier, StepOverSamples(
state, input, rate, output, outputi,
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; }); [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
} }
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) { void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
size_t& outputi) {
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware. // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
return StepOverSamples(state, input, rate_multiplier, StepOverSamples(state, input, rate, output, outputi,
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
// This is a saturated subtraction. (Verified by black-box fuzzing.) // This is a saturated subtraction. (Verified by black-box fuzzing.)
s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767); s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767); s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
return std::array<s16, 2>{ return std::array<s16, 2>{
static_cast<s16>(x0[0] + fraction * delta0 / scale_factor), static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor), static_cast<s16>(x0[1] + fraction * delta1 / scale_factor),
}; };
}); });
} }
} // namespace AudioInterp } // namespace AudioInterp

View file

@ -6,6 +6,7 @@
#include <array> #include <array>
#include <vector> #include <vector>
#include "audio_core/hle/common.h"
#include "common/common_types.h" #include "common/common_types.h"
namespace AudioInterp { namespace AudioInterp {
@ -14,31 +15,35 @@ namespace AudioInterp {
using StereoBuffer16 = std::vector<std::array<s16, 2>>; using StereoBuffer16 = std::vector<std::array<s16, 2>>;
struct State { struct State {
// Two historical samples. /// Two historical samples.
std::array<s16, 2> xn1 = {}; ///< x[n-1] std::array<s16, 2> xn1 = {}; ///< x[n-1]
std::array<s16, 2> xn2 = {}; ///< x[n-2] 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. * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
* @param state Interpolation state. * @param state Interpolation state.
* @param input Input buffer. * @param input Input buffer.
* @param rate_multiplier Stretch factor. Must be a positive non-zero value. * @param rate Stretch factor. Must be a positive non-zero value.
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 * rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
* performs upsampling. * @param output The resampled audio buffer.
* @return The resampled audio buffer. * @param outputi The index of output to start writing to.
*/ */
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier); void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
size_t& outputi);
/** /**
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
* @param state Interpolation state. * @param state Interpolation state.
* @param input Input buffer. * @param input Input buffer.
* @param rate_multiplier Stretch factor. Must be a positive non-zero value. * @param rate Stretch factor. Must be a positive non-zero value.
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 * rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
* performs upsampling. * @param output The resampled audio buffer.
* @return The resampled audio buffer. * @param outputi The index of output to start writing to.
*/ */
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier); void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
size_t& outputi);
} // namespace AudioInterp } // namespace AudioInterp