mirror of
				https://git.suyu.dev/suyu/suyu.git
				synced 2025-10-31 06:46:40 +08:00 
			
		
		
		
	DSP: Implement audio filters (simple, biquad)
This commit is contained in:
		
							parent
							
								
									efd1c3f8c3
								
							
						
					
					
						commit
						2a99464ef1
					
				| @ -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 | ||||||
|             ) |             ) | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								src/audio_core/hle/common.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/hle/common.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 { | ||||||
|  | |||||||
							
								
								
									
										115
									
								
								src/audio_core/hle/filter.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/audio_core/hle/filter.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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
 | ||||||
							
								
								
									
										112
									
								
								src/audio_core/hle/filter.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/audio_core/hle/filter.h
									
									
									
									
									
										Normal file
									
								
							| @ -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
 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user