Examples
sine_through_graph
examples/primitives/sine_through_graph.rs, the whole program
Run it with cargo run --example sine_through_graph (see the examples for the features some need).
//! Three seconds of 440 Hz through a gain of 0.5 and a strip at −6 dB,
//! panned to −0.3. The file is 144000 stereo frames, peaks 0.171 and
//! 0.105. The 3-beat loop makes 563 blocks into 564 segments.
//!
//! ```text
//! cargo run --example sine_through_graph
//! ```
use superb::primitives::buffers::gain::db_to_linear;
use superb::primitives::graph::nodes::{GainNode, SineNode};
use superb::primitives::io::offline;
use superb::primitives::io::wav::{self, WavFormat};
use superb::primitives::io::Engine;
use superb::primitives::mixer::{Mixer, PanLaw};
use superb::primitives::transport::{LoopRange, Transport};
use superb::primitives::{BeatPosition, Result, SampleRate, TimePosition};
fn main() -> Result<()> {
let sample_rate = SampleRate::HZ_48K;
assert_eq!(sample_rate.seconds_to_samples(1.0), 48_000);
let block = 256;
let seconds = 3.0;
// Stereo sine at 440 Hz, amplitude 0.8, then a gain of 0.5. The track
// fader is −6 dB and constant-power pan is −0.3. The master is balance
// at centre, gain 1, so it does not change the level.
let mut mixer = Mixer::new(2);
let track = mixer.add_track("lead");
let osc_node = SineNode::new(440.0, 0.8, 2);
assert!((osc_node.frequency().get() - 440.0).abs() < 1e-6);
assert!((osc_node.amplitude().get() - 0.8).abs() < 1e-6);
let osc = mixer.graph_mut().add_node(Box::new(osc_node));
let gain_node = GainNode::new(0.5, 2);
assert!((gain_node.gain().get() - 0.5).abs() < 1e-6);
let gain = mixer.graph_mut().add_node(Box::new(gain_node));
mixer.graph_mut().connect(osc, gain)?;
mixer.connect_source(gain, track)?;
let params = mixer.track_params(track)?;
params.set_volume_db(-6.0);
params.set_pan(-0.3);
assert!((params.volume_db() + 6.0).abs() < 1e-3);
assert!((params.pan() + 0.3).abs() < 1e-6);
assert_eq!(params.pan_law(), PanLaw::ConstantPower);
let master = mixer.bus_params(mixer.master())?;
assert_eq!(master.pan_law(), PanLaw::Balance);
assert!(master.pan().abs() < 1e-6);
assert!((master.gain() - 1.0).abs() < 1e-6);
// A 3-beat loop at 120 bpm is 72000 samples. Three seconds is 144000
// frames, exactly two trips around that loop.
let mut transport = Transport::new(sample_rate, 120.0);
let loop_end = transport
.tempo_map()
.beat_to_samples(BeatPosition::new(3.0));
assert_eq!(loop_end.samples(), 72_000);
transport.set_loop(Some(LoopRange::new(TimePosition::ZERO, loop_end)?));
transport.set_loop_enabled(true);
transport.play();
let mut engine = Engine::new(mixer, transport);
let total = sample_rate.seconds_to_samples(seconds) as usize;
assert_eq!(total, 144_000);
let audio = offline::render(&mut engine, sample_rate, block, total)?;
assert_eq!(audio.frames(), 144_000);
assert_eq!(audio.channels(), 2);
let path = "sine_through_graph.wav";
wav::write(path, &audio, sample_rate, WavFormat::Float32)?;
// 0.8 × 0.5 × −6 dB × constant-power pan −0.3. Left is the louder side.
// The sine reaches full scale, so the file peaks at 0.171 and 0.105.
let (pan_l, pan_r) = PanLaw::ConstantPower.gains(-0.3);
let scale = 0.8 * 0.5 * db_to_linear(-6.0);
let peak_l = audio.channel(0).iter().fold(0.0f32, |m, s| m.max(s.abs()));
let peak_r = audio.channel(1).iter().fold(0.0f32, |m, s| m.max(s.abs()));
assert!((peak_l - scale * pan_l).abs() < 1e-3);
assert!((peak_r - scale * pan_r).abs() < 1e-3);
assert!(peak_l > peak_r);
assert_eq!(format!("{peak_l:.3}"), "0.171");
assert_eq!(format!("{peak_r:.3}"), "0.105");
assert_eq!(format!("{:.3}", params.peak(0)), "0.171");
assert_eq!(format!("{:.3}", params.peak(1)), "0.105");
// 144000 / 256 is 562 blocks of 256 plus a tail of 128, so 563 blocks.
// One of them crosses the loop and splits, so 564 segments. The playhead
// lands on the loop end and wraps to 0, bar 1 beat 1.
let stats = engine.stats();
assert_eq!(144_000 / block, 562);
assert_eq!(144_000 % block, 128);
assert_eq!(stats.blocks, 563);
assert_eq!(stats.segments, 564);
let snap = engine.transport().snapshot();
assert_eq!(snap.position.samples(), 0);
assert_eq!(snap.bbt.to_string(), "1.1.000");
println!(
"wrote {path}: {} frames x {} ch @ {}",
audio.frames(),
audio.channels(),
sample_rate
);
println!("peak L/R: {:.3} / {:.3}", peak_l, peak_r);
println!(
"blocks: {}, segments (loop wraps add one): {}",
stats.blocks, stats.segments
);
println!("transport now at {} = {}", snap.position, snap.bbt);
println!(
"track meter L/R: {:.3} / {:.3}",
params.peak(0),
params.peak(1)
);
Ok(())
}