11"""
22Equal-loudness filter implementation for audio processing.
33
4- This module implements an equal-loudness filter which compensates for the human ear's
4+ This module implements an equal-loudness filter which compensates for the human ear's
55non-linear response to sound using cascaded IIR filters.
66"""
77
@@ -23,44 +23,44 @@ def _yulewalk_approximation(
2323) -> tuple [np .ndarray , np .ndarray ]:
2424 """
2525 Simplified Yule-Walker approximation for filter design.
26-
26+
2727 This is a basic implementation that approximates the yulewalker functionality
2828 using numpy for creating filter coefficients from frequency response data.
29-
29+
3030 Args:
3131 order: Filter order
3232 frequencies: Normalized frequencies (0 to 1)
3333 gains: Desired gains at those frequencies
34-
34+
3535 Returns:
3636 Tuple of (a_coeffs, b_coeffs) for the IIR filter
3737 """
3838 # Simple approach: create coefficients that approximate the desired response
3939 # This is a simplified version - in practice, yulewalker uses more sophisticated methods
40-
40+
4141 # Create a basic filter response approximation
4242 # Using a simple polynomial fit approach
4343 try :
4444 # Fit polynomial to log-magnitude response
4545 log_gains = np .log10 (gains + 1e-10 ) # Avoid log(0)
4646 coeffs = np .polyfit (frequencies , log_gains , min (order , len (frequencies ) - 1 ))
47-
47+
4848 # Convert polynomial coefficients to filter coefficients
4949 a_coeffs = np .zeros (order + 1 )
5050 b_coeffs = np .zeros (order + 1 )
51-
51+
5252 a_coeffs [0 ] = 1.0 # Normalized
53-
53+
5454 # Simple mapping from polynomial to filter coefficients
5555 for i in range (min (len (coeffs ), order )):
5656 b_coeffs [i ] = coeffs [- (i + 1 )] * 0.1 # Scale factor for stability
57-
57+
5858 # Ensure some basic coefficients are set
5959 if b_coeffs [0 ] == 0 :
6060 b_coeffs [0 ] = 0.1
61-
61+
6262 return a_coeffs , b_coeffs
63-
63+
6464 except (np .linalg .LinAlgError , ValueError ):
6565 # Fallback to simple pass-through filter
6666 a_coeffs = np .zeros (order + 1 )
@@ -74,27 +74,27 @@ class EqualLoudnessFilter:
7474 """
7575 An equal-loudness filter which compensates for the human ear's non-linear response
7676 to sound.
77-
77+
7878 This filter corrects the frequency response by cascading a Yule-Walker approximation
7979 filter and a Butterworth high-pass filter.
80-
81- The filter is designed for use with sample rates of 44.1kHz and above. If you're
80+
81+ The filter is designed for use with sample rates of 44.1kHz and above. If you're
8282 using a lower sample rate, use with caution.
83-
83+
8484 The equal-loudness contours are based on the Robinson-Dadson curves (1956), which
8585 describe how the human ear perceives different frequencies at various loudness levels.
86-
86+
8787 References:
8888 - Robinson, D. W., & Dadson, R. S. (1956). A re-determination of the equal-
8989 loudness relations for pure tones. British Journal of Applied Physics, 7(5), 166.
9090 - Original MATLAB implementation by David Robinson, 2001
91-
91+
9292 Examples:
9393 >>> filt = EqualLoudnessFilter(48000)
9494 >>> processed_sample = filt.process(0.5)
9595 >>> isinstance(processed_sample, float)
9696 True
97-
97+
9898 >>> # Process silence
9999 >>> filt = EqualLoudnessFilter()
100100 >>> filt.process(0.0)
@@ -104,17 +104,17 @@ class EqualLoudnessFilter:
104104 def __init__ (self , samplerate : int = 44100 ) -> None :
105105 """
106106 Initialize the equal-loudness filter.
107-
107+
108108 Args:
109109 samplerate: Sample rate in Hz (default: 44100)
110-
110+
111111 Raises:
112112 ValueError: If samplerate is not positive
113113 """
114114 if samplerate <= 0 :
115115 msg = "Sample rate must be positive"
116116 raise ValueError (msg )
117-
117+
118118 self .samplerate = samplerate
119119 self .yulewalk_filter = IIRFilter (10 )
120120 self .butterworth_filter = make_highpass (150 , samplerate )
@@ -138,17 +138,17 @@ def __init__(self, samplerate: int = 44100) -> None:
138138 def process (self , sample : Union [float , int ]) -> float :
139139 """
140140 Process a single sample through both filters.
141-
141+
142142 The sample is first processed through the Yule-Walker approximation filter
143143 to apply the equal-loudness curve correction, then through a high-pass
144144 Butterworth filter to remove low-frequency artifacts.
145-
145+
146146 Args:
147147 sample: Input audio sample (should be normalized to [-1, 1] range)
148-
148+
149149 Returns:
150150 Processed audio sample as float
151-
151+
152152 Examples:
153153 >>> filt = EqualLoudnessFilter()
154154 >>> filt.process(0.0)
@@ -160,17 +160,17 @@ def process(self, sample: Union[float, int]) -> float:
160160 """
161161 # Convert to float for processing
162162 sample_float = float (sample )
163-
163+
164164 # Apply Yule-Walker approximation filter first
165165 tmp = self .yulewalk_filter .process (sample_float )
166-
166+
167167 # Then apply Butterworth high-pass filter
168168 return self .butterworth_filter .process (tmp )
169169
170170 def reset (self ) -> None :
171171 """
172172 Reset the filter's internal state (clear history).
173-
173+
174174 This is useful when starting to process a new audio stream
175175 to avoid artifacts from previous processing.
176176 """
@@ -181,7 +181,7 @@ def reset(self) -> None:
181181 def get_filter_info (self ) -> dict [str , Union [int , float , list [float ]]]:
182182 """
183183 Get information about the filter configuration.
184-
184+
185185 Returns:
186186 Dictionary containing filter parameters and coefficients
187187 """
@@ -197,17 +197,17 @@ def get_filter_info(self) -> dict[str, Union[int, float, list[float]]]:
197197if __name__ == "__main__" :
198198 # Demonstration of the filter
199199 import doctest
200-
200+
201201 doctest .testmod ()
202-
202+
203203 # Create a simple test
204204 filter_instance = EqualLoudnessFilter (44100 )
205205 test_samples = [0.0 , 0.1 , 0.5 , - 0.3 , 1.0 , - 1.0 ]
206-
206+
207207 print ("Equal-Loudness Filter Demo:" )
208208 print ("Sample Rate: 44100 Hz" )
209209 print ("Test samples and their filtered outputs:" )
210-
210+
211211 for sample in test_samples :
212212 filtered = filter_instance .process (sample )
213- print (f"Input: { sample :6.1f} → Output: { filtered :8.6f} " )
213+ print (f"Input: { sample :6.1f} → Output: { filtered :8.6f} " )
0 commit comments