use quiver::prelude::*;
pub struct BitCrusher {
sample_rate: f64,
hold_sample: f64,
hold_counter: f64,
spec: PortSpec,
}
impl BitCrusher {
pub fn new(sample_rate: f64) -> Self {
Self {
sample_rate,
hold_sample: 0.0,
hold_counter: 0.0,
spec: PortSpec {
inputs: vec![
PortDef::new(0, "in", SignalKind::Audio),
PortDef::new(1, "bits", SignalKind::CvUnipolar).with_default(0.0),
PortDef::new(2, "rate", SignalKind::CvUnipolar).with_default(0.0),
],
outputs: vec![
PortDef::new(10, "out", SignalKind::Audio),
],
},
}
}
}
impl GraphModule for BitCrusher {
fn port_spec(&self) -> &PortSpec {
&self.spec
}
fn tick(&mut self, inputs: &PortValues, outputs: &mut PortValues) {
let input = inputs.get_or(0, 0.0);
let bits_cv = inputs.get_or(1, 0.0).clamp(0.0, 10.0);
let rate_cv = inputs.get_or(2, 0.0).clamp(0.0, 10.0);
let bits = 16.0 - (bits_cv / 10.0 * 15.0);
let levels = 2.0_f64.powf(bits);
let rate_reduction = 1.0 + (rate_cv / 10.0 * 63.0);
self.hold_counter += 1.0;
if self.hold_counter >= rate_reduction {
self.hold_counter = 0.0;
self.hold_sample = input;
}
let normalized = (self.hold_sample + 5.0) / 10.0; let quantized = (normalized * levels).round() / levels;
let output = quantized * 10.0 - 5.0;
outputs.set(10, output);
}
fn reset(&mut self) {
self.hold_sample = 0.0;
self.hold_counter = 0.0;
}
fn set_sample_rate(&mut self, sample_rate: f64) {
self.sample_rate = sample_rate;
}
}
fn main() {
let sample_rate = 44100.0;
println!("=== Custom Module Demo: BitCrusher ===\n");
let mut patch = Patch::new(sample_rate);
let vco = patch.add("vco", Vco::new(sample_rate));
let crusher = patch.add("crusher", BitCrusher::new(sample_rate));
let output = patch.add("output", StereoOutput::new());
let bits_cv = patch.add("bits_cv", Offset::new(0.0)); let rate_cv = patch.add("rate_cv", Offset::new(0.0));
patch.connect(vco.out("sin"), crusher.in_("in")).unwrap();
patch
.connect(bits_cv.out("out"), crusher.in_("bits"))
.unwrap();
patch
.connect(rate_cv.out("out"), crusher.in_("rate"))
.unwrap();
patch
.connect(crusher.out("out"), output.in_("left"))
.unwrap();
patch.set_output(output.id());
patch.compile().unwrap();
println!("Testing BitCrusher at various settings:\n");
for (bits_v, rate_v, desc) in [
(0.0, 0.0, "Clean (16-bit, no rate reduction)"),
(5.0, 0.0, "8-bit, full rate"),
(8.0, 0.0, "4-bit, full rate"),
(0.0, 5.0, "16-bit, 32x rate reduction"),
(7.0, 5.0, "Lo-fi (5-bit, 32x reduction)"),
(9.0, 8.0, "Extreme (2-bit, 50x reduction)"),
] {
let mut test_patch = Patch::new(sample_rate);
let vco = test_patch.add("vco", Vco::new(sample_rate));
let crusher = test_patch.add("crusher", BitCrusher::new(sample_rate));
let bits = test_patch.add("bits", Offset::new(bits_v));
let rate = test_patch.add("rate", Offset::new(rate_v));
let output = test_patch.add("output", StereoOutput::new());
test_patch
.connect(vco.out("sin"), crusher.in_("in"))
.unwrap();
test_patch
.connect(bits.out("out"), crusher.in_("bits"))
.unwrap();
test_patch
.connect(rate.out("out"), crusher.in_("rate"))
.unwrap();
test_patch
.connect(crusher.out("out"), output.in_("left"))
.unwrap();
test_patch.set_output(output.id());
test_patch.compile().unwrap();
let num_samples = (sample_rate * 0.1) as usize;
let mut samples = Vec::with_capacity(num_samples);
for _ in 0..num_samples {
let (left, _) = test_patch.tick();
samples.push(left);
}
let peak = samples.iter().map(|s| s.abs()).fold(0.0_f64, f64::max);
let rms = (samples.iter().map(|s| s * s).sum::<f64>() / num_samples as f64).sqrt();
let mut unique: Vec<i32> = samples.iter().map(|s| (s * 1000.0) as i32).collect();
unique.sort();
unique.dedup();
println!("{}", desc);
println!(" Bits CV: {:.1}V, Rate CV: {:.1}V", bits_v, rate_v);
println!(
" Peak: {:.2}V, RMS: {:.2}V, Unique levels: {}\n",
peak,
rms,
unique.len()
);
}
let module = BitCrusher::new(sample_rate);
let spec = module.port_spec();
println!("--- Port Specification ---");
println!("Inputs:");
for def in &spec.inputs {
println!(
" {} (id={}): {:?}, default={:.1}V",
def.name, def.id, def.kind, def.default
);
}
println!("Outputs:");
for def in &spec.outputs {
println!(" {} (id={}): {:?}", def.name, def.id, def.kind);
}
}