Implemented VPS oscillator and adapted some oversampling code
This commit is contained in:
parent
3d140b2ead
commit
32b2725f49
3 changed files with 410 additions and 0 deletions
226
src/dsp/biquad.rs
Normal file
226
src/dsp/biquad.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
|
||||||
|
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
||||||
|
// See README.md and COPYING for details.
|
||||||
|
//
|
||||||
|
// The implementation of this Biquad Filter has been adapted from
|
||||||
|
// SamiPerttu, Copyright (c) 2020, under the MIT License.
|
||||||
|
// See also: https://github.com/SamiPerttu/fundsp/blob/master/src/filter.rs
|
||||||
|
//
|
||||||
|
// You will find a float type agnostic version in SamiPerttu's code.
|
||||||
|
// I converted this to pure f32 for no good reason, other than making
|
||||||
|
// the code more readable (for me).
|
||||||
|
|
||||||
|
use std::f32::consts::*;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct BiquadCoefs {
|
||||||
|
pub a1: f32,
|
||||||
|
pub a2: f32,
|
||||||
|
pub b0: f32,
|
||||||
|
pub b1: f32,
|
||||||
|
pub b2: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BiquadCoefs {
|
||||||
|
/// Returns settings for a Butterworth lowpass filter.
|
||||||
|
/// Cutoff is the -3 dB point of the filter in Hz.
|
||||||
|
#[inline]
|
||||||
|
pub fn butter_lowpass(sample_rate: f32, cutoff: f32) -> BiquadCoefs {
|
||||||
|
let f = (cutoff * PI / sample_rate).tan();
|
||||||
|
let a0r = 1.0 / (1.0 + SQRT_2 * f + f * f);
|
||||||
|
let a1 = (2.0 * f * f - 2.0) * a0r;
|
||||||
|
let a2 = (1.0 - SQRT_2 * f + f * f) * a0r;
|
||||||
|
let b0 = f * f * a0r;
|
||||||
|
let b1 = 2.0 * b0;
|
||||||
|
let b2 = b0;
|
||||||
|
BiquadCoefs { a1, a2, b0, b1, b2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns settings for a constant-gain bandpass resonator.
|
||||||
|
/// The center frequency is given in Hz.
|
||||||
|
/// Bandwidth is the difference in Hz between -3 dB points of the filter response.
|
||||||
|
/// The overall gain of the filter is independent of bandwidth.
|
||||||
|
pub fn resonator(sample_rate: f32, center: f32, bandwidth: f32) -> BiquadCoefs {
|
||||||
|
let r = (-PI * bandwidth / sample_rate).exp();
|
||||||
|
let a1 = -2.0 * r * (TAU * center / sample_rate).cos();
|
||||||
|
let a2 = r * r;
|
||||||
|
let b0 = (1.0 - r * r).sqrt() * 0.5;
|
||||||
|
let b1 = 0.0;
|
||||||
|
let b2 = -b0;
|
||||||
|
BiquadCoefs { a1, a2, b0, b1, b2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Frequency response at frequency `omega` expressed as fraction of sampling rate.
|
||||||
|
// pub fn response(&self, omega: f64) -> Complex64 {
|
||||||
|
// let z1 = Complex64::from_polar(1.0, -TAU * omega);
|
||||||
|
// let z2 = Complex64::from_polar(1.0, -2.0 * TAU * omega);
|
||||||
|
// (re(self.b0) + re(self.b1) * z1 + re(self.b2) * z2)
|
||||||
|
// / (re(1.0) + re(self.a1) * z1 + re(self.a2) * z2)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 2nd order IIR filter implemented in normalized Direct Form I.
|
||||||
|
#[derive(Copy, Clone, Default)]
|
||||||
|
pub struct Biquad {
|
||||||
|
coefs: BiquadCoefs,
|
||||||
|
x1: f32,
|
||||||
|
x2: f32,
|
||||||
|
y1: f32,
|
||||||
|
y2: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Biquad {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn coefs(&self) -> &BiquadCoefs {
|
||||||
|
&self.coefs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_coefs(&mut self, coefs: BiquadCoefs) {
|
||||||
|
self.coefs = coefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.x1 = 0.0;
|
||||||
|
self.x2 = 0.0;
|
||||||
|
self.y1 = 0.0;
|
||||||
|
self.y2 = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tick(&mut self, input: f32) -> f32 {
|
||||||
|
let x0 = input;
|
||||||
|
let y0 =
|
||||||
|
self.coefs.b0 * x0
|
||||||
|
+ self.coefs.b1 * self.x1
|
||||||
|
+ self.coefs.b2 * self.x2
|
||||||
|
- self.coefs.a1 * self.y1
|
||||||
|
- self.coefs.a2 * self.y2;
|
||||||
|
self.x2 = self.x1;
|
||||||
|
self.x1 = x0;
|
||||||
|
self.y2 = self.y1;
|
||||||
|
self.y1 = y0;
|
||||||
|
y0
|
||||||
|
|
||||||
|
// Transposed Direct Form II would be:
|
||||||
|
// y0 = b0 * x0 + s1
|
||||||
|
// s1 = s2 + b1 * x0 - a1 * y0
|
||||||
|
// s2 = b2 * x0 - a2 * y0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct ButterLowpass {
|
||||||
|
biquad: Biquad,
|
||||||
|
sample_rate: f32,
|
||||||
|
cutoff: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButterLowpass {
|
||||||
|
pub fn new(sample_rate: f32, cutoff: f32) -> Self {
|
||||||
|
let mut this = ButterLowpass {
|
||||||
|
biquad: Biquad::new(),
|
||||||
|
sample_rate,
|
||||||
|
cutoff: 0.0,
|
||||||
|
};
|
||||||
|
this.set_cutoff(cutoff);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cutoff(&mut self, cutoff: f32) {
|
||||||
|
self.biquad
|
||||||
|
.set_coefs(BiquadCoefs::butter_lowpass(self.sample_rate, cutoff));
|
||||||
|
self.cutoff = cutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
self.sample_rate = srate;
|
||||||
|
self.reset();
|
||||||
|
self.biquad.reset();
|
||||||
|
self.set_cutoff(self.cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.biquad.reset();
|
||||||
|
self.set_cutoff(self.cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn tick(&mut self, input: f32) -> f32 {
|
||||||
|
self.biquad.tick(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loosely adapted from https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp
|
||||||
|
// Copyright (c) 2019-2020 Andrew Belt and Befaco contributors
|
||||||
|
// Under GPLv-3.0-or-later
|
||||||
|
//
|
||||||
|
// Which was originally taken from https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp
|
||||||
|
// Copyright (c) 2020 jatinchowdhury18
|
||||||
|
/// Implements oversampling with a ratio of 4 and a 4 times cascade
|
||||||
|
/// of Butterworth lowpass filters.
|
||||||
|
struct Oversampling4x4 {
|
||||||
|
filters: [Biquad; 4],
|
||||||
|
buffer: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oversampling4x4 {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut this = Self {
|
||||||
|
filters: [Biquad::new(); 4],
|
||||||
|
buffer: [0.0; 4],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.set_sample_rate(44100.0);
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.buffer = [0.0; 4];
|
||||||
|
for filt in &mut self.filters {
|
||||||
|
filt.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
let cutoff = 0.98 * (srate / 2.0);
|
||||||
|
|
||||||
|
for filt in &mut self.filters {
|
||||||
|
filt.set_coefs(BiquadCoefs::butter_lowpass(srate, cutoff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn upsample(&mut self, v: f32) {
|
||||||
|
self.buffer[0] = 4.0 * v;
|
||||||
|
self.buffer[1] = 0.0;
|
||||||
|
self.buffer[2] = 0.0;
|
||||||
|
self.buffer[3] = 0.0;
|
||||||
|
|
||||||
|
for s in &mut self.buffer {
|
||||||
|
for filt in &mut self.filters {
|
||||||
|
*s = filt.tick(*s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn resample_buffer(&mut self) -> &mut [f32; 4] { &mut self.buffer }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn downsample(&mut self) -> f32 {
|
||||||
|
let mut ret = 0.0;
|
||||||
|
for s in &mut self.buffer {
|
||||||
|
for filt in &mut self.filters {
|
||||||
|
ret = filt.tick(*s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,10 @@ mod node_sfilter;
|
||||||
mod node_mix3;
|
mod node_mix3;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
mod node_bosc;
|
mod node_bosc;
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
mod node_vosc;
|
||||||
|
|
||||||
|
pub mod biquad;
|
||||||
pub mod tracker;
|
pub mod tracker;
|
||||||
mod satom;
|
mod satom;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
|
@ -82,6 +85,7 @@ use node_smap::SMap;
|
||||||
use node_sfilter::SFilter;
|
use node_sfilter::SFilter;
|
||||||
use node_mix3::Mix3;
|
use node_mix3::Mix3;
|
||||||
use node_bosc::BOsc;
|
use node_bosc::BOsc;
|
||||||
|
use node_vosc::VOsc;
|
||||||
|
|
||||||
pub const MIDI_MAX_FREQ : f32 = 13289.75;
|
pub const MIDI_MAX_FREQ : f32 = 13289.75;
|
||||||
|
|
||||||
|
@ -399,6 +403,15 @@ macro_rules! r_fq { ($x: expr, $coarse: expr) => {
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
/// The rounding function for vs (v scale) UI knobs
|
||||||
|
macro_rules! r_vps { ($x: expr, $coarse: expr) => {
|
||||||
|
if $coarse {
|
||||||
|
n_vps!((d_vps!($x)).round())
|
||||||
|
} else {
|
||||||
|
n_vps!((d_vps!($x) * 10.0).round() / 10.0)
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
|
||||||
/// The default steps function:
|
/// The default steps function:
|
||||||
macro_rules! stp_d { () => { (20.0, 100.0) } }
|
macro_rules! stp_d { () => { (20.0, 100.0) } }
|
||||||
/// The UI steps to control parameters with a finer fine control:
|
/// The UI steps to control parameters with a finer fine control:
|
||||||
|
@ -414,6 +427,11 @@ macro_rules! f_def { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||||
write!($formatter, "{:6.3}", $denorm_v)
|
write!($formatter, "{:6.3}", $denorm_v)
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
// Default formatting function with very low precision
|
||||||
|
macro_rules! f_defvlp { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||||
|
write!($formatter, "{:4.1}", $denorm_v)
|
||||||
|
} }
|
||||||
|
|
||||||
macro_rules! f_freq { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
macro_rules! f_freq { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||||
if ($denorm_v >= 1000.0) {
|
if ($denorm_v >= 1000.0) {
|
||||||
write!($formatter, "{:6.0}Hz", $denorm_v)
|
write!($formatter, "{:6.0}Hz", $denorm_v)
|
||||||
|
@ -448,6 +466,7 @@ macro_rules! f_det { ($formatter: expr, $v: expr, $denorm_v: expr) => {
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
|
|
||||||
// norm-fun denorm-min
|
// norm-fun denorm-min
|
||||||
// denorm-fun denorm-max
|
// denorm-fun denorm-max
|
||||||
define_exp!{n_gain d_gain 0.0, 2.0}
|
define_exp!{n_gain d_gain 0.0, 2.0}
|
||||||
|
@ -464,6 +483,8 @@ define_exp!{n_ftme d_ftme 0.25, 1000.0}
|
||||||
// to reach more exact "1.0".
|
// to reach more exact "1.0".
|
||||||
define_lin!{n_ogin d_ogin 0.0, 2.0}
|
define_lin!{n_ogin d_ogin 0.0, 2.0}
|
||||||
|
|
||||||
|
define_lin!{n_vps d_vps 1.0, 10.0}
|
||||||
|
|
||||||
// A note about the input-indicies:
|
// A note about the input-indicies:
|
||||||
//
|
//
|
||||||
// Atoms and Input parameters share the same global ID space
|
// Atoms and Input parameters share the same global ID space
|
||||||
|
@ -558,6 +579,13 @@ macro_rules! node_list {
|
||||||
(2 pw n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
(2 pw n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||||
{3 0 wtype setting(0) fa_bosc_wtype 0 3}
|
{3 0 wtype setting(0) fa_bosc_wtype 0 3}
|
||||||
[0 sig],
|
[0 sig],
|
||||||
|
vosc => VOsc UIType::Generic UICategory::Osc
|
||||||
|
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
|
||||||
|
(1 det n_det d_det r_det f_det stp_f -0.2, 0.2, 0.0)
|
||||||
|
(2 d n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||||
|
(3 v n_id n_id r_id f_def stp_d 0.0, 1.0, 0.5)
|
||||||
|
(4 vs n_vps d_vps r_vps f_defvlp stp_d 0.0, 1.0, 1.0)
|
||||||
|
[0 sig],
|
||||||
out => Out UIType::Generic UICategory::IOUtil
|
out => Out UIType::Generic UICategory::IOUtil
|
||||||
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
(0 ch1 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
(1 ch2 n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)
|
||||||
|
|
156
src/dsp/node_vosc.rs
Normal file
156
src/dsp/node_vosc.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
// Copyright (c) 2021 Weird Constructor <weirdconstructor@gmail.com>
|
||||||
|
// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
|
||||||
|
// See README.md and COPYING for details.
|
||||||
|
|
||||||
|
use crate::nodes::{NodeAudioContext, NodeExecContext};
|
||||||
|
use crate::dsp::{
|
||||||
|
NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals, NodeContext,
|
||||||
|
GraphAtomData, GraphFun,
|
||||||
|
};
|
||||||
|
|
||||||
|
//#[macro_export]
|
||||||
|
//macro_rules! fa_bosc_wtype { ($formatter: expr, $v: expr, $denorm_v: expr) => { {
|
||||||
|
// let s =
|
||||||
|
// match ($v.round() as usize) {
|
||||||
|
// 0 => "Sin",
|
||||||
|
// 1 => "Tri",
|
||||||
|
// 2 => "Saw",
|
||||||
|
// 3 => "Pulse",
|
||||||
|
// _ => "?",
|
||||||
|
// };
|
||||||
|
// write!($formatter, "{}", s)
|
||||||
|
//} } }
|
||||||
|
|
||||||
|
/// A simple amplifier
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VOsc {
|
||||||
|
// osc: PolyBlepOscillator,
|
||||||
|
israte: f32,
|
||||||
|
phase: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VOsc {
|
||||||
|
pub fn new(nid: &NodeId) -> Self {
|
||||||
|
let init_phase = nid.init_phase();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
israte: 1.0 / 44100.0,
|
||||||
|
phase: init_phase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const freq : &'static str =
|
||||||
|
"VOsc freq\nBase frequency of the oscillator.\n\nRange: (-1..1)\n";
|
||||||
|
pub const det : &'static str =
|
||||||
|
"VOsc det\nDetune the oscillator in semitones and cents. \
|
||||||
|
the input of this value is rounded to semitones on coarse input. \
|
||||||
|
Fine input lets you detune in cents (rounded). \
|
||||||
|
A signal sent to this port is not rounded.\n\
|
||||||
|
Note: The signal input allows detune +-10 octaves.\
|
||||||
|
\nRange: (Knob -0.2 .. 0.2) / (Signal -1.0 .. 1.0)\n";
|
||||||
|
pub const d : &'static str =
|
||||||
|
"VOsc d\n\nRange: (0..1)\n";
|
||||||
|
pub const v : &'static str =
|
||||||
|
"VOsc v\n\nRange: (0..1)\n";
|
||||||
|
pub const vs : &'static str =
|
||||||
|
"VOsc vs\nScaling factor for 'v'.\nRange: (0..1)\n";
|
||||||
|
pub const wtype : &'static str =
|
||||||
|
"VOsc wtype\nWaveform type\nAvailable waveforms:\n\
|
||||||
|
Sin - Sine Waveform\n\
|
||||||
|
Tri - Triangle Waveform\n\
|
||||||
|
Saw - Sawtooth Waveform\n\
|
||||||
|
Pulse - Pulse Waveform with configurable pulse width";
|
||||||
|
pub const sig : &'static str =
|
||||||
|
"VOsc sig\nOscillator output\nRange: (-1..1)\n";
|
||||||
|
pub const DESC : &'static str =
|
||||||
|
r#"V Oscillator
|
||||||
|
|
||||||
|
A vector phase shaping oscillator, to create interesting waveforms and
|
||||||
|
ways to manipulate them.
|
||||||
|
"#;
|
||||||
|
pub const HELP : &'static str =
|
||||||
|
r#"VOsc - Vector Phase Shaping Oscillator
|
||||||
|
|
||||||
|
A vector phase shaping oscillator, to create interesting waveforms and
|
||||||
|
ways to manipulate them.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn s(p: f32) -> f32 {
|
||||||
|
-(std::f32::consts::TAU * p).cos()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn phi_vps(x: f32, v: f32, d: f32) -> f32 {
|
||||||
|
if x < d {
|
||||||
|
(v * x) / d
|
||||||
|
} else {
|
||||||
|
v + ((1.0 - v) * (x - d))/(1.0 - d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DspNode for VOsc {
|
||||||
|
fn outputs() -> usize { 1 }
|
||||||
|
|
||||||
|
fn set_sample_rate(&mut self, srate: f32) {
|
||||||
|
self.israte = 1.0 / srate;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.phase = 0.0;
|
||||||
|
// self.osc.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn process<T: NodeAudioContext>(
|
||||||
|
&mut self, ctx: &mut T, _ectx: &mut NodeExecContext,
|
||||||
|
_nctx: &NodeContext,
|
||||||
|
atoms: &[SAtom], inputs: &[ProcBuf],
|
||||||
|
outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals)
|
||||||
|
{
|
||||||
|
use crate::dsp::{out, inp, denorm, denorm_offs, at};
|
||||||
|
|
||||||
|
let freq = inp::VOsc::freq(inputs);
|
||||||
|
let det = inp::VOsc::det(inputs);
|
||||||
|
let d = inp::VOsc::d(inputs);
|
||||||
|
let v = inp::VOsc::v(inputs);
|
||||||
|
let vs = inp::VOsc::vs(inputs);
|
||||||
|
let out = out::VOsc::sig(outputs);
|
||||||
|
|
||||||
|
let israte = self.israte;
|
||||||
|
|
||||||
|
for frame in 0..ctx.nframes() {
|
||||||
|
let freq = denorm_offs::VOsc::freq(freq, det.read(frame), frame);
|
||||||
|
let v = denorm::VOsc::v(v, frame);
|
||||||
|
let d = denorm::VOsc::d(d, frame);
|
||||||
|
let vs = denorm::VOsc::vs(vs, frame);
|
||||||
|
|
||||||
|
let s = s(phi_vps(self.phase, v * vs, d));
|
||||||
|
out.write(frame, s);
|
||||||
|
|
||||||
|
self.phase += freq * israte;
|
||||||
|
self.phase = self.phase.fract();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx_vals[0].set(out.read(ctx.nframes() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn graph_fun() -> Option<GraphFun> {
|
||||||
|
let israte = 1.0 / 128.0;
|
||||||
|
|
||||||
|
Some(Box::new(move |gd: &dyn GraphAtomData, _init: bool, x: f32, _xn: f32| -> f32 {
|
||||||
|
let v = NodeId::VOsc(0).inp_param("v").unwrap().inp();
|
||||||
|
let vs = NodeId::VOsc(0).inp_param("vs").unwrap().inp();
|
||||||
|
let d = NodeId::VOsc(0).inp_param("d").unwrap().inp();
|
||||||
|
|
||||||
|
let v = gd.get_denorm(v as u32);
|
||||||
|
let vs = gd.get_denorm(vs as u32);
|
||||||
|
let d = gd.get_denorm(d as u32);
|
||||||
|
|
||||||
|
let s = s(phi_vps(x, v * vs, d));
|
||||||
|
(s + 1.0) * 0.5
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue