Examples
automation_gain_ramp
examples/primitives/automation_gain_ramp.rs, the whole program
Run it with cargo run --example automation_gain_ramp (see the examples for the features some need).
//! Read mode fills a gain that runs from 0 at sample 0 to 1 at sample 200,
//! then holds. Each 32-sample block stores its first sample in the atomic.
//!
//! ```text
//! cargo run --example automation_gain_ramp
//! ```
use std::sync::Arc;
use superb::primitives::automation::{
advance_block, AutomationLane, AutomationMode, EnvelopePoint,
};
use superb::primitives::realtime::AtomicF32;
use superb::primitives::{IdGenerator, TimePosition};
fn main() {
let ids = IdGenerator::new();
let gain = Arc::new(AtomicF32::new(0.0));
let mut lane = AutomationLane::new(ids.next(), Arc::clone(&gain));
// Linear from sample 0 to sample 200, then the last point holds.
lane.envelope_mut()
.upsert(EnvelopePoint::linear(TimePosition::from_samples(0), 0.0));
lane.envelope_mut()
.upsert(EnvelopePoint::linear(TimePosition::from_samples(200), 1.0));
lane.set_mode(AutomationMode::Read);
assert!(lane.evaluate(TimePosition::from_samples(0)).abs() < 1e-6);
assert!((lane.evaluate(TimePosition::from_samples(200)) - 1.0).abs() < 1e-6);
assert!((lane.evaluate(TimePosition::from_samples(256)) - 1.0).abs() < 1e-6);
let block = 32usize;
let mut pos = TimePosition::ZERO;
let mut scratch = vec![0.0f32; block];
// Eight blocks. The atomic matches the first sample. Printed rows:
// t=0 0.000..0.155, 32 0.160..0.315, 64 0.320..0.475, 96 0.480..0.635,
// 128 0.640..0.795, 160 0.800..0.955, 192 0.960..1.000, 224 1.000..1.000.
let rows = [
(0, "0.000", "0.155"),
(32, "0.160", "0.315"),
(64, "0.320", "0.475"),
(96, "0.480", "0.635"),
(128, "0.640", "0.795"),
(160, "0.800", "0.955"),
(192, "0.960", "1.000"),
(224, "1.000", "1.000"),
];
println!("sample-accurate gain ramp (Read mode), block={block}");
for (i, _) in rows.iter().enumerate() {
lane.apply_block(pos, block, true, Some(&mut scratch));
let first = scratch[0];
let last = scratch[block - 1];
let sample = pos.samples();
let ramp = |s: i64| -> f32 {
if s >= 200 {
1.0
} else {
s as f32 / 200.0
}
};
assert_eq!(sample, rows[i].0);
assert!((first - ramp(sample)).abs() < 1e-6);
assert!((last - ramp(sample + block as i64 - 1)).abs() < 1e-6);
assert!((gain.get() - first).abs() < 1e-6);
assert_eq!(format!("{first:.3}"), rows[i].1);
assert_eq!(format!("{last:.3}"), rows[i].2);
assert_eq!(format!("{:.3}", gain.get()), rows[i].1);
// Twenty cells, one hash per 0.05 of the block's last sample.
let bar = |v: f32| {
let n = (v * 20.0).round().clamp(0.0, 20.0) as usize;
format!("{}{}", "#".repeat(n), ".".repeat(20 - n))
};
let drawn = bar(last);
assert_eq!(drawn.len(), 20);
assert_eq!(
drawn.matches('#').count(),
(last * 20.0).round().clamp(0.0, 20.0) as usize
);
println!(
"t={:>4} atomic={:.3} block=[{:.3} .. {:.3}] {}",
pos.samples(),
gain.get(),
first,
last,
drawn
);
pos = advance_block(pos, block);
}
// Eight blocks of 32 end at sample 256, on the hold.
assert_eq!(pos.samples(), 256);
assert!((gain.get() - 1.0).abs() < 1e-6);
}