@@ -7,8 +7,11 @@ use firewheel_core::{
77 buffer:: ChannelBuffer ,
88 declick:: { DeclickFadeCurve , Declicker } ,
99 fade:: FadeCurve ,
10- filter:: single_pole_iir:: {
11- OnePoleIirHPF , OnePoleIirHPFCoeff , OnePoleIirLPF , OnePoleIirLPFCoeff ,
10+ filter:: {
11+ single_pole_iir:: {
12+ OnePoleIirHPF , OnePoleIirHPFCoeff , OnePoleIirLPF , OnePoleIirLPFCoeff ,
13+ } ,
14+ smoothing_filter:: DEFAULT_SMOOTH_SECONDS ,
1215 } ,
1316 mix:: { Mix , MixDSP } ,
1417 volume:: Volume ,
@@ -65,7 +68,7 @@ impl Default for EchoNodeConfig {
6568#[ cfg_attr( feature = "bevy" , derive( bevy_ecs:: prelude:: Component ) ) ]
6669#[ cfg_attr( feature = "bevy_reflect" , derive( bevy_reflect:: Reflect ) ) ]
6770pub struct EchoNode < const CHANNELS : usize > {
68- /// TODO: must be smooothed! The lowpass frequency in hertz in the range
71+ /// The lowpass frequency in hertz in the range
6972 /// `[20.0, 20480.0]`.
7073 pub feedback_lpf : f32 ,
7174 /// The highpass frequency in hertz in the range `[20.0, 20480.0]`.
@@ -96,6 +99,11 @@ pub struct EchoNode<const CHANNELS: usize> {
9699 /// By default this is set to [`FadeCurve::EqualPower3dB`].
97100 pub fade_curve : FadeCurve ,
98101
102+ /// Adjusts the time in seconds over which parameters are smoothed.
103+ ///
104+ /// Defaults to `0.015` (15ms).
105+ pub smooth_seconds : f32 ,
106+
99107 pub stop : Notify < ( ) > ,
100108 pub paused : bool ,
101109}
@@ -112,6 +120,16 @@ impl<const CHANNELS: usize> Default for EchoNode<CHANNELS> {
112120 delay_seconds : [ 0.5 ; CHANNELS ] ,
113121 feedback : [ Volume :: from_percent ( 30.0 ) ; CHANNELS ] ,
114122 crossfeed : [ Volume :: from_percent ( 0.0 ) ; CHANNELS ] ,
123+ smooth_seconds : DEFAULT_SMOOTH_SECONDS ,
124+ }
125+ }
126+ }
127+
128+ impl < const CHANNELS : usize > EchoNode < CHANNELS > {
129+ fn smoother_config ( & self ) -> SmootherConfig {
130+ SmootherConfig {
131+ smooth_seconds : self . smooth_seconds ,
132+ ..Default :: default ( )
115133 }
116134 }
117135}
@@ -130,62 +148,55 @@ impl<const CHANNELS: usize> AudioNode for EchoNode<CHANNELS> {
130148 config : & Self :: Configuration ,
131149 cx : ConstructProcessorContext ,
132150 ) -> impl AudioNodeProcessor {
133- const DELAY_SMOOTH_SECONDS : f32 = 0.15 ;
134151 let max_frames = cx. stream_info . max_block_frames . get ( ) as usize ;
135152 let sample_rate = cx. stream_info . sample_rate ;
153+ let smoother_config = self . smoother_config ( ) ;
136154 Processor :: < CHANNELS > {
137155 params : * self ,
138156 declicker : Declicker :: default ( ) ,
139- delay_seconds_smoothed : self . delay_seconds . map ( |channel| {
140- SmoothedParam :: new (
141- channel,
142- SmootherConfig {
143- smooth_seconds : DELAY_SMOOTH_SECONDS ,
144- ..Default :: default ( )
145- } ,
146- sample_rate,
147- )
148- } ) ,
149- feedback_smoothed : self . feedback . map ( |channel| {
150- SmoothedParam :: new ( channel. linear ( ) , Default :: default ( ) , sample_rate)
151- } ) ,
152- crossfeed_smoothed : self . crossfeed . map ( |channel| {
153- SmoothedParam :: new ( channel. linear ( ) , Default :: default ( ) , sample_rate)
154- } ) ,
157+ delay_seconds_smoothed : self
158+ . delay_seconds
159+ . map ( |channel| SmoothedParam :: new ( channel, smoother_config, sample_rate) ) ,
160+ feedback_smoothed : self
161+ . feedback
162+ . map ( |channel| SmoothedParam :: new ( channel. linear ( ) , smoother_config, sample_rate) ) ,
163+ crossfeed_smoothed : self
164+ . crossfeed
165+ . map ( |channel| SmoothedParam :: new ( channel. linear ( ) , smoother_config, sample_rate) ) ,
155166 delay_seconds_smoothed_buffer : ChannelBuffer :: < f32 , CHANNELS > :: new ( max_frames) ,
156167 feedback_smoothed_buffer : ChannelBuffer :: < f32 , CHANNELS > :: new ( max_frames) ,
157168 crossfeed_smoothed_buffer : ChannelBuffer :: < f32 , CHANNELS > :: new ( max_frames) ,
158169 delay_buffers : from_fn ( |_| DelayLine :: < f32 > :: initialized ( config. buffer_capacity ) ) ,
159170 mix_dsp : MixDSP :: new (
160171 self . mix ,
161172 self . fade_curve ,
162- SmootherConfig :: default ( ) ,
173+ smoother_config ,
163174 cx. stream_info . sample_rate ,
164175 ) ,
165- tap_lpf_coeff : OnePoleIirLPFCoeff :: new (
166- // Ensure cutoff is above 0Hz
167- self . feedback_lpf . max ( 0.0 ) ,
168- cx. stream_info . sample_rate_recip as f32 ,
169- ) ,
170- tap_hpf_coeff : OnePoleIirHPFCoeff :: new (
171- // Ensure cutoff is above 0Hz
172- self . feedback_hpf . max ( 0.0 ) ,
173- cx. stream_info . sample_rate_recip as f32 ,
174- ) ,
175- tap_lpf : [ OnePoleIirLPF :: default ( ) ; CHANNELS ] ,
176- tap_hpf : [ OnePoleIirHPF :: default ( ) ; CHANNELS ] ,
176+ feedback_lpf : [ OnePoleIirLPF :: default ( ) ; CHANNELS ] ,
177+ feedback_hpf : [ OnePoleIirHPF :: default ( ) ; CHANNELS ] ,
177178 prev_delay_seconds : [ None ; CHANNELS ] ,
178179 next_delay_seconds : [ None ; CHANNELS ] ,
180+ feedback_lpf_smoothed : SmoothedParam :: new (
181+ self . feedback_lpf ,
182+ smoother_config,
183+ sample_rate,
184+ ) ,
185+ feedback_hpf_smoothed : SmoothedParam :: new (
186+ self . feedback_hpf ,
187+ smoother_config,
188+ sample_rate,
189+ ) ,
179190 }
180191 }
181192}
182193
183194struct Processor < const CHANNELS : usize > {
184195 params : EchoNode < CHANNELS > ,
185- tap_lpf : [ OnePoleIirLPF ; CHANNELS ] ,
186- tap_hpf : [ OnePoleIirHPF ; CHANNELS ] ,
187- tap_lpf_coeff : OnePoleIirLPFCoeff ,
188- tap_hpf_coeff : OnePoleIirHPFCoeff ,
196+ feedback_lpf_smoothed : SmoothedParam ,
197+ feedback_hpf_smoothed : SmoothedParam ,
198+ feedback_lpf : [ OnePoleIirLPF ; CHANNELS ] ,
199+ feedback_hpf : [ OnePoleIirHPF ; CHANNELS ] ,
189200 mix_dsp : MixDSP ,
190201 declicker : Declicker ,
191202 // Set when transitioning delay seconds. When settled on the new value,
@@ -227,49 +238,62 @@ impl<const CHANNELS: usize> AudioNodeProcessor for Processor<CHANNELS> {
227238 events : & mut ProcEvents ,
228239 extra : & mut ProcExtra ,
229240 ) -> ProcessStatus {
241+ const SCRATCH_CHANNELS : usize = 2 ;
242+ const LPF_SCRATCH_INDEX : usize = 0 ;
243+ const HPF_SCRATCH_INDEX : usize = 1 ;
244+
230245 let mut clear_buffers = false ;
231246 for mut patch in events. drain_patches :: < EchoNode < CHANNELS > > ( ) {
232247 match & mut patch {
233- // TODO: This must be a smoothed value!
234- EchoNodePatch :: FeedbackLpf ( value) => {
235- self . tap_lpf_coeff =
236- OnePoleIirLPFCoeff :: new ( * value, info. sample_rate_recip as f32 ) ;
248+ EchoNodePatch :: SmoothSeconds ( seconds) => {
249+ // Change all smoothed parameters to new smoothing
250+ let update_smoothing = |param : & mut SmoothedParam | {
251+ param. set_smooth_seconds ( * seconds, info. sample_rate ) ;
252+ } ;
253+ self . crossfeed_smoothed
254+ . iter_mut ( )
255+ . chain ( self . feedback_smoothed . iter_mut ( ) )
256+ . chain ( [ self . feedback_hpf_smoothed , self . feedback_lpf_smoothed ] . iter_mut ( ) )
257+ . chain ( self . delay_seconds_smoothed . iter_mut ( ) )
258+ . for_each ( update_smoothing) ;
259+ }
260+ EchoNodePatch :: FeedbackLpf ( cutoff_hz) => {
261+ self . feedback_lpf_smoothed . set_value ( * cutoff_hz) ;
237262 }
238- EchoNodePatch :: FeedbackHpf ( value) => {
239- self . tap_hpf_coeff =
240- OnePoleIirHPFCoeff :: new ( * value, info. sample_rate_recip as f32 ) ;
263+ EchoNodePatch :: FeedbackHpf ( cutoff_hz) => {
264+ self . feedback_hpf_smoothed . set_value ( * cutoff_hz) ;
241265 }
242- EchoNodePatch :: Mix ( value ) => {
243- self . mix_dsp . set_mix ( * value , self . params . fade_curve ) ;
266+ EchoNodePatch :: Mix ( mix ) => {
267+ self . mix_dsp . set_mix ( * mix , self . params . fade_curve ) ;
244268 }
245- EchoNodePatch :: DelaySeconds ( ( index, value ) ) => {
269+ EchoNodePatch :: DelaySeconds ( ( index, delay_seconds ) ) => {
246270 // TODO: make more robust
247271 // Check to see if settled.
248272 if self . prev_delay_seconds [ * index] . is_none ( ) {
249273 self . prev_delay_seconds [ * index] =
250274 Some ( self . delay_seconds_smoothed [ * index] . target_value ( ) ) ;
251- self . delay_seconds_smoothed [ * index] . set_value ( * value )
275+ self . delay_seconds_smoothed [ * index] . set_value ( * delay_seconds )
252276 } else {
253277 // If we're still transitioning, queue up the desired change.
254- self . next_delay_seconds [ * index] = Some ( * value ) ;
278+ self . next_delay_seconds [ * index] = Some ( * delay_seconds ) ;
255279 }
256280 }
257- EchoNodePatch :: Feedback ( ( index, value ) ) => {
258- self . feedback_smoothed [ * index] . set_value ( value . linear ( ) )
281+ EchoNodePatch :: Feedback ( ( index, feedback ) ) => {
282+ self . feedback_smoothed [ * index] . set_value ( feedback . linear ( ) )
259283 }
260- EchoNodePatch :: Crossfeed ( ( index, value ) ) => {
261- self . crossfeed_smoothed [ * index] . set_value ( value . linear ( ) ) ;
284+ EchoNodePatch :: Crossfeed ( ( index, crossfeed ) ) => {
285+ self . crossfeed_smoothed [ * index] . set_value ( crossfeed . linear ( ) ) ;
262286 }
263287 EchoNodePatch :: Stop ( _) => {
264288 clear_buffers = true ;
265289 self . declicker . fade_to_enabled ( false , & extra. declick_values ) ;
266290 }
267- EchoNodePatch :: Paused ( value ) => {
291+ EchoNodePatch :: Paused ( is_paused ) => {
268292 self . declicker
269- . fade_to_enabled ( !* value , & extra. declick_values ) ;
293+ . fade_to_enabled ( !* is_paused , & extra. declick_values ) ;
270294 }
271- EchoNodePatch :: FadeCurve ( value ) => {
272- self . mix_dsp . set_mix ( self . params . mix , * value ) ;
295+ EchoNodePatch :: FadeCurve ( fade_curve ) => {
296+ self . mix_dsp . set_mix ( self . params . mix , * fade_curve ) ;
273297 }
274298 }
275299 self . params . apply ( patch) ;
@@ -287,6 +311,17 @@ impl<const CHANNELS: usize> AudioNodeProcessor for Processor<CHANNELS> {
287311 }
288312
289313 // Process smoothed values all at the same time
314+
315+ // Smoothed cutoff values do not have to be calculated per channel.
316+ // Calculate smoothed filter values
317+ let mut scratch = extra. scratch_buffers . channels_mut :: < SCRATCH_CHANNELS > ( ) ;
318+ let scratch: [ & mut [ & mut [ f32 ] ] ; 2 ] = scratch. split_at_mut ( 1 ) . into ( ) ;
319+ let lpf_smoothed = & mut scratch[ LPF_SCRATCH_INDEX ] [ 0 ] ;
320+ self . feedback_lpf_smoothed . process_into_buffer ( lpf_smoothed) ;
321+
322+ let hpf_smoothed = & mut scratch[ HPF_SCRATCH_INDEX ] [ 0 ] ;
323+ self . feedback_hpf_smoothed . process_into_buffer ( hpf_smoothed) ;
324+
290325 for channel_index in ( 0 ..CHANNELS ) . into_iter ( ) {
291326 // Queue up delays if applicable
292327 if self . next_delay_seconds [ channel_index] . is_some ( ) {
@@ -380,8 +415,8 @@ impl<const CHANNELS: usize> AudioNodeProcessor for Processor<CHANNELS> {
380415 // Process signal to find next samples to feed into the buffer (wet
381416 // signal)
382417 let next_buffer_samples: [ f32 ; CHANNELS ] = from_fn ( |channel_index| {
383- let lpf = & mut self . tap_lpf [ channel_index] ;
384- let hpf = & mut self . tap_hpf [ channel_index] ;
418+ let lpf = & mut self . feedback_lpf [ channel_index] ;
419+ let hpf = & mut self . feedback_hpf [ channel_index] ;
385420
386421 let input_sample = buffers. inputs [ channel_index] [ sample_index] ;
387422 let feedback_sample = delayed_samples[ channel_index]
@@ -394,8 +429,25 @@ impl<const CHANNELS: usize> AudioNodeProcessor for Processor<CHANNELS> {
394429 . sum :: < f32 > ( ) ;
395430
396431 let mut next = input_sample + feedback_sample + crossfed_sample;
397- next = lpf. process ( next, self . tap_lpf_coeff ) ;
398- next = hpf. process ( next, self . tap_hpf_coeff ) ;
432+
433+ // Change filter coeffs based on smoothed values
434+ let scratch = extra. scratch_buffers . channels :: < SCRATCH_CHANNELS > ( ) ;
435+
436+ // Filter samples through high and lowpass filter
437+ next = lpf. process (
438+ next,
439+ OnePoleIirLPFCoeff :: new (
440+ scratch[ LPF_SCRATCH_INDEX ] [ sample_index] ,
441+ info. sample_rate_recip as f32 ,
442+ ) ,
443+ ) ;
444+ next = hpf. process (
445+ next,
446+ OnePoleIirHPFCoeff :: new (
447+ scratch[ HPF_SCRATCH_INDEX ] [ sample_index] ,
448+ info. sample_rate_recip as f32 ,
449+ ) ,
450+ ) ;
399451 next
400452 } ) ;
401453
0 commit comments