1mod cubic_resampler;
2pub mod fir_resampler;
3
4pub use cubic_resampler::CubicResampler;
5
6pub const DEFAULT_OUTPUT_FREQUENCY: u64 = 48000;
7
8// 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;
10
11// Based on https://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf
12#[must_use]
13pub fn interpolate_cubic_hermite_4p([ym1, y0, y1, y2]: [f64; 4], x: f64) -> f64 {
14    let c0 = y0;
15    let c1 = 0.5 * (y1 - ym1);
16    let c2 = ym1 - 2.5 * y0 + 2.0 * y1 - 0.5 * y2;
17    let c3 = 0.5 * (y2 - ym1) + 1.5 * (y0 - y1);
18
19    ((c3 * x + c2) * x + c1) * x + c0
20}
21
22// Based on https://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf
23// Assuming that Rust/LLVM will optimize these constant floating-point divisions into multiplications,
24// 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}
35
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}