#![allow(dead_code)]
use std::any::Any;
use std::path::Path;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use cpal::SampleFormat;
use playr::audio::meter::Meter;
use playr::audio::output::{render, Backend, DeviceEvent, OutputError, Plan, Shared};
use playr::audio::{Player, Spec};
pub fn skip(var: &str, what: &str) {
if std::env::var_os(var).is_some() {
panic!("{what}, and {var} is set");
}
eprintln!("skipping: {what}");
}
pub fn have_ffmpeg() -> bool {
let ok = Command::new("ffmpeg")
.arg("-version")
.output()
.is_ok_and(|o| o.status.success());
if !ok {
skip("PLAYR_REQUIRE_FFMPEG", "ffmpeg not available");
}
ok
}
pub fn silence(path: &Path, rate: u32, secs: f32) {
levels(path, rate, &[(secs, 0.0)]);
}
pub fn tone(path: &Path, rate: u32, secs: f32, dbfs: f32) {
let amplitude = 10f32.powf(dbfs / 20.0);
let pcm: Vec<u8> = (0..(rate as f32 * secs) as usize)
.flat_map(|i| {
let v = amplitude * (std::f32::consts::TAU * 1000.0 * i as f32 / rate as f32).sin();
let sample = ((v * i16::MAX as f32).round() as i16).to_le_bytes();
[sample, sample].concat()
})
.collect();
write_wav(path, rate, pcm);
}
pub fn levels(path: &Path, rate: u32, parts: &[(f32, f32)]) {
let pcm: Vec<u8> = parts
.iter()
.flat_map(|&(secs, level)| {
let sample = ((level * i16::MAX as f32) as i16).to_le_bytes();
std::iter::repeat_n([sample, sample].concat(), (rate as f32 * secs) as usize).flatten()
})
.collect();
write_wav(path, rate, pcm);
}
fn write_wav(path: &Path, rate: u32, pcm: Vec<u8>) {
let data = pcm.len() as u32;
let mut wav = Vec::new();
wav.extend_from_slice(b"RIFF");
wav.extend_from_slice(&(36 + data).to_le_bytes());
wav.extend_from_slice(b"WAVEfmt ");
wav.extend_from_slice(&16u32.to_le_bytes());
wav.extend_from_slice(&1u16.to_le_bytes()); wav.extend_from_slice(&2u16.to_le_bytes()); wav.extend_from_slice(&rate.to_le_bytes());
wav.extend_from_slice(&(rate * 4).to_le_bytes()); wav.extend_from_slice(&4u16.to_le_bytes()); wav.extend_from_slice(&16u16.to_le_bytes()); wav.extend_from_slice(b"data");
wav.extend_from_slice(&data.to_le_bytes());
wav.extend_from_slice(&pcm);
std::fs::write(path, wav).unwrap();
}
#[derive(Default)]
pub struct Control {
pub refuse_open: AtomicBool,
pub stall: AtomicBool,
pub opened: AtomicUsize,
pub played: Mutex<Vec<f32>>,
events: Mutex<Option<Sender<DeviceEvent>>>,
}
impl Control {
pub fn send(&self, event: DeviceEvent) {
let events = self.events.lock().unwrap();
events
.as_ref()
.expect("no stream open")
.send(event)
.unwrap();
}
}
struct Fake(Arc<Control>);
struct Running(Arc<AtomicBool>);
impl Drop for Running {
fn drop(&mut self) {
self.0.store(false, Ordering::Relaxed);
}
}
impl Backend for Fake {
fn negotiate(&self, src: Spec) -> Result<Plan, OutputError> {
Ok(Plan {
rate: src.rate,
channels: 2,
format: SampleFormat::F32,
})
}
fn start(
&self,
plan: Plan,
mut consumer: rtrb::Consumer<f32>,
shared: Arc<Shared>,
events: Sender<DeviceEvent>,
) -> Result<Box<dyn Any>, OutputError> {
if self.0.refuse_open.load(Ordering::Relaxed) {
return Err(OutputError::Build("refused by the fake device".into()));
}
*self.0.events.lock().unwrap() = Some(events);
self.0.opened.fetch_add(1, Ordering::Relaxed);
let running = Arc::new(AtomicBool::new(true));
let alive = running.clone();
let control = self.0.clone();
std::thread::spawn(move || {
let mut buf = vec![0.0f32; (plan.rate / 100) as usize * plan.channels as usize];
let mut meter = Meter::new(plan.rate, plan.channels);
while alive.load(Ordering::Relaxed) {
if !control.stall.load(Ordering::Relaxed) {
render(
&mut buf,
&mut consumer,
&shared,
&mut meter,
plan.channels as u64,
|v| v,
);
control.played.lock().unwrap().extend_from_slice(&buf);
}
std::thread::sleep(Duration::from_millis(10));
}
});
Ok(Box::new(Running(running)))
}
}
pub fn fake_player() -> (Player, Arc<Control>) {
let control = Arc::new(Control::default());
let player = Player::with_backend(Fake(control.clone())).unwrap();
(player, control)
}