use crate::event::{GraphEvent, NoteEvent, TimedEvent};
use crate::graph::Graph;
use crate::grid::Grid;
use crate::math::Rational;
use crate::pitch::Scale;
use crate::time::beat_to_sample;
use crate::tree::Tree;
const DEFAULT_BLOCK_SIZE: usize = 512;
fn resolve_frequency(event: &NoteEvent, scale: &Scale, reference_hz: f64) -> f64 {
let pitch = scale.resolve(event.degree);
let octave_pitch = crate::pitch::Pitch(pitch.0 + event.octave as f64);
octave_pitch.to_hz(reference_hz)
}
pub fn tree_to_timed_events(
tree: &Tree<NoteEvent>,
beats: Rational,
bpm: f64,
sample_rate: f64,
scale: &Scale,
reference_hz: f64,
) -> Vec<TimedEvent> {
let flat = tree.flatten();
let mut events = Vec::new();
let mut voice = 0u32;
for fe in &flat {
let beat_start = fe.start * beats;
let beat_end = (fe.start + fe.duration) * beats;
let sample_on = beat_to_sample(beat_start, bpm, sample_rate) as usize;
let sample_off = beat_to_sample(beat_end, bpm, sample_rate) as usize;
let freq = resolve_frequency(fe.event, scale, reference_hz);
let vel = fe.event.velocity.to_f64();
events.push(TimedEvent {
sample_offset: sample_on,
event: GraphEvent::NoteOn {
frequency: freq,
velocity: vel,
voice,
},
});
events.push(TimedEvent {
sample_offset: sample_off,
event: GraphEvent::NoteOff { voice },
});
voice += 1;
}
events.sort_by_key(|e| e.sample_offset);
events
}
pub fn render(
graph: &mut Graph,
events: &[TimedEvent],
duration_samples: usize,
sample_rate: f64,
output_node: u32,
output_ports: &[u16],
) -> Vec<Vec<f32>> {
let block_size = DEFAULT_BLOCK_SIZE;
let num_channels = output_ports.len();
let mut output = vec![vec![0.0f32; duration_samples]; num_channels];
let mut pos = 0;
while pos < duration_samples {
let frames = (duration_samples - pos).min(block_size);
let block_events: Vec<TimedEvent> = events
.iter()
.filter(|e| e.sample_offset >= pos && e.sample_offset < pos + frames)
.map(|e| TimedEvent {
sample_offset: e.sample_offset - pos,
event: e.event.clone(),
})
.collect();
graph.run(frames, sample_rate, &block_events);
for (ch, &port) in output_ports.iter().enumerate() {
let buf = graph.output_buffer(output_node, port);
output[ch][pos..pos + frames].copy_from_slice(&buf[..frames]);
}
pos += frames;
}
output
}
pub fn render_pattern(
tree: &Tree<NoteEvent>,
beats: Rational,
bpm: f64,
sample_rate: f64,
scale: &Scale,
reference_hz: f64,
graph: &mut Graph,
output_node: u32,
) -> Vec<Vec<f32>> {
let duration_beats = beats.to_f64();
let duration_seconds = duration_beats * 60.0 / bpm;
let duration_samples = (duration_seconds * sample_rate).ceil() as usize;
let events = tree_to_timed_events(tree, beats, bpm, sample_rate, scale, reference_hz);
render(
graph,
&events,
duration_samples,
sample_rate,
output_node,
&[0, 1],
)
}
pub fn grid_to_timed_events(
grid: &Grid,
beats: Rational,
bpm: f64,
sample_rate: f64,
scale: &Scale,
reference_hz: f64,
voice_ids: &[u32],
swing: f64,
) -> Vec<TimedEvent> {
let mut events = Vec::new();
let rows = grid.rows as i64;
let step_beats = beats.to_f64() / grid.rows as f64;
let swing_offset_samples =
(swing * 0.5 * step_beats * (60.0 / bpm) * sample_rate).round() as usize;
for col in 0..grid.columns {
let vid = voice_ids.get(col as usize).copied().unwrap_or(col);
let tree = grid.column_tree(col);
let flat = tree.flatten();
for fe in &flat {
let step_idx = (fe.start * Rational::integer(rows)).floor();
let swing_samples = if step_idx.rem_euclid(2) == 1 {
swing_offset_samples
} else {
0
};
let beat_start = fe.start * beats;
let gated_dur = fe.duration * fe.event.gate;
let beat_end = (fe.start + gated_dur) * beats;
let sample_on = beat_to_sample(beat_start, bpm, sample_rate) as usize + swing_samples;
let sample_off = beat_to_sample(beat_end, bpm, sample_rate) as usize + swing_samples;
let freq = resolve_frequency(fe.event, scale, reference_hz);
let vel = fe.event.velocity.to_f64();
events.push(TimedEvent {
sample_offset: sample_on,
event: GraphEvent::NoteOn {
frequency: freq,
velocity: vel,
voice: vid,
},
});
events.push(TimedEvent {
sample_offset: sample_off,
event: GraphEvent::NoteOff { voice: vid },
});
}
}
events.sort_by_key(|e| e.sample_offset);
events
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsp::{Adsr, Gain, Oscillator, Waveform};
use crate::pitch::Tuning;
#[test]
fn render_simple_pattern() {
let scale = Tuning::edo12().to_scale();
let tree = Tree::seq(vec![
Tree::leaf(NoteEvent::simple(0)),
Tree::rest(),
Tree::leaf(NoteEvent::simple(4)),
Tree::rest(),
]);
let mut graph = Graph::new(DEFAULT_BLOCK_SIZE);
let osc = graph.add_node(Box::new(Oscillator::new(Waveform::Saw)));
let env = graph.add_node(Box::new(Adsr::new(0.005, 0.05, 0.3, 0.1)));
let gain = graph.add_node(Box::new(Gain::new(0.5)));
graph.connect(osc, 0, env, 0);
graph.connect(env, 0, gain, 0);
let output = render_pattern(
&tree,
Rational::integer(4),
120.0,
44100.0,
&scale,
440.0,
&mut graph,
gain,
);
assert_eq!(output.len(), 2);
assert!(output[0].len() >= 88200);
let energy: f32 = output[0].iter().map(|s| s * s).sum();
assert!(energy > 0.0, "output should contain audio");
}
}