-
-
Save jamesmunns/af57e642fdf378160ffbc00c2e791274 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
// A pre-calculated Sine Look Up Table, or LUT. | |
use crate::SINE_TABLE; // : [i16; 256]; | |
// We have a sample rate at 44.1kHz. Figure out how many samples it will take | |
// to play a single loop of our sine wave. For example, at 441hz, it will take | |
// 100 samples to "traverse" one whole sine wave. | |
let samp_per_cyc: f32 = 44100.0 / 441.0; | |
// The increment is (how many items are in our look up table) | |
// divided by (how long it takes to go through one look up table) | |
let fincr = 256.0 / samp_per_cyc; | |
// Convert this number to an i32, which we use as a fixed point decimal, basically | |
// 8bits.24bits, where the 8 bits are which LUT position we are currently in, and | |
// the 24 bits are like a "decimal" describing where we are between the LUT positions | |
let incr: i32 = (((1 << 24) as f32) * fincr) as i32; | |
// This calculation is based on the current "phase angle" of the sine table. | |
// This works because we increment our progress through the 256 value in | |
// our 8.24 fixed point number, and it will wrap around correctly whenever | |
// we "overflow" (this is a good thing! our sine table is the same loop over | |
// and over and over and... | |
let mut cur_offset = 0i32; | |
// generate the next N samples... | |
idata.chunks_exact_mut(2).for_each(|i| { | |
let val = cur_offset as u32; | |
// Mask off the top 8 bits. This tells us which LUT position we are in | |
let idx_now = ((val >> 24) & 0xFF) as u8; | |
// Add one (wrapping), to tell us what the NEXT LUT position is. | |
let idx_nxt = idx_now.wrapping_add(1); | |
// Get the i16 value of the two LUT data points | |
let base_val = SINE_TABLE[idx_now as usize] as i32; | |
let next_val = SINE_TABLE[idx_nxt as usize] as i32; | |
// Distance to next value - perform 256 slot linear interpolation | |
// Here, I take the top 8 bits of the 24 bit "decimal" part of the number. | |
// This will be used to interpolate one of 256 positions between the two | |
// LUT positions. This is to reduce error between the "steps" of each LUT | |
// value in our table | |
let off = ((val >> 16) & 0xFF) as i32; // 0..=255 | |
// Here, we "weight" each sample based on how close we are at. We multiply | |
// this to a total of 256x larger than our original sample, split between | |
// how close we are between the two samples. We multiply each, then add them | |
// back together | |
let cur_weight = base_val.wrapping_mul(256i32.wrapping_sub(off)); | |
let nxt_weight = next_val.wrapping_mul(off); | |
let ttl_weight = cur_weight.wrapping_add(nxt_weight); | |
// Our number is now the weighted average of the two LUT samples, but 256x too big. | |
// Reduce it down with a right shift operation. | |
let ttl_val = ttl_weight >> 8; // div 256 | |
// Un-sign-extend this back to an i16, to use as a sample | |
let ttl_val = ttl_val as i16; | |
// Set the linearly interpolated value to the left and right channel | |
i.iter_mut().for_each(|i| *i = ttl_val); | |
// Adjust our phase angle by the "increment" we calculated earlier. | |
cur_offset = cur_offset.wrapping_add(incr); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment