use crate::dsl::SoundDoc;
use crate::render;
pub fn render_stereo(doc: &SoundDoc) -> (Vec<f32>, Vec<f32>) {
let product = render::render_product(doc);
match product.stereo {
Some((l, r)) => (l, r),
None => render::stereoize(&product.mono, doc.stereo, doc.sample_rate),
}
}
pub struct Player {
doc: SoundDoc,
left: Vec<f32>,
right: Vec<f32>,
pos: usize,
pub looping: bool,
pub playing: bool,
}
impl Player {
pub fn new(doc: SoundDoc) -> Self {
let (left, right) = render_stereo(&doc);
Self {
doc,
left,
right,
pos: 0,
looping: true,
playing: false,
}
}
pub fn set_doc(&mut self, doc: SoundDoc) {
let (left, right) = render_stereo(&doc);
self.pos = self.pos.min(left.len());
self.doc = doc;
self.left = left;
self.right = right;
}
pub fn frames(&self) -> usize {
self.left.len()
}
pub fn position(&self) -> usize {
self.pos
}
pub fn seek(&mut self, frame: usize) {
self.pos = frame.min(self.left.len());
}
pub fn play(&mut self) {
self.playing = true;
}
pub fn stop(&mut self) {
self.playing = false;
self.pos = 0;
}
pub fn fill(&mut self, out: &mut [f32]) -> usize {
let frames = out.len() / 2;
if !self.playing {
out.fill(0.0);
return 0;
}
let n = self.left.len();
let mut served = 0;
for f in 0..frames {
if self.pos >= n {
if self.looping && n > 0 {
self.pos = 0;
} else {
out[f * 2] = 0.0;
out[f * 2 + 1] = 0.0;
self.playing = self.looping && n > 0;
continue;
}
}
out[f * 2] = self.left[self.pos];
out[f * 2 + 1] = self.right[self.pos];
self.pos += 1;
served += 1;
}
served
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsl::SoundDoc;
fn coin() -> SoundDoc {
serde_json::from_str(
r#"{ "name":"coin", "duration":0.18, "root": { "type":"mul", "inputs": [
{ "type":"square", "duty":0.5, "freq": { "arp": { "steps":[988,1319], "rate":14 } } },
{ "type":"env", "a":0.0, "d":0.16, "s":0.0, "r":0.0, "punch":0.2 } ] } }"#,
)
.expect("valid doc")
}
#[test]
fn player_output_matches_offline_bounce() {
let doc = coin();
let (exp_l, exp_r) = render_stereo(&doc);
let mut p = Player::new(doc);
p.looping = false;
p.play();
let mut got_l = Vec::new();
let mut got_r = Vec::new();
let mut block = vec![0.0f32; 257 * 2];
while p.playing && got_l.len() < exp_l.len() {
let served = p.fill(&mut block);
for f in 0..served {
got_l.push(block[f * 2]);
got_r.push(block[f * 2 + 1]);
}
}
assert_eq!(got_l.len(), exp_l.len(), "served frame count");
assert_eq!(
got_l.iter().map(|x| x.to_bits()).collect::<Vec<_>>(),
exp_l.iter().map(|x| x.to_bits()).collect::<Vec<_>>(),
"left channel byte-identical to offline bounce"
);
assert_eq!(
got_r.iter().map(|x| x.to_bits()).collect::<Vec<_>>(),
exp_r.iter().map(|x| x.to_bits()).collect::<Vec<_>>(),
"right channel byte-identical to offline bounce"
);
}
#[test]
fn paused_player_writes_silence_without_advancing() {
let mut p = Player::new(coin());
let mut block = vec![1.0f32; 64];
let served = p.fill(&mut block); assert_eq!(served, 0);
assert!(block.iter().all(|&x| x == 0.0));
}
#[test]
fn looping_wraps_at_the_end() {
let mut p = Player::new(coin());
p.play();
let n = p.frames();
let mut block = vec![0.0f32; (n + 50) * 2];
let served = p.fill(&mut block);
assert_eq!(served, n + 50);
}
}