use super::compile::note_end;
use crate::dsl::{Node, SoundDoc};
use crate::program::ResourceEstimates;
pub(crate) fn duration_frames(doc: &SoundDoc) -> u64 {
(doc.duration * doc.sample_rate as f32).round().max(0.0) as u64
}
pub(crate) fn track_note_spans(doc: &SoundDoc, index: usize) -> Option<Vec<(u32, u32)>> {
let Node::Tracks { tracks, .. } = &doc.root else {
return None;
};
let node = &tracks.get(index)?.node;
let seq = match node {
Node::Seq { notes, .. } => {
return Some(notes.iter().map(|n| (n.step, note_end(n))).collect());
}
Node::Chain { stages } => match stages.first() {
Some(Node::Seq { notes, .. }) => notes,
_ => return None,
},
_ => return None,
};
Some(seq.iter().map(|n| (n.step, note_end(n))).collect())
}
pub(crate) fn track_note_count(doc: &SoundDoc, index: usize) -> u32 {
track_note_spans(doc, index).map_or(0, |v| v.len() as u32)
}
pub(crate) fn peak_overlap(mut spans: Vec<(u32, u32)>) -> u32 {
let mut points: Vec<(u32, i64)> = Vec::with_capacity(spans.len() * 2);
for (start, end) in spans.drain(..) {
points.push((start, 1));
points.push((end, -1));
}
points.sort();
let mut current = 0i64;
let mut peak = 0i64;
for (_, delta) in points {
current += delta;
peak = peak.max(current);
}
peak.max(0) as u32
}
pub(crate) fn program_estimates(doc: &SoundDoc) -> ResourceEstimates {
let mut events = 0u64;
let mut peak_voices = 0u32;
if let Node::Tracks { tracks, .. } = &doc.root {
for i in 0..tracks.len() {
if let Some(spans) = track_note_spans(doc, i) {
events += spans.len() as u64;
peak_voices = peak_voices.saturating_add(peak_overlap(spans));
}
}
}
let frames = duration_frames(doc);
ResourceEstimates {
frames,
events,
peak_voices,
memory_bytes: frames.saturating_mul(8),
}
}
#[cfg(test)]
mod tests {
use super::peak_overlap;
#[test]
fn peak_overlap_treats_ends_as_half_open() {
assert_eq!(
peak_overlap(vec![(0, 4), (4, 8)]),
1,
"back-to-back, never 2"
);
assert_eq!(peak_overlap(vec![(0, 5), (4, 8)]), 2, "one step of overlap");
assert_eq!(peak_overlap(vec![]), 0);
}
}