/* Copyright (C) 2023 Ardura This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. ##################################### Oscillator by Ardura This creates an oscillator mathematically with some modifiers I made myself. This is intended to be a building block used by other files in the Actuate synth. ##################################### */ use lazy_static::lazy_static; use nih_plug::params::enums::Enum; use rand::{rngs::StdRng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; use std::f32::consts::{self, FRAC_2_PI, PI}; // Make a lookup table for faster but less accurate sine approx for additive const TABLE_SIZE: usize = 512; lazy_static! { // Generating waveform tables static ref SIN_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; for i in 0..TABLE_SIZE { let phase = (i as f32 / TABLE_SIZE as f32) * std::f32::consts::PI * 2.0; table[i] = phase.sin(); } table }; static ref SAW_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Adjusted phase calculation // Calculate the sawtooth waveform directly table[i] = -1.0 + 2.0 * phase; } table }; // These are for creating an "analog saw" by rotating at random between WSAW tables static ref WSAW_TABLE_1: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(38); // Use a fixed seed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.1..0.1); // Adjust the range of randomness table[i] = -1.0 + 2.0 * (phase + randomness); } table }; static ref WSAW_TABLE_2: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(57); // Use a fixed seed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.1..0.1); // Adjust the range of randomness table[i] = -1.0 + 2.0 * (phase + randomness); } table }; // These are for creating an "analog saw" by rotating at random between WSAW tables static ref SSAW_TABLE_1: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(22); // Use a fixed seed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.01..0.01); // Adjust the range of randomness table[i] = -1.0 + 2.0 * (phase + randomness); } table }; static ref SSAW_TABLE_2: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(17); // Use a fixed seed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.01..0.01); // Adjust the range of randomness table[i] = -1.0 + 2.0 * (phase + randomness); } table }; // Combine the SSAW technique with the RSAW function // "A for Analog" static ref ASAW_TABLE_1: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(44); // Use a fixed seed let rounding_amount: i32 = 30; // Adjust the rounding amount as needed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.009..0.009); // Adjust the range of randomness let scaled_phase = -1.0 + 2.0 * (phase + randomness); // Calculate the rounded sawtooth waveform directly table[i] = scaled_phase * (1.0 - scaled_phase.powi(2 * rounding_amount)); } table }; static ref ASAW_TABLE_2: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(44); // Use a fixed seed let rounding_amount: i32 = 30; // Adjust the rounding amount as needed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.008..0.008); // Adjust the range of randomness let scaled_phase = -1.0 + 2.0 * (phase + randomness); // Calculate the rounded sawtooth waveform directly table[i] = scaled_phase * (1.0 - scaled_phase.powi(2 * rounding_amount)); } table }; static ref ASAW_TABLE_3: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mut rng = StdRng::seed_from_u64(44); // Use a fixed seed let rounding_amount: i32 = 32; // Adjust the rounding amount as needed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Introduce deterministic randomness to the waveform let randomness = rng.gen_range(-0.01..0.01); // Adjust the range of randomness let scaled_phase = -1.0 + 2.0 * (phase + randomness); // Calculate the rounded sawtooth waveform directly table[i] = scaled_phase * (1.0 - scaled_phase.powi(2 * rounding_amount)); } table }; static ref RSAW_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let rounding_amount: i32 = 15; // Adjust the rounding amount as needed for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; let scaled_phase = -1.0 + 2.0 * phase; // Calculate the rounded sawtooth waveform directly table[i] = scaled_phase * (1.0 - scaled_phase.powi(2 * rounding_amount)); } table }; static ref RAMP_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; let scaled_phase = -1.0 + 2.0 * phase; // Calculate the ramp wave directly table[i] = -scaled_phase % consts::TAU; } table }; static ref SQUARE_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Calculate the square wave directly if phase < 0.5 { table[i] = 1.0; // Positive phase half } else { table[i] = -1.0; // Negative phase half } } table }; static ref PULSE_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; // Calculate the pulse wave directly if phase < 0.25 { table[i] = 1.0; // Positive phase quarter } else { table[i] = -1.0; // Negative phase three-quarters } } table }; static ref RSQUARE_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; let mod_amount: f32 = 0.15; let mod_scaled: i32 = scale_range(mod_amount, 2.0, 8.0).floor() as i32 * 2; for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; let scaled_phase = -1.0 + 2.0 * phase; // Calculate the rounded square wave directly if scaled_phase < 0.0 { table[i] = (2.0 * scaled_phase + 1.0).powi(mod_scaled) - 1.0; } else { table[i] = -(2.0 * scaled_phase - 1.0).powi(mod_scaled) + 1.0; } } table }; static ref TRI_TABLE: [f32; TABLE_SIZE] = { let mut table = [0.0; TABLE_SIZE]; for i in 0..TABLE_SIZE { let phase = i as f32 / (TABLE_SIZE - 1) as f32; let tri = (FRAC_2_PI) * (((2.0 * PI) * phase).sin()).asin(); // Store the calculated triangle wave value in the table table[i] = tri; } table }; } #[derive(Enum, PartialEq, Eq, Debug, Copy, Clone, Serialize, Deserialize)] pub enum VoiceType { Sine, Tri, Saw, RSaw, WSaw, SSaw, RASaw, Ramp, Square, RSquare, Pulse, Noise, } #[derive(Enum, PartialEq, Eq, Debug, Copy, Clone)] pub enum OscState { Off, Attacking, Decaying, Sustaining, Releasing, } #[derive(Enum, PartialEq, Eq, Debug, Copy, Clone, Deserialize, Serialize)] pub enum SmoothStyle { Linear, Logarithmic, LogSteep, Exponential, } #[derive(Enum, PartialEq, Eq, Debug, Copy, Clone, Deserialize, Serialize)] pub enum RetriggerStyle { Free, Retrigger, Random, UniRandom, } // Super useful function to scale an input 0-1 into other ranges pub(crate) fn scale_range(input: f32, min_output: f32, max_output: f32) -> f32 { let scaled = input * (max_output - min_output) + min_output; scaled.clamp(min_output, max_output) } // Sine wave oscillator with lerp smoothing pub fn get_sine(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; let frac = phase * (TABLE_SIZE - 1) as f32 - index as f32; let next_index = index + 1; let sine = if next_index < TABLE_SIZE - 1 { SIN_TABLE[index] * (1.0 - frac) + SIN_TABLE[next_index] * frac } else { SIN_TABLE[index] // If next_index is out of bounds, use the current index }; sine } // Rounded Saw Wave with rounding amount pub fn get_rsaw(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return RSAW_TABLE[index]; } // Rounded Saw Wave with analog-ey modification pub fn get_rasaw(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; let mut rng = rand::thread_rng(); let random_int: u32 = rng.gen_range(0..=2); // Based on our int, use the three seed-noise tables match random_int { 0 => { return ASAW_TABLE_1[index]; } 1 => { return ASAW_TABLE_2[index]; } 2 => { return ASAW_TABLE_3[index]; } _ => { return 0.0; } } } // Saw Wave pub fn get_saw(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return SAW_TABLE[index]; } // "Analog" inspired "whiter" Saw wave pub fn get_wsaw(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; let mut rng = rand::thread_rng(); let random_bool: bool = rng.gen(); // Based on our random bool, obtain the Saw waveforms with seed-introduced randomness waveform tables if random_bool { return WSAW_TABLE_1[index]; } else { return WSAW_TABLE_2[index]; } } // "Analog" inspired "subtle warm" Saw wave pub fn get_ssaw(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; let mut rng = rand::thread_rng(); let random_bool: bool = rng.gen(); // Based on our random bool, obtain the Saw waveforms with seed-introduced randomness waveform tables if random_bool { return SSAW_TABLE_1[index]; } else { return SSAW_TABLE_2[index]; } } // Ramp Wave pub fn get_ramp(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return RAMP_TABLE[index]; } // Square Wave pub fn get_square(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return SQUARE_TABLE[index]; } // 1/4 Pulse Wave pub fn get_pulse(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return PULSE_TABLE[index]; } pub fn get_rsquare(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return RSQUARE_TABLE[index]; } pub fn get_tri(phase: f32) -> f32 { let index = (phase * (TABLE_SIZE - 1) as f32) as usize; return TRI_TABLE[index]; } // Bard helped me out on this one #[derive(Clone)] pub struct DeterministicWhiteNoiseGenerator { seed: u64, } impl DeterministicWhiteNoiseGenerator { pub fn new(seed: u64) -> Self { // Magic number seed I made up to have same noise pattern every time DeterministicWhiteNoiseGenerator { seed } } pub fn generate_sample(&mut self) -> f32 { let random_value = self.xorshift(); // Scale the random value to be between -1.0 and 1.0 let sample = (random_value as f32 / u64::MAX as f32) * 2.0 - 1.0; sample } fn xorshift(&mut self) -> u64 { let mut x = self.seed; x ^= x << 21; x ^= x >> 35; x ^= x << 4; self.seed = x; x } }