Merge pull request #1572 from MerryMage/audio-filter
DSP: Implement audio filters (simple, biquad)
This commit is contained in:
commit
644fbbeb13
|
@ -2,13 +2,16 @@ set(SRCS
|
||||||
audio_core.cpp
|
audio_core.cpp
|
||||||
codec.cpp
|
codec.cpp
|
||||||
hle/dsp.cpp
|
hle/dsp.cpp
|
||||||
|
hle/filter.cpp
|
||||||
hle/pipe.cpp
|
hle/pipe.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
audio_core.h
|
audio_core.h
|
||||||
codec.h
|
codec.h
|
||||||
|
hle/common.h
|
||||||
hle/dsp.h
|
hle/dsp.h
|
||||||
|
hle/filter.h
|
||||||
hle/pipe.h
|
hle/pipe.h
|
||||||
sink.h
|
sink.h
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "audio_core/audio_core.h"
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace DSP {
|
||||||
|
namespace HLE {
|
||||||
|
|
||||||
|
/// The final output to the speakers is stereo. Preprocessing output in Source is also stereo.
|
||||||
|
using StereoFrame16 = std::array<std::array<s16, 2>, AudioCore::samples_per_frame>;
|
||||||
|
|
||||||
|
/// The DSP is quadraphonic internally.
|
||||||
|
using QuadFrame32 = std::array<std::array<s32, 4>, AudioCore::samples_per_frame>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This performs the filter operation defined by FilterT::ProcessSample on the frame in-place.
|
||||||
|
* FilterT::ProcessSample is called sequentially on the samples.
|
||||||
|
*/
|
||||||
|
template<typename FrameT, typename FilterT>
|
||||||
|
void FilterFrame(FrameT& frame, FilterT& filter) {
|
||||||
|
std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const typename FrameT::value_type& sample) {
|
||||||
|
return filter.ProcessSample(sample);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HLE
|
||||||
|
} // namespace DSP
|
|
@ -126,8 +126,11 @@ struct SourceConfiguration {
|
||||||
union {
|
union {
|
||||||
u32_le dirty_raw;
|
u32_le dirty_raw;
|
||||||
|
|
||||||
|
BitField<0, 1, u32_le> format_dirty;
|
||||||
|
BitField<1, 1, u32_le> mono_or_stereo_dirty;
|
||||||
BitField<2, 1, u32_le> adpcm_coefficients_dirty;
|
BitField<2, 1, u32_le> adpcm_coefficients_dirty;
|
||||||
BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
|
BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
|
||||||
|
BitField<4, 1, u32_le> partial_reset_flag;
|
||||||
|
|
||||||
BitField<16, 1, u32_le> enable_dirty;
|
BitField<16, 1, u32_le> enable_dirty;
|
||||||
BitField<17, 1, u32_le> interpolation_dirty;
|
BitField<17, 1, u32_le> interpolation_dirty;
|
||||||
|
@ -143,8 +146,7 @@ struct SourceConfiguration {
|
||||||
BitField<27, 1, u32_le> gain_2_dirty;
|
BitField<27, 1, u32_le> gain_2_dirty;
|
||||||
BitField<28, 1, u32_le> sync_dirty;
|
BitField<28, 1, u32_le> sync_dirty;
|
||||||
BitField<29, 1, u32_le> reset_flag;
|
BitField<29, 1, u32_le> reset_flag;
|
||||||
|
BitField<30, 1, u32_le> embedded_buffer_dirty;
|
||||||
BitField<31, 1, u32_le> embedded_buffer_dirty;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gain control
|
// Gain control
|
||||||
|
@ -175,7 +177,8 @@ struct SourceConfiguration {
|
||||||
/**
|
/**
|
||||||
* This is the simplest normalized first-order digital recursive filter.
|
* This is the simplest normalized first-order digital recursive filter.
|
||||||
* The transfer function of this filter is:
|
* The transfer function of this filter is:
|
||||||
* H(z) = b0 / (1 + a1 z^-1)
|
* H(z) = b0 / (1 - a1 z^-1)
|
||||||
|
* Note the feedbackward coefficient is negated.
|
||||||
* Values are signed fixed point with 15 fractional bits.
|
* Values are signed fixed point with 15 fractional bits.
|
||||||
*/
|
*/
|
||||||
struct SimpleFilter {
|
struct SimpleFilter {
|
||||||
|
@ -192,11 +195,11 @@ struct SourceConfiguration {
|
||||||
* Values are signed fixed point with 14 fractional bits.
|
* Values are signed fixed point with 14 fractional bits.
|
||||||
*/
|
*/
|
||||||
struct BiquadFilter {
|
struct BiquadFilter {
|
||||||
s16_le b0;
|
|
||||||
s16_le b1;
|
|
||||||
s16_le b2;
|
|
||||||
s16_le a1;
|
|
||||||
s16_le a2;
|
s16_le a2;
|
||||||
|
s16_le a1;
|
||||||
|
s16_le b2;
|
||||||
|
s16_le b1;
|
||||||
|
s16_le b0;
|
||||||
};
|
};
|
||||||
|
|
||||||
union {
|
union {
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "audio_core/hle/common.h"
|
||||||
|
#include "audio_core/hle/dsp.h"
|
||||||
|
#include "audio_core/hle/filter.h"
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/math_util.h"
|
||||||
|
|
||||||
|
namespace DSP {
|
||||||
|
namespace HLE {
|
||||||
|
|
||||||
|
void SourceFilters::Reset() {
|
||||||
|
Enable(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceFilters::Enable(bool simple, bool biquad) {
|
||||||
|
simple_filter_enabled = simple;
|
||||||
|
biquad_filter_enabled = biquad;
|
||||||
|
|
||||||
|
if (!simple)
|
||||||
|
simple_filter.Reset();
|
||||||
|
if (!biquad)
|
||||||
|
biquad_filter.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceFilters::Configure(SourceConfiguration::Configuration::SimpleFilter config) {
|
||||||
|
simple_filter.Configure(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceFilters::Configure(SourceConfiguration::Configuration::BiquadFilter config) {
|
||||||
|
biquad_filter.Configure(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceFilters::ProcessFrame(StereoFrame16& frame) {
|
||||||
|
if (!simple_filter_enabled && !biquad_filter_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (simple_filter_enabled) {
|
||||||
|
FilterFrame(frame, simple_filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (biquad_filter_enabled) {
|
||||||
|
FilterFrame(frame, biquad_filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleFilter
|
||||||
|
|
||||||
|
void SourceFilters::SimpleFilter::Reset() {
|
||||||
|
y1.fill(0);
|
||||||
|
// Configure as passthrough.
|
||||||
|
a1 = 0;
|
||||||
|
b0 = 1 << 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) {
|
||||||
|
a1 = config.a1;
|
||||||
|
b0 = config.b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<s16, 2> SourceFilters::SimpleFilter::ProcessSample(const std::array<s16, 2>& x0) {
|
||||||
|
std::array<s16, 2> y0;
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
const s32 tmp = (b0 * x0[i] + a1 * y1[i]) >> 15;
|
||||||
|
y0[i] = MathUtil::Clamp(tmp, -32768, 32767);
|
||||||
|
}
|
||||||
|
|
||||||
|
y1 = y0;
|
||||||
|
|
||||||
|
return y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BiquadFilter
|
||||||
|
|
||||||
|
void SourceFilters::BiquadFilter::Reset() {
|
||||||
|
x1.fill(0);
|
||||||
|
x2.fill(0);
|
||||||
|
y1.fill(0);
|
||||||
|
y2.fill(0);
|
||||||
|
// Configure as passthrough.
|
||||||
|
a1 = a2 = b1 = b2 = 0;
|
||||||
|
b0 = 1 << 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) {
|
||||||
|
a1 = config.a1;
|
||||||
|
a2 = config.a2;
|
||||||
|
b0 = config.b0;
|
||||||
|
b1 = config.b1;
|
||||||
|
b2 = config.b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s16, 2>& x0) {
|
||||||
|
std::array<s16, 2> y0;
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
const s32 tmp = (b0 * x0[i] + b1 * x1[i] + b2 * x2[i] + a1 * y1[i] + a2 * y2[i]) >> 14;
|
||||||
|
y0[i] = MathUtil::Clamp(tmp, -32768, 32767);
|
||||||
|
}
|
||||||
|
|
||||||
|
x2 = x1;
|
||||||
|
x1 = x0;
|
||||||
|
y2 = y1;
|
||||||
|
y1 = y0;
|
||||||
|
|
||||||
|
return y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HLE
|
||||||
|
} // namespace DSP
|
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright 2016 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "audio_core/hle/common.h"
|
||||||
|
#include "audio_core/hle/dsp.h"
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace DSP {
|
||||||
|
namespace HLE {
|
||||||
|
|
||||||
|
/// Preprocessing filters. There is an independent set of filters for each Source.
|
||||||
|
class SourceFilters final {
|
||||||
|
SourceFilters() { Reset(); }
|
||||||
|
|
||||||
|
/// Reset internal state.
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable filters
|
||||||
|
* See also: SourceConfiguration::Configuration::simple_filter_enabled,
|
||||||
|
* SourceConfiguration::Configuration::biquad_filter_enabled.
|
||||||
|
* @param simple If true, enables the simple filter. If false, disables it.
|
||||||
|
* @param simple If true, enables the biquad filter. If false, disables it.
|
||||||
|
*/
|
||||||
|
void Enable(bool simple, bool biquad);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure simple filter.
|
||||||
|
* @param config Configuration from DSP shared memory.
|
||||||
|
*/
|
||||||
|
void Configure(SourceConfiguration::Configuration::SimpleFilter config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure biquad filter.
|
||||||
|
* @param config Configuration from DSP shared memory.
|
||||||
|
*/
|
||||||
|
void Configure(SourceConfiguration::Configuration::BiquadFilter config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a frame in-place.
|
||||||
|
* @param frame Audio samples to process. Modified in-place.
|
||||||
|
*/
|
||||||
|
void ProcessFrame(StereoFrame16& frame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool simple_filter_enabled;
|
||||||
|
bool biquad_filter_enabled;
|
||||||
|
|
||||||
|
struct SimpleFilter {
|
||||||
|
SimpleFilter() { Reset(); }
|
||||||
|
|
||||||
|
/// Resets internal state.
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures this filter with application settings.
|
||||||
|
* @param config Configuration from DSP shared memory.
|
||||||
|
*/
|
||||||
|
void Configure(SourceConfiguration::Configuration::SimpleFilter config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a single stereo PCM16 sample.
|
||||||
|
* @param x0 Input sample
|
||||||
|
* @return Output sample
|
||||||
|
*/
|
||||||
|
std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Configuration
|
||||||
|
s32 a1, b0;
|
||||||
|
// Internal state
|
||||||
|
std::array<s16, 2> y1;
|
||||||
|
} simple_filter;
|
||||||
|
|
||||||
|
struct BiquadFilter {
|
||||||
|
BiquadFilter() { Reset(); }
|
||||||
|
|
||||||
|
/// Resets internal state.
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures this filter with application settings.
|
||||||
|
* @param config Configuration from DSP shared memory.
|
||||||
|
*/
|
||||||
|
void Configure(SourceConfiguration::Configuration::BiquadFilter config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a single stereo PCM16 sample.
|
||||||
|
* @param x0 Input sample
|
||||||
|
* @return Output sample
|
||||||
|
*/
|
||||||
|
std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Configuration
|
||||||
|
s32 a1, a2, b0, b1, b2;
|
||||||
|
// Internal state
|
||||||
|
std::array<s16, 2> x1;
|
||||||
|
std::array<s16, 2> x2;
|
||||||
|
std::array<s16, 2> y1;
|
||||||
|
std::array<s16, 2> y2;
|
||||||
|
} biquad_filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HLE
|
||||||
|
} // namespace DSP
|
Reference in New Issue