old school music tracker audio backend
at main 175 lines 5.4 kB view raw
1use std::{num::NonZero, ops::ControlFlow}; 2 3use crate::{ 4 project::note_event::Note, 5 sample::{ProcessingFrame, ProcessingFunction, Sample, SampleMetaData}, 6}; 7 8use super::Frame; 9 10#[repr(u8)] 11pub enum Interpolation { 12 Nearest = 0, 13 Linear = 1, 14} 15 16impl From<u8> for Interpolation { 17 fn from(value: u8) -> Self { 18 Self::from_u8(value) 19 } 20} 21 22impl Interpolation { 23 /// Amount of Padding in the SampleData to do each type of Interpolation. 24 /// This much padding is needed at the start and end of the sample. 25 pub const fn pad_needed(&self) -> usize { 26 match self { 27 Interpolation::Nearest => 1, 28 Interpolation::Linear => 1, 29 } 30 } 31 32 pub const fn from_u8(value: u8) -> Self { 33 match value { 34 0 => Self::Nearest, 35 1 => Self::Linear, 36 _ => panic!(), 37 } 38 } 39} 40 41#[derive(Debug)] 42pub struct SamplePlayer { 43 sample: Sample, 44 meta: SampleMetaData, 45 46 note: Note, 47 // position in the sample, the next output frame should be. 48 // Done this way, so 0 is a valid, useful and intuitive value 49 // always a valid position in the sample. checked against sample lenght on each change 50 // stored as fixed point data: usize + f32 51 // f32 ranges 0..1 52 position: (usize, f32), 53 // is_done: bool, 54 out_rate: NonZero<u32>, 55 // how much the position is advanced for each output sample. 56 // computed from in and out rate 57 step_size: f32, 58} 59 60impl SamplePlayer { 61 pub fn new(sample: Sample, meta: SampleMetaData, out_rate: NonZero<u32>, note: Note) -> Self { 62 let step_size = Self::compute_step_size(meta.sample_rate, out_rate, meta.base_note, note); 63 Self { 64 sample, 65 meta, 66 position: (Sample::PAD_SIZE_EACH, 0.), 67 out_rate, 68 step_size, 69 note, 70 } 71 } 72 73 pub fn check_position(&self) -> ControlFlow<()> { 74 if self.position.0 > self.sample.len_with_pad() - Sample::PAD_SIZE_EACH { 75 ControlFlow::Break(()) 76 } else { 77 ControlFlow::Continue(()) 78 } 79 } 80 81 #[inline] 82 fn compute_step_size( 83 in_rate: NonZero<u32>, 84 out_rate: NonZero<u32>, 85 sample_base_note: Note, 86 playing_note: Note, 87 ) -> f32 { 88 // original formula: (outrate / inrate) * (playing_freq / sample_base_freq). 89 // Where each freq is computed with MIDI tuning standard formula: 440 * 2^((note - 69)/12) 90 // manually reduced formula: 2^((play_note - sample_base_note)/12) * (outrate / inrate) 91 // herbie (https://herbie.uwplse.org/demo/index.html) can't optimize further: https://herbie.uwplse.org/demo/e096ef89ee257ad611dd56378bd139a065a6bea0.02e7ec5a3709ad3e06968daa97db50d636f1e44b/graph.html 92 (f32::from(i16::from(playing_note.get()) - i16::from(sample_base_note.get())) / 12.).exp2() 93 * (out_rate.get() as f32 / in_rate.get() as f32) 94 } 95 96 fn set_step_size(&mut self) { 97 self.step_size = Self::compute_step_size( 98 self.meta.sample_rate, 99 self.out_rate, 100 self.meta.base_note, 101 self.note, 102 ); 103 } 104 105 pub fn set_out_samplerate(&mut self, samplerate: NonZero<u32>) { 106 self.out_rate = samplerate; 107 self.set_step_size(); 108 } 109 110 /// steps self and sets is_done if needed 111 fn step(&mut self) { 112 self.position.1 += self.step_size; 113 let floor = self.position.1.trunc(); 114 self.position.1 -= floor; 115 self.position.0 += floor as usize; 116 } 117 118 pub fn iter<const INTERPOLATION: u8>(&mut self) -> SampleIter<'_, INTERPOLATION> { 119 SampleIter { inner: self } 120 } 121 122 pub fn next<const INTERPOLATION: u8>(&mut self) -> Option<Frame> { 123 // const block allows turning an invalid u8 into compile time error 124 let interpolation = const { Interpolation::from_u8(INTERPOLATION) }; 125 126 if self.check_position().is_break() { 127 return None; 128 } 129 130 let out = match interpolation { 131 Interpolation::Nearest => self.compute_nearest(), 132 Interpolation::Linear => self.compute_linear(), 133 }; 134 135 self.step(); 136 Some(out) 137 } 138 139 fn compute_linear(&mut self) -> Frame { 140 // There are two types that implement ProcessingFrame: f32 and Frame, so stereo and mono audio data. 141 // the compiler will monomorphize this function to both versions and depending on wether that sample is mono 142 // or stereo the correct version will be called. 143 struct Linear(f32); 144 impl<S: ProcessingFrame> ProcessingFunction<2, S> for Linear { 145 fn process(self, data: &[S; 2]) -> S { 146 let diff = data[1] - data[0]; 147 (diff * self.0) + data[0] 148 } 149 } 150 self.sample 151 .compute(self.position.0, Linear(self.position.1)) 152 } 153 154 fn compute_nearest(&mut self) -> Frame { 155 let load_idx = if self.position.1 < 0.5 { 156 self.position.0 157 } else { 158 self.position.0 + 1 159 }; 160 161 self.sample.index(load_idx) 162 } 163} 164 165pub struct SampleIter<'player, const INTERPOLATION: u8> { 166 inner: &'player mut SamplePlayer, 167} 168 169impl<const INTERPOLATION: u8> Iterator for SampleIter<'_, INTERPOLATION> { 170 type Item = Frame; 171 172 fn next(&mut self) -> Option<Self::Item> { 173 self.inner.next::<INTERPOLATION>() 174 } 175}