use alloc::sync::Arc;
use alloc::vec::Vec;
use super::{AudioSource, Pole, SampleFormat, StreamInfo};
use crate::core::sync::{LockRank, Mutex};
use crate::dev::apu::Apu;
static NES_OUTPUT_STAGE: &[Pole] = &[
Pole::high_pass(90),
Pole::high_pass(440),
Pole::low_pass(14_000),
];
#[derive(Debug)]
pub struct NesAudio {
apu: Arc<Apu>,
scratch: Mutex<Vec<u16>>,
}
impl NesAudio {
#[must_use]
pub fn new(apu: Arc<Apu>) -> NesAudio {
NesAudio {
apu,
scratch: Mutex::with_rank(LockRank::LEAF, Vec::new()),
}
}
#[must_use]
pub fn apu(&self) -> &Arc<Apu> {
&self.apu
}
}
impl AudioSource for NesAudio {
fn info(&self) -> StreamInfo {
let region = self.apu.tv_region();
let (num, den) = region.master_clock();
let den = den * region.cpu_divider() * 2;
let g = gcd(num, den);
StreamInfo::new(num / g, den / g, 1, SampleFormat::S16).with_output_stage(NES_OUTPUT_STAGE)
}
fn drain(&self, out: &mut Vec<i16>) -> u64 {
let mut raw = core::mem::take(&mut *self.scratch.lock());
raw.clear();
self.apu.take_samples(&mut raw);
let taken = raw.len() as u64;
out.reserve(raw.len());
for sample in &raw {
out.push((i32::from(*sample) - 32_768) as i16);
}
*self.scratch.lock() = raw;
taken
}
fn dropped(&self) -> u64 {
self.apu.samples_dropped()
}
}
const fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let t = a % b;
a = b;
b = t;
}
if a == 0 { 1 } else { a }
}
pub mod capture {
use super::{Apu, Arc, NesAudio};
use crate::core::error::Result;
use crate::core::hosts::{Captured, HostKind, HostObjects};
use crate::dev::apu::{APU_CLASS, MAX_SAMPLE_BUFFER};
use crate::machine::BuildOptions;
pub fn install(options: &mut BuildOptions, capacity: u64) -> Result<()> {
let seen: Arc<Captured<Apu>> =
options
.realize
.hosts
.open(HostKind::CAPTURE, APU_CLASS.name, Captured::new)?;
let wanted = capacity.min(MAX_SAMPLE_BUFFER);
options.bindings.replace(APU_CLASS.name, move |props| {
let asked = props
.get("sample-buffer")
.and_then(crate::core::props::Value::as_uint);
let apu = match asked {
_ if wanted == 0 => Arc::new(Apu::new(props)?),
Some(have) if have >= wanted => Arc::new(Apu::new(props)?),
_ => Arc::new(Apu::new(&props.clone().with("sample-buffer", wanted))?),
};
seen.push(&apu);
Ok(apu)
});
Ok(())
}
#[must_use]
pub fn take(hosts: &HostObjects) -> Option<NesAudio> {
let seen = hosts
.get::<Captured<Apu>>(HostKind::CAPTURE, APU_CLASS.name)
.ok()
.flatten()?;
seen.take().map(NesAudio::new)
}
}