use super::{Action, AdaptiveMusic, Scheduled, SoundDoc, doc_at, render_stereo_pair};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Quantize {
#[default]
Immediate,
Beat,
Bar,
Bars(u32),
}
impl AdaptiveMusic {
pub fn set_tempo(&mut self, bpm: f32, beats_per_bar: u32) {
self.bpm = bpm.max(0.0);
self.beats_per_bar = beats_per_bar.max(1);
}
pub(super) fn frames_per_beat(&self) -> Option<f64> {
(self.bpm > 0.0).then(|| self.sample_rate as f64 * 60.0 / self.bpm as f64)
}
pub fn beats(&self) -> f64 {
self.frames_per_beat()
.map_or(0.0, |fpb| self.position as f64 / fpb)
}
pub fn bars(&self) -> f64 {
self.beats() / self.beats_per_bar as f64
}
pub fn position_frames(&self) -> u64 {
self.position
}
fn fire_frame(&self, q: Quantize) -> u64 {
let period = match q {
Quantize::Immediate => return self.position,
Quantize::Beat => self.frames_per_beat(),
Quantize::Bar => self
.frames_per_beat()
.map(|f| f * self.beats_per_bar as f64),
Quantize::Bars(n) => self
.frames_per_beat()
.map(|f| f * self.beats_per_bar as f64 * n.max(1) as f64),
};
let Some(period) = period.filter(|p| *p >= 1.0) else {
return self.position;
};
let pos = self.position as f64;
let boundary = (pos / period).floor() * period + period;
boundary.round() as u64
}
fn schedule(&mut self, q: Quantize, action: Action) {
let fire_at = self.fire_frame(q);
self.pending.push(Scheduled { fire_at, action });
}
pub(super) fn apply_or_schedule(&mut self, q: Quantize, action: Action) {
if self.fire_frame(q) <= self.position {
self.apply(action);
} else {
self.schedule(q, action);
}
}
fn apply(&mut self, action: Action) {
match action {
Action::SetIntensity(x) => self.set_intensity(x),
Action::Stinger(st) => self.stingers.push(st),
Action::Transition { to } => self.begin_transition(to),
}
}
pub fn set_intensity_at(&mut self, x: f32, q: Quantize) {
self.apply_or_schedule(q, Action::SetIntensity(x));
}
pub fn stinger_at(&mut self, doc: &SoundDoc, q: Quantize) {
let (left, right) = render_stereo_pair(&doc_at(doc, self.sample_rate));
self.stinger_stereo_at(left, right, q);
}
pub fn stinger_stereo_at(&mut self, left: Vec<f32>, right: Vec<f32>, q: Quantize) {
self.apply_or_schedule(q, Action::Stinger(super::Stinger::new(left, right)));
}
pub(super) fn fire_due(&mut self) {
if self.pending.iter().all(|s| s.fire_at > self.position) {
return;
}
let mut due: Vec<Scheduled> = Vec::new();
let mut i = 0;
while i < self.pending.len() {
if self.pending[i].fire_at <= self.position {
due.push(self.pending.swap_remove(i));
} else {
i += 1;
}
}
due.sort_by_key(|s| s.fire_at);
for s in due {
self.apply(s.action);
}
}
}