use rill_core::traits::{ActionContext, Algorithm};
use rill_core::{
SignalNode, Transcendental, NodeCategory, NodeId, NodeMetadata, NodeState, ParamValue, ParameterId,
Port, ProcessError, ProcessResult, Processor,
};
use rill_core_dsp::algorithm::ParameterizedAlgorithm;
use rill_core_dsp::filters::{Biquad, FilterParams, FilterType};
pub struct BiquadProcessor<T: Transcendental, const BUF_SIZE: usize> {
id: NodeId,
metadata: NodeMetadata,
inputs: Vec<Port<T, BUF_SIZE>>,
outputs: Vec<Port<T, BUF_SIZE>>,
controls: Vec<Port<T, BUF_SIZE>>,
state: NodeState<T, BUF_SIZE>,
pub cutoff: f32,
pub q: f32,
pub gain_db: f32,
pub filter_type: FilterType,
pub algorithm: Biquad<T>,
}
impl<T: Transcendental, const BUF_SIZE: usize> BiquadProcessor<T, BUF_SIZE> {
pub fn new(sample_rate: f32) -> Self {
let metadata = NodeMetadata::new("BiquadProcessor", NodeCategory::Processor);
let mut inputs = Vec::new();
let mut outputs = Vec::new();
inputs.push(Port::input(NodeId(0), 0, "signal_in"));
outputs.push(Port::output(NodeId(0), 0, "signal_out"));
let params = FilterParams {
filter_type: FilterType::LowPass,
cutoff: 1000.0,
q: 0.707,
gain_db: 0.0,
};
let mut algorithm = Biquad::new(params);
algorithm.init(sample_rate);
Self {
id: NodeId(0),
metadata,
inputs,
outputs,
controls: Vec::new(),
state: NodeState::new(sample_rate),
cutoff: 1000.0,
q: 0.707,
gain_db: 0.0,
filter_type: FilterType::LowPass,
algorithm,
}
}
pub fn from_params(params: FilterParams) -> Self {
let mut instance = Self::new(44100.0); instance.cutoff = params.cutoff;
instance.q = params.q;
instance.gain_db = params.gain_db;
instance.filter_type = params.filter_type;
instance.update_algorithm();
instance
}
pub fn new_with_params(filter_type: FilterType, cutoff: f32, q: f32, gain_db: f32) -> Self {
let params = FilterParams {
filter_type,
cutoff,
q,
gain_db,
};
Self::from_params(params)
}
pub fn cutoff(&self) -> f32 {
self.cutoff
}
pub fn set_cutoff(&mut self, cutoff: f32) {
self.cutoff = cutoff.max(20.0).min(20000.0);
self.update_algorithm();
}
pub fn q(&self) -> f32 {
self.q
}
pub fn set_q(&mut self, q: f32) {
self.q = q.max(0.1).min(20.0);
self.update_algorithm();
}
pub fn gain_db(&self) -> f32 {
self.gain_db
}
pub fn set_gain_db(&mut self, gain_db: f32) {
self.gain_db = gain_db.max(-24.0).min(24.0);
self.update_algorithm();
}
pub fn filter_type(&self) -> FilterType {
self.filter_type
}
pub fn set_filter_type(&mut self, filter_type: FilterType) {
self.filter_type = filter_type;
self.update_algorithm();
}
pub fn algorithm(&self) -> &Biquad<T> {
&self.algorithm
}
pub fn algorithm_mut(&mut self) -> &mut Biquad<T> {
&mut self.algorithm
}
fn update_algorithm(&mut self) {
let params = FilterParams {
filter_type: self.filter_type,
cutoff: self.cutoff,
q: self.q,
gain_db: self.gain_db,
};
self.algorithm.set_params(params);
if self.state.sample_rate > 0.0 {
self.algorithm.init(self.state.sample_rate);
}
}
}
impl<T: Transcendental, const BUF_SIZE: usize> SignalNode<T, BUF_SIZE> for BiquadProcessor<T, BUF_SIZE> {
fn node_type_id(&self) -> rill_core::NodeTypeId
where
Self: 'static + Sized,
{
rill_core::NodeTypeId::of::<Self>()
}
fn id(&self) -> NodeId {
self.id
}
fn set_id(&mut self, id: NodeId) {
self.id = id;
}
fn metadata(&self) -> NodeMetadata {
self.metadata.clone()
}
fn init(&mut self, sample_rate: f32) {
self.state.sample_rate = sample_rate;
self.algorithm.init(sample_rate);
}
fn reset(&mut self) {
self.state.sample_pos = 0;
self.state.blocks_processed = 0;
self.algorithm.reset();
}
fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue> {
let name = id.as_str();
match name {
"cutoff" => Some(ParamValue::Float(self.cutoff)),
"q" => Some(ParamValue::Float(self.q)),
"gain_db" => Some(ParamValue::Float(self.gain_db)),
_ => None,
}
}
fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()> {
let name = id.as_str();
if let Some(v) = value.as_f32() {
match name {
"cutoff" => {
self.set_cutoff(v);
Ok(())
}
"q" => {
self.set_q(v);
Ok(())
}
"gain_db" => {
self.set_gain_db(v);
Ok(())
}
_ => Err(ProcessError::parameter(format!(
"Unknown parameter: {}",
name
))),
}
} else {
Err(ProcessError::parameter("Expected float value"))
}
}
fn input_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
self.inputs.get(index)
}
fn input_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
self.inputs.get_mut(index)
}
fn output_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
self.outputs.get(index)
}
fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
self.outputs.get_mut(index)
}
fn control_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
self.controls.get(index)
}
fn control_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
self.controls.get_mut(index)
}
fn num_inputs(&self) -> usize {
self.inputs.len()
}
fn num_outputs(&self) -> usize {
self.outputs.len()
}
fn state(&self) -> &NodeState<T, BUF_SIZE> {
&self.state
}
fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
&mut self.state
}
}
impl<T: Transcendental, const BUF_SIZE: usize> Processor<T, BUF_SIZE> for BiquadProcessor<T, BUF_SIZE> {
fn process(
&mut self,
_clock: &rill_core::ClockTick,
_signal_inputs: &[&[T; BUF_SIZE]],
_control_inputs: &[T],
_clock_inputs: &[rill_core::ClockTick],
_feedback_inputs: &[&[T; BUF_SIZE]],
) -> ProcessResult<()> {
let input_buf = *self.inputs[0].buffer.as_array();
let output_buf = self.outputs[0].buffer.as_mut_array();
let ctx = ActionContext::new(_clock);
self.algorithm
.process(Some(&input_buf[..]), &mut output_buf[..], &ctx)?;
Ok(())
}
fn latency(&self) -> usize {
0
}
}
pub use rill_core_dsp::filters::Biquad as BiquadFilterGeneric;
pub type BiquadFilter = BiquadFilterGeneric<f32>;
pub trait BiquadExt<T> {
fn cutoff(&self) -> f32;
fn set_cutoff(&mut self, cutoff: f32);
fn q(&self) -> f32;
fn set_q(&mut self, q: f32);
fn gain_db(&self) -> f32;
fn set_gain_db(&mut self, gain_db: f32);
fn filter_type(&self) -> FilterType;
fn set_filter_type(&mut self, filter_type: FilterType);
}
impl<T: rill_core::Transcendental> BiquadExt<T> for Biquad<T>
where
Biquad<T>: ParameterizedAlgorithm<T, Params = FilterParams>,
{
fn cutoff(&self) -> f32 {
self.params().cutoff
}
fn set_cutoff(&mut self, cutoff: f32) {
let mut params = self.params().clone();
params.cutoff = cutoff.max(20.0).min(20000.0);
self.set_params(params);
}
fn q(&self) -> f32 {
self.params().q
}
fn set_q(&mut self, q: f32) {
let mut params = self.params().clone();
params.q = q.max(0.1).min(20.0);
self.set_params(params);
}
fn gain_db(&self) -> f32 {
self.params().gain_db
}
fn set_gain_db(&mut self, gain_db: f32) {
let mut params = self.params().clone();
params.gain_db = gain_db.max(-24.0).min(24.0);
self.set_params(params);
}
fn filter_type(&self) -> FilterType {
self.params().filter_type
}
fn set_filter_type(&mut self, filter_type: FilterType) {
let mut params = self.params().clone();
params.filter_type = filter_type;
self.set_params(params);
}
}
#[allow(dead_code)]
fn filter_type_to_string(ft: FilterType) -> String {
match ft {
FilterType::LowPass => "LowPass".to_string(),
FilterType::HighPass => "HighPass".to_string(),
FilterType::BandPass => "BandPass".to_string(),
FilterType::Notch => "Notch".to_string(),
FilterType::Peak => "Peak".to_string(),
FilterType::LowShelf => "LowShelf".to_string(),
FilterType::HighShelf => "HighShelf".to_string(),
FilterType::AllPass => "AllPass".to_string(),
}
}
#[allow(dead_code)]
fn string_to_filter_type(s: &str) -> Option<FilterType> {
match s {
"LowPass" => Some(FilterType::LowPass),
"HighPass" => Some(FilterType::HighPass),
"BandPass" => Some(FilterType::BandPass),
"Notch" => Some(FilterType::Notch),
"Peak" => Some(FilterType::Peak),
"LowShelf" => Some(FilterType::LowShelf),
"HighShelf" => Some(FilterType::HighShelf),
"AllPass" => Some(FilterType::AllPass),
_ => None,
}
}
pub fn new(filter_type: FilterType, cutoff: f32, q: f32, gain_db: f32) -> BiquadFilter {
BiquadFilter::new(FilterParams {
filter_type,
cutoff,
q,
gain_db,
})
}