jevsnes.git / third-party / rust / jgenesis / backend / snes-core / src / apu / dsp / interpolate.rs
1use crate::apu::dsp::tables;
2
3const I15_MIN: f64 = -16384.0;
4const I15_MAX: f64 = 16383.0;
5
6pub struct InterpolateArgs {
7    pub pitch_counter: u16,
8    pub oldest: i16,
9    pub older: i16,
10    pub old: i16,
11    pub sample: i16,
12}
13
14pub fn gaussian(
15    InterpolateArgs { pitch_counter, oldest, older, old, sample }: InterpolateArgs,
16) -> i16 {
17    // Do math in 32 bits to avoid overflows
18    let sample: i32 = sample.into();
19    let old: i32 = old.into();
20    let older: i32 = older.into();
21    let oldest: i32 = oldest.into();
22
23    // Bits 4-11 of pitch counter are the interpolation index into the Gaussian interpolation table
24    let interpolation_idx = ((pitch_counter >> 4) & 0xFF) as usize;
25
26    // Sum the 3 older samples with 15-bit wrapping
27    let mut sum = (tables::GAUSSIAN[0x0FF - interpolation_idx] * oldest) >> 11;
28    sum += (tables::GAUSSIAN[0x1FF - interpolation_idx] * older) >> 11;
29    sum += (tables::GAUSSIAN[0x100 + interpolation_idx] * old) >> 11;
30
31    // Clip to 15 bits
32    sum = (((sum as i16) << 1) >> 1).into();
33
34    // Add in the current sample
35    sum += (tables::GAUSSIAN[interpolation_idx] * sample) >> 11;
36
37    // Clamp the final result to signed 15-bit
38    sum.clamp((i16::MIN >> 1).into(), (i16::MAX >> 1).into()) as i16
39}
40
41pub fn hermite(
42    InterpolateArgs { pitch_counter, oldest, older, old, sample }: InterpolateArgs,
43) -> i16 {
44    let samples = [oldest.into(), older.into(), old.into(), sample.into()];
45    let x = f64::from(pitch_counter & 0xFFF) / 4096.0;
46
47    let result = jgenesis_common::audio::interpolate_cubic_hermite_4p(samples, x);
48    result.round().clamp(I15_MIN, I15_MAX) as i16
49}