Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
7.1 kB · 206 lines
Rust
at sans-io
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207use std::{num::NonZero, ops::ControlFlow};
use crate::{ project::note_event::Note, sample::{ProcessingFrame, ProcessingFunction, Sample, SampleMetaData},};
use super::Frame;
#[repr(u8)]// quadratic is probably enough i can't hear it anymore#[derive(Copy, Clone, Debug)]pub enum Interpolation { Nearest = 0, Linear = 1, Quadratic = 2,}
impl From<u8> for Interpolation { fn from(value: u8) -> Self { Self::from_u8(value) }}
impl Interpolation { /// Amount of Padding in the SampleData to do each type of Interpolation. /// This much padding is needed at the start and end of the sample. pub const fn pad_needed(&self) -> usize { match self { Interpolation::Nearest => 1, Interpolation::Linear => 1, Interpolation::Quadratic => 2, } }
pub const fn from_u8(value: u8) -> Self { match value { 0 => Self::Nearest, 1 => Self::Linear, 2 => Self::Quadratic, _ => panic!(), } }}
#[derive(Debug)]pub struct SamplePlayer { sample: Sample, meta: SampleMetaData,
note: Note, // position in the sample, the next output frame should be. // Done this way, so 0 is a valid, useful and intuitive value // always a valid position in the sample. checked against sample lenght on each change // stored as fixed point data: usize + f32 // f32 ranges 0..1 position: (usize, f32), // is_done: bool, out_rate: NonZero<u32>, // how much the position is advanced for each output sample. // computed from in and out rate step_size: f32,}
impl SamplePlayer { pub fn new(sample: Sample, meta: SampleMetaData, out_rate: NonZero<u32>, note: Note) -> Self { let step_size = Self::compute_step_size(meta.sample_rate, out_rate, meta.base_note, note); Self { sample, meta, position: (Sample::PAD_SIZE_EACH, 0.), out_rate, step_size, note, } }
pub fn check_position(&self) -> ControlFlow<()> { if self.position.0 > self.sample.len_with_pad() - Sample::PAD_SIZE_EACH { ControlFlow::Break(()) } else { ControlFlow::Continue(()) } }
#[inline] fn compute_step_size( in_rate: NonZero<u32>, out_rate: NonZero<u32>, sample_base_note: Note, playing_note: Note, ) -> f32 { // original formula: (outrate / inrate) * (playing_freq / sample_base_freq). // Where each freq is computed with MIDI tuning standard formula: 440 * 2^((note - 69)/12) // manually reduced formula: 2^((play_note - sample_base_note)/12) * (outrate / inrate) // herbie (https://herbie.uwplse.org/demo/index.html) can't optimize further: https://herbie.uwplse.org/demo/e096ef89ee257ad611dd56378bd139a065a6bea0.02e7ec5a3709ad3e06968daa97db50d636f1e44b/graph.html (f32::from(i16::from(playing_note.get()) - i16::from(sample_base_note.get())) / 12.).exp2() * (out_rate.get() as f32 / in_rate.get() as f32) }
fn set_step_size(&mut self) { self.step_size = Self::compute_step_size( self.meta.sample_rate, self.out_rate, self.meta.base_note, self.note, ); }
pub fn set_out_samplerate(&mut self, samplerate: NonZero<u32>) { self.out_rate = samplerate; self.set_step_size(); }
/// steps self and sets is_done if needed fn step(&mut self) { self.position.1 += self.step_size; let floor = self.position.1.trunc(); self.position.1 -= floor; self.position.0 += floor as usize; }
pub fn iter<const INTERPOLATION: u8>(&mut self) -> impl Iterator<Item = Frame> { std::iter::from_fn(|| self.next::<INTERPOLATION>()) }
pub fn next<const INTERPOLATION: u8>(&mut self) -> Option<Frame> { // const block allows turning an invalid u8 into compile time error let interpolation = const { Interpolation::from_u8(INTERPOLATION) };
if self.check_position().is_break() { return None; }
let out = match interpolation { Interpolation::Nearest => self.compute_nearest(), Interpolation::Linear => self.compute_linear(), Interpolation::Quadratic => self.compute_quadratic(), };
self.step(); Some(out) }
fn compute_linear(&mut self) -> Frame { // There are two types that implement ProcessingFrame: f32 and Frame, so stereo and mono audio data. // the compiler will monomorphize this function to both versions and depending on wether that sample is mono // or stereo the correct version will be called. struct Linear; impl<S: ProcessingFrame> ProcessingFunction<2, S> for Linear { fn process(pos: f32, data: &[S; 2]) -> S { let diff = data[1] - data[0]; (diff * pos) + data[0] } } self.sample.compute::<2, Linear>(self.position) }
fn compute_nearest(&mut self) -> Frame { let load_idx = if self.position.1 < 0.5 { self.position.0 } else { self.position.0 + 1 };
self.sample.index(load_idx) }
// need to hear it on a better system. With standard output i can't hear a difference. // maybe also look at the waveforms fn compute_quadratic(&mut self) -> Frame { struct Quadratic; impl<S: ProcessingFrame> ProcessingFunction<3, S> for Quadratic { fn process(pos: f32, data: &[S; 3]) -> S { // let y0_half = data[0] / 2.; // let y2_half = data[2] / 2.; // let y1 = data[1];
// (y0_half - y1 + y2_half) * pos * pos + (y2_half - y0_half) * pos + y1 // (data[0] / 2 - data[1] + data[2] / 2) * pos * pos + (data[2] / 2- data[0] / 2) * pos + y1 // https://herbie.uwplse.org/demo/e824f96dd380ac5390d6cb0362398b0e9defed73.0cc3b6492c83efca5bd11399e0830e7873c749d9/graph.html // alternative 1, accuracy 100%, 1.3x speed // using fused multiply add can be faster and more accurate // S::mul_add( // (data[2] - data[0]) / 2. - data[1], // pos * pos, // S::mul_add((data[2] - data[0]) * pos, 0.5, data[1]), // ) // // alternative 2, accuracy 100%, 1.5x speed // even alternative 3 with 98.5% accuracy sounds really bad S::mul_add( S::mul_add( (data[2] + data[0]) * 0.5 - data[1], pos, (data[0] - data[2]) * -0.5, ), pos, data[1], ) } }
self.sample.compute::<3, Quadratic>(self.position) }}