Scale frequencies up by 1e9 to better handle non-integer source frequencies, e.g. the Master System PSG
9const RESAMPLE_SCALING_FACTOR: u64 = 1_000_000_000;
Based on https://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf
Based on https://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf Assuming that Rust/LLVM will optimize these constant floating-point divisions into multiplications, which it does seem to do based on experimentation in Compiler Explorer
25#[must_use] 26pub fn interpolate_cubic_hermite_6p([ym2, ym1, y0, y1, y2, y3]: [f64; 6], x: f64) -> f64 { 27 let c0 = y0; 28 let c1 = 1.0 / 12.0 * (ym2 - y2) + 2.0 / 3.0 * (y1 - ym1); 29 let c2 = 5.0 / 4.0 * ym1 - 7.0 / 3.0 * y0 + 5.0 / 3.0 * y1 - 1.0 / 2.0 * y2 + 1.0 / 12.0 * y3 30 - 1.0 / 6.0 * ym2; 31 let c3 = 1.0 / 12.0 * (ym2 - y3) + 7.0 / 12.0 * (y2 - ym1) + 4.0 / 3.0 * (y0 - y1); 32 33 ((c3 * x + c2) * x + c1) * x + c0 34}
36#[derive(Debug, Clone)] 37pub struct DynamicResamplingRate { 38 base_output_frequency: u32, 39 dynamic_output_frequency: u32, 40 dynamic_update_counter: u32, 41 target_audio_buffer_size: u32, 42} 43 44impl DynamicResamplingRate { 45 #[must_use] 46 pub fn new(base_output_frequency: u32, target_audio_buffer_size: u32) -> Self { 47 Self { 48 base_output_frequency, 49 dynamic_output_frequency: base_output_frequency, 50 dynamic_update_counter: 0, 51 target_audio_buffer_size, 52 } 53 } 54 55 pub fn update_config(&mut self, base_output_frequency: u32, target_audio_buffer_size: u32) { 56 *self = Self::new(base_output_frequency, target_audio_buffer_size); 57 } 58 59 #[must_use] 60 pub fn current_output_frequency(&self) -> u32 { 61 self.dynamic_output_frequency 62 } 63 64 pub fn adjust(&mut self, audio_buffer_len: u32) { 65 // Restrict the adjusted ratio to within 0.5% of the expected ratio 66 const MAX_DELTA: f64 = 0.005; 67 68 // Only update the ratio every 20 frames 69 const UPDATE_PERIOD: u32 = 20; 70 71 self.dynamic_update_counter += 1; 72 if self.dynamic_update_counter != UPDATE_PERIOD { 73 return; 74 } 75 self.dynamic_update_counter = 0; 76 77 let target_len: f64 = self.target_audio_buffer_size.into(); 78 let current_len: f64 = audio_buffer_len.into(); 79 let difference = ((target_len - current_len) / target_len).clamp(-1.0, 1.0); 80 let adjustment = 1.0 + MAX_DELTA * difference; 81 82 // This should _probably_ adjust the current dynamic frequency rather than the audio output 83 // stream frequency, but adjusting the latter seems to work much better in practice 84 self.dynamic_output_frequency = 85 (adjustment * f64::from(self.base_output_frequency)).round() as u32; 86 87 log::debug!( 88 "Adjusted dynamic frequency to {}; target={target_len}, current={current_len}, adjustment={adjustment}", 89 self.dynamic_output_frequency 90 ); 91 } 92}