diff options
| author | 2016-03-30 20:17:52 -0400 | |
|---|---|---|
| committer | 2016-03-30 20:17:52 -0400 | |
| commit | 644fbbeb13db8147ce74cfbde80f62ee492c201a (patch) | |
| tree | 0c9e5ee0a8e9bb98cc2df13d5584f756c7cfeea2 /src | |
| parent | Merge pull request #1611 from ObsidianX/cfg-common-fix (diff) | |
| parent | DSP: Implement audio filters (simple, biquad) (diff) | |
| download | yuzu-644fbbeb13db8147ce74cfbde80f62ee492c201a.tar.gz yuzu-644fbbeb13db8147ce74cfbde80f62ee492c201a.tar.xz yuzu-644fbbeb13db8147ce74cfbde80f62ee492c201a.zip | |
Merge pull request #1572 from MerryMage/audio-filter
DSP: Implement audio filters (simple, biquad)
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio_core/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/audio_core/hle/common.h | 35 | ||||
| -rw-r--r-- | src/audio_core/hle/dsp.h | 17 | ||||
| -rw-r--r-- | src/audio_core/hle/filter.cpp | 115 | ||||
| -rw-r--r-- | src/audio_core/hle/filter.h | 112 |
5 files changed, 275 insertions, 7 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index c4bad8cb0..869da5e83 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt | |||
| @@ -2,13 +2,16 @@ set(SRCS | |||
| 2 | audio_core.cpp | 2 | audio_core.cpp |
| 3 | codec.cpp | 3 | codec.cpp |
| 4 | hle/dsp.cpp | 4 | hle/dsp.cpp |
| 5 | hle/filter.cpp | ||
| 5 | hle/pipe.cpp | 6 | hle/pipe.cpp |
| 6 | ) | 7 | ) |
| 7 | 8 | ||
| 8 | set(HEADERS | 9 | set(HEADERS |
| 9 | audio_core.h | 10 | audio_core.h |
| 10 | codec.h | 11 | codec.h |
| 12 | hle/common.h | ||
| 11 | hle/dsp.h | 13 | hle/dsp.h |
| 14 | hle/filter.h | ||
| 12 | hle/pipe.h | 15 | hle/pipe.h |
| 13 | sink.h | 16 | sink.h |
| 14 | ) | 17 | ) |
diff --git a/src/audio_core/hle/common.h b/src/audio_core/hle/common.h new file mode 100644 index 000000000..37d441eb2 --- /dev/null +++ b/src/audio_core/hle/common.h | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <algorithm> | ||
| 8 | #include <array> | ||
| 9 | |||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | |||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace DSP { | ||
| 15 | namespace HLE { | ||
| 16 | |||
| 17 | /// The final output to the speakers is stereo. Preprocessing output in Source is also stereo. | ||
| 18 | using StereoFrame16 = std::array<std::array<s16, 2>, AudioCore::samples_per_frame>; | ||
| 19 | |||
| 20 | /// The DSP is quadraphonic internally. | ||
| 21 | using QuadFrame32 = std::array<std::array<s32, 4>, AudioCore::samples_per_frame>; | ||
| 22 | |||
| 23 | /** | ||
| 24 | * This performs the filter operation defined by FilterT::ProcessSample on the frame in-place. | ||
| 25 | * FilterT::ProcessSample is called sequentially on the samples. | ||
| 26 | */ | ||
| 27 | template<typename FrameT, typename FilterT> | ||
| 28 | void FilterFrame(FrameT& frame, FilterT& filter) { | ||
| 29 | std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const typename FrameT::value_type& sample) { | ||
| 30 | return filter.ProcessSample(sample); | ||
| 31 | }); | ||
| 32 | } | ||
| 33 | |||
| 34 | } // namespace HLE | ||
| 35 | } // namespace DSP | ||
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h index 376436c29..c15ef0b7a 100644 --- a/src/audio_core/hle/dsp.h +++ b/src/audio_core/hle/dsp.h | |||
| @@ -126,8 +126,11 @@ struct SourceConfiguration { | |||
| 126 | union { | 126 | union { |
| 127 | u32_le dirty_raw; | 127 | u32_le dirty_raw; |
| 128 | 128 | ||
| 129 | BitField<0, 1, u32_le> format_dirty; | ||
| 130 | BitField<1, 1, u32_le> mono_or_stereo_dirty; | ||
| 129 | BitField<2, 1, u32_le> adpcm_coefficients_dirty; | 131 | BitField<2, 1, u32_le> adpcm_coefficients_dirty; |
| 130 | BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued. | 132 | BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued. |
| 133 | BitField<4, 1, u32_le> partial_reset_flag; | ||
| 131 | 134 | ||
| 132 | BitField<16, 1, u32_le> enable_dirty; | 135 | BitField<16, 1, u32_le> enable_dirty; |
| 133 | BitField<17, 1, u32_le> interpolation_dirty; | 136 | BitField<17, 1, u32_le> interpolation_dirty; |
| @@ -143,8 +146,7 @@ struct SourceConfiguration { | |||
| 143 | BitField<27, 1, u32_le> gain_2_dirty; | 146 | BitField<27, 1, u32_le> gain_2_dirty; |
| 144 | BitField<28, 1, u32_le> sync_dirty; | 147 | BitField<28, 1, u32_le> sync_dirty; |
| 145 | BitField<29, 1, u32_le> reset_flag; | 148 | BitField<29, 1, u32_le> reset_flag; |
| 146 | 149 | BitField<30, 1, u32_le> embedded_buffer_dirty; | |
| 147 | BitField<31, 1, u32_le> embedded_buffer_dirty; | ||
| 148 | }; | 150 | }; |
| 149 | 151 | ||
| 150 | // Gain control | 152 | // Gain control |
| @@ -175,7 +177,8 @@ struct SourceConfiguration { | |||
| 175 | /** | 177 | /** |
| 176 | * This is the simplest normalized first-order digital recursive filter. | 178 | * This is the simplest normalized first-order digital recursive filter. |
| 177 | * The transfer function of this filter is: | 179 | * The transfer function of this filter is: |
| 178 | * H(z) = b0 / (1 + a1 z^-1) | 180 | * H(z) = b0 / (1 - a1 z^-1) |
| 181 | * Note the feedbackward coefficient is negated. | ||
| 179 | * Values are signed fixed point with 15 fractional bits. | 182 | * Values are signed fixed point with 15 fractional bits. |
| 180 | */ | 183 | */ |
| 181 | struct SimpleFilter { | 184 | struct SimpleFilter { |
| @@ -192,11 +195,11 @@ struct SourceConfiguration { | |||
| 192 | * Values are signed fixed point with 14 fractional bits. | 195 | * Values are signed fixed point with 14 fractional bits. |
| 193 | */ | 196 | */ |
| 194 | struct BiquadFilter { | 197 | struct BiquadFilter { |
| 195 | s16_le b0; | ||
| 196 | s16_le b1; | ||
| 197 | s16_le b2; | ||
| 198 | s16_le a1; | ||
| 199 | s16_le a2; | 198 | s16_le a2; |
| 199 | s16_le a1; | ||
| 200 | s16_le b2; | ||
| 201 | s16_le b1; | ||
| 202 | s16_le b0; | ||
| 200 | }; | 203 | }; |
| 201 | 204 | ||
| 202 | union { | 205 | union { |
diff --git a/src/audio_core/hle/filter.cpp b/src/audio_core/hle/filter.cpp new file mode 100644 index 000000000..2c65ef026 --- /dev/null +++ b/src/audio_core/hle/filter.cpp | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <cstddef> | ||
| 7 | |||
| 8 | #include "audio_core/hle/common.h" | ||
| 9 | #include "audio_core/hle/dsp.h" | ||
| 10 | #include "audio_core/hle/filter.h" | ||
| 11 | |||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/math_util.h" | ||
| 14 | |||
| 15 | namespace DSP { | ||
| 16 | namespace HLE { | ||
| 17 | |||
| 18 | void SourceFilters::Reset() { | ||
| 19 | Enable(false, false); | ||
| 20 | } | ||
| 21 | |||
| 22 | void SourceFilters::Enable(bool simple, bool biquad) { | ||
| 23 | simple_filter_enabled = simple; | ||
| 24 | biquad_filter_enabled = biquad; | ||
| 25 | |||
| 26 | if (!simple) | ||
| 27 | simple_filter.Reset(); | ||
| 28 | if (!biquad) | ||
| 29 | biquad_filter.Reset(); | ||
| 30 | } | ||
| 31 | |||
| 32 | void SourceFilters::Configure(SourceConfiguration::Configuration::SimpleFilter config) { | ||
| 33 | simple_filter.Configure(config); | ||
| 34 | } | ||
| 35 | |||
| 36 | void SourceFilters::Configure(SourceConfiguration::Configuration::BiquadFilter config) { | ||
| 37 | biquad_filter.Configure(config); | ||
| 38 | } | ||
| 39 | |||
| 40 | void SourceFilters::ProcessFrame(StereoFrame16& frame) { | ||
| 41 | if (!simple_filter_enabled && !biquad_filter_enabled) | ||
| 42 | return; | ||
| 43 | |||
| 44 | if (simple_filter_enabled) { | ||
| 45 | FilterFrame(frame, simple_filter); | ||
| 46 | } | ||
| 47 | |||
| 48 | if (biquad_filter_enabled) { | ||
| 49 | FilterFrame(frame, biquad_filter); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | // SimpleFilter | ||
| 54 | |||
| 55 | void SourceFilters::SimpleFilter::Reset() { | ||
| 56 | y1.fill(0); | ||
| 57 | // Configure as passthrough. | ||
| 58 | a1 = 0; | ||
| 59 | b0 = 1 << 15; | ||
| 60 | } | ||
| 61 | |||
| 62 | void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) { | ||
| 63 | a1 = config.a1; | ||
| 64 | b0 = config.b0; | ||
| 65 | } | ||
| 66 | |||
| 67 | std::array<s16, 2> SourceFilters::SimpleFilter::ProcessSample(const std::array<s16, 2>& x0) { | ||
| 68 | std::array<s16, 2> y0; | ||
| 69 | for (size_t i = 0; i < 2; i++) { | ||
| 70 | const s32 tmp = (b0 * x0[i] + a1 * y1[i]) >> 15; | ||
| 71 | y0[i] = MathUtil::Clamp(tmp, -32768, 32767); | ||
| 72 | } | ||
| 73 | |||
| 74 | y1 = y0; | ||
| 75 | |||
| 76 | return y0; | ||
| 77 | } | ||
| 78 | |||
| 79 | // BiquadFilter | ||
| 80 | |||
| 81 | void SourceFilters::BiquadFilter::Reset() { | ||
| 82 | x1.fill(0); | ||
| 83 | x2.fill(0); | ||
| 84 | y1.fill(0); | ||
| 85 | y2.fill(0); | ||
| 86 | // Configure as passthrough. | ||
| 87 | a1 = a2 = b1 = b2 = 0; | ||
| 88 | b0 = 1 << 14; | ||
| 89 | } | ||
| 90 | |||
| 91 | void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) { | ||
| 92 | a1 = config.a1; | ||
| 93 | a2 = config.a2; | ||
| 94 | b0 = config.b0; | ||
| 95 | b1 = config.b1; | ||
| 96 | b2 = config.b2; | ||
| 97 | } | ||
| 98 | |||
| 99 | std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s16, 2>& x0) { | ||
| 100 | std::array<s16, 2> y0; | ||
| 101 | for (size_t i = 0; i < 2; i++) { | ||
| 102 | const s32 tmp = (b0 * x0[i] + b1 * x1[i] + b2 * x2[i] + a1 * y1[i] + a2 * y2[i]) >> 14; | ||
| 103 | y0[i] = MathUtil::Clamp(tmp, -32768, 32767); | ||
| 104 | } | ||
| 105 | |||
| 106 | x2 = x1; | ||
| 107 | x1 = x0; | ||
| 108 | y2 = y1; | ||
| 109 | y1 = y0; | ||
| 110 | |||
| 111 | return y0; | ||
| 112 | } | ||
| 113 | |||
| 114 | } // namespace HLE | ||
| 115 | } // namespace DSP | ||
diff --git a/src/audio_core/hle/filter.h b/src/audio_core/hle/filter.h new file mode 100644 index 000000000..75738f600 --- /dev/null +++ b/src/audio_core/hle/filter.h | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // Copyright 2016 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | |||
| 9 | #include "audio_core/hle/common.h" | ||
| 10 | #include "audio_core/hle/dsp.h" | ||
| 11 | |||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace DSP { | ||
| 15 | namespace HLE { | ||
| 16 | |||
| 17 | /// Preprocessing filters. There is an independent set of filters for each Source. | ||
| 18 | class SourceFilters final { | ||
| 19 | SourceFilters() { Reset(); } | ||
| 20 | |||
| 21 | /// Reset internal state. | ||
| 22 | void Reset(); | ||
| 23 | |||
| 24 | /** | ||
| 25 | * Enable/Disable filters | ||
| 26 | * See also: SourceConfiguration::Configuration::simple_filter_enabled, | ||
| 27 | * SourceConfiguration::Configuration::biquad_filter_enabled. | ||
| 28 | * @param simple If true, enables the simple filter. If false, disables it. | ||
| 29 | * @param simple If true, enables the biquad filter. If false, disables it. | ||
| 30 | */ | ||
| 31 | void Enable(bool simple, bool biquad); | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Configure simple filter. | ||
| 35 | * @param config Configuration from DSP shared memory. | ||
| 36 | */ | ||
| 37 | void Configure(SourceConfiguration::Configuration::SimpleFilter config); | ||
| 38 | |||
| 39 | /** | ||
| 40 | * Configure biquad filter. | ||
| 41 | * @param config Configuration from DSP shared memory. | ||
| 42 | */ | ||
| 43 | void Configure(SourceConfiguration::Configuration::BiquadFilter config); | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Processes a frame in-place. | ||
| 47 | * @param frame Audio samples to process. Modified in-place. | ||
| 48 | */ | ||
| 49 | void ProcessFrame(StereoFrame16& frame); | ||
| 50 | |||
| 51 | private: | ||
| 52 | bool simple_filter_enabled; | ||
| 53 | bool biquad_filter_enabled; | ||
| 54 | |||
| 55 | struct SimpleFilter { | ||
| 56 | SimpleFilter() { Reset(); } | ||
| 57 | |||
| 58 | /// Resets internal state. | ||
| 59 | void Reset(); | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Configures this filter with application settings. | ||
| 63 | * @param config Configuration from DSP shared memory. | ||
| 64 | */ | ||
| 65 | void Configure(SourceConfiguration::Configuration::SimpleFilter config); | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Processes a single stereo PCM16 sample. | ||
| 69 | * @param x0 Input sample | ||
| 70 | * @return Output sample | ||
| 71 | */ | ||
| 72 | std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0); | ||
| 73 | |||
| 74 | private: | ||
| 75 | // Configuration | ||
| 76 | s32 a1, b0; | ||
| 77 | // Internal state | ||
| 78 | std::array<s16, 2> y1; | ||
| 79 | } simple_filter; | ||
| 80 | |||
| 81 | struct BiquadFilter { | ||
| 82 | BiquadFilter() { Reset(); } | ||
| 83 | |||
| 84 | /// Resets internal state. | ||
| 85 | void Reset(); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Configures this filter with application settings. | ||
| 89 | * @param config Configuration from DSP shared memory. | ||
| 90 | */ | ||
| 91 | void Configure(SourceConfiguration::Configuration::BiquadFilter config); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Processes a single stereo PCM16 sample. | ||
| 95 | * @param x0 Input sample | ||
| 96 | * @return Output sample | ||
| 97 | */ | ||
| 98 | std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0); | ||
| 99 | |||
| 100 | private: | ||
| 101 | // Configuration | ||
| 102 | s32 a1, a2, b0, b1, b2; | ||
| 103 | // Internal state | ||
| 104 | std::array<s16, 2> x1; | ||
| 105 | std::array<s16, 2> x2; | ||
| 106 | std::array<s16, 2> y1; | ||
| 107 | std::array<s16, 2> y2; | ||
| 108 | } biquad_filter; | ||
| 109 | }; | ||
| 110 | |||
| 111 | } // namespace HLE | ||
| 112 | } // namespace DSP | ||