pub mod filter;
pub mod resample;
pub mod wav;
#[cfg(feature = "dev-nes-apu")]
#[cfg_attr(docsrs, doc(cfg(feature = "dev-nes-apu")))]
pub mod nes;
#[cfg(test)]
mod tests;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use resample::Resampler;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SampleFormat(pub u16);
impl SampleFormat {
pub const S16: SampleFormat = SampleFormat(0);
pub const F32: SampleFormat = SampleFormat(1);
pub const U8: SampleFormat = SampleFormat(2);
#[inline]
#[must_use]
pub const fn bytes_per_sample(self) -> u64 {
match self {
SampleFormat::U8 => 1,
SampleFormat::F32 => 4,
_ => 2,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
SampleFormat::S16 => "s16",
SampleFormat::F32 => "f32",
SampleFormat::U8 => "u8",
_ => "unknown",
}
}
#[inline]
fn encode(self, value: f32, dst: &mut [u8]) {
let clamped = clamp_unit(value);
match self {
SampleFormat::F32 => dst[..4].copy_from_slice(&clamped.to_le_bytes()),
SampleFormat::U8 => {
let scaled = round_to_i32(clamped * 127.0) + 128;
dst[0] = scaled.clamp(0, 255) as u8;
}
_ => {
let scaled = round_to_i32(clamped * 32767.0).clamp(-32768, 32767) as i16;
dst[..2].copy_from_slice(&scaled.to_le_bytes());
}
}
}
#[inline]
fn decode(self, src: &[u8]) -> f32 {
match self {
SampleFormat::F32 => f32::from_le_bytes([src[0], src[1], src[2], src[3]]),
SampleFormat::U8 => (f32::from(src[0]) - 128.0) / 127.0,
_ => f32::from(i16::from_le_bytes([src[0], src[1]])) / 32767.0,
}
}
}
impl fmt::Display for SampleFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[inline]
fn clamp_unit(x: f32) -> f32 {
if x > 1.0 {
1.0
} else if x > -1.0 {
x
} else if x <= -1.0 {
-1.0
} else {
0.0
}
}
#[inline]
fn round_to_i32(x: f32) -> i32 {
if x >= 0.0 {
(x + 0.5) as i32
} else {
(x - 0.5) as i32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct PoleKind(pub u16);
impl PoleKind {
pub const HIGH_PASS: PoleKind = PoleKind(0);
pub const LOW_PASS: PoleKind = PoleKind(1);
#[must_use]
pub const fn name(self) -> &'static str {
match self {
PoleKind::HIGH_PASS => "high-pass",
PoleKind::LOW_PASS => "low-pass",
_ => "unknown",
}
}
}
impl fmt::Display for PoleKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pole {
pub kind: PoleKind,
pub corner_hz: u32,
}
impl Pole {
#[must_use]
pub const fn high_pass(hz: u32) -> Pole {
Pole {
kind: PoleKind::HIGH_PASS,
corner_hz: hz,
}
}
#[must_use]
pub const fn low_pass(hz: u32) -> Pole {
Pole {
kind: PoleKind::LOW_PASS,
corner_hz: hz,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StreamInfo {
pub rate_num: u64,
pub rate_den: u64,
pub channels: u16,
pub preferred_format: SampleFormat,
pub output_stage: &'static [Pole],
}
impl StreamInfo {
#[must_use]
pub const fn new(
rate_num: u64,
rate_den: u64,
channels: u16,
preferred_format: SampleFormat,
) -> StreamInfo {
StreamInfo {
rate_num,
rate_den: if rate_den == 0 { 1 } else { rate_den },
channels,
preferred_format,
output_stage: &[],
}
}
#[must_use]
pub const fn with_output_stage(mut self, stage: &'static [Pole]) -> StreamInfo {
self.output_stage = stage;
self
}
#[must_use]
pub const fn rate_hz(self) -> u32 {
let scaled = (self.rate_num * 2 + self.rate_den) / (self.rate_den * 2);
if scaled > u32::MAX as u64 {
u32::MAX
} else {
scaled as u32
}
}
#[must_use]
pub const fn frame_bytes(self) -> u64 {
self.preferred_format.bytes_per_sample() * self.channels as u64
}
}
impl fmt::Display for StreamInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} Hz ({}/{}), {} ch, {}",
self.rate_hz(),
self.rate_num,
self.rate_den,
self.channels,
self.preferred_format
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AudioBuffer {
format: SampleFormat,
channels: u16,
bytes: Vec<u8>,
}
impl AudioBuffer {
#[must_use]
pub fn new(format: SampleFormat, channels: u16) -> AudioBuffer {
AudioBuffer {
format,
channels,
bytes: Vec::new(),
}
}
#[must_use]
pub const fn empty() -> AudioBuffer {
AudioBuffer {
format: SampleFormat::F32,
channels: 0,
bytes: Vec::new(),
}
}
#[inline]
#[must_use]
pub const fn format(&self) -> SampleFormat {
self.format
}
#[inline]
#[must_use]
pub const fn channels(&self) -> u16 {
self.channels
}
#[inline]
#[must_use]
pub const fn frame_bytes(&self) -> u64 {
self.format.bytes_per_sample() * self.channels as u64
}
#[inline]
#[must_use]
pub fn frames(&self) -> u64 {
(self.bytes.len() as u64)
.checked_div(self.frame_bytes())
.unwrap_or(0)
}
#[inline]
#[must_use]
pub fn len(&self) -> u64 {
self.bytes.len() as u64
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[inline]
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
#[inline]
#[must_use]
pub fn as_ptr(&self) -> *const u8 {
self.bytes.as_ptr()
}
pub fn clear(&mut self) {
self.bytes.clear();
}
pub fn reshape(&mut self, format: SampleFormat, channels: u16) {
if self.format == format && self.channels == channels {
return;
}
self.format = format;
self.channels = channels;
self.bytes.clear();
}
pub fn push_normalised(&mut self, frame: &[f32]) {
let width = self.format.bytes_per_sample() as usize;
for channel in 0..usize::from(self.channels) {
let value = frame.get(channel).copied().unwrap_or(0.0);
let at = self.bytes.len();
self.bytes.resize(at + width, 0);
self.format.encode(value, &mut self.bytes[at..]);
}
}
pub fn push_frame(&mut self, frame: &[i16]) {
let width = self.format.bytes_per_sample() as usize;
for channel in 0..usize::from(self.channels) {
let value = f32::from(frame.get(channel).copied().unwrap_or(0)) / 32767.0;
let at = self.bytes.len();
self.bytes.resize(at + width, 0);
self.format.encode(value, &mut self.bytes[at..]);
}
}
#[must_use]
pub fn sample(&self, frame: u64, channel: u16) -> Option<i16> {
if channel >= self.channels || frame >= self.frames() {
return None;
}
let width = self.format.bytes_per_sample();
let at = (frame * self.frame_bytes() + u64::from(channel) * width) as usize;
let value = self.format.decode(&self.bytes[at..]);
Some(round_to_i32(clamp_unit(value) * 32767.0).clamp(-32768, 32767) as i16)
}
pub fn consume(&mut self, frames: u64) -> u64 {
let available = self.frames();
let taken = frames.min(available);
if taken == available {
self.bytes.clear();
} else if taken > 0 {
self.bytes.drain(..(taken * self.frame_bytes()) as usize);
}
taken
}
#[must_use]
pub fn hash(&self) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in &self.bytes {
h ^= u64::from(*b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
}
pub trait AudioSource: Send + Sync + fmt::Debug {
fn info(&self) -> StreamInfo;
fn drain(&self, out: &mut Vec<i16>) -> u64;
fn dropped(&self) -> u64 {
0
}
}
pub trait Sink: Send + fmt::Debug {
fn info(&self) -> StreamInfo;
fn write(&mut self, buffer: &AudioBuffer) -> u64;
}
pub const DEFAULT_QUEUE_FRAMES: u64 = 96_000;
#[derive(Debug)]
pub struct AudioStream {
source: Box<dyn AudioSource>,
resampler: Resampler,
buffer: AudioBuffer,
scratch: Vec<i16>,
out_rate: u32,
limit: u64,
overflowed: u64,
produced: u64,
}
impl AudioStream {
#[must_use]
pub fn new(source: Box<dyn AudioSource>, out_rate: u32, format: SampleFormat) -> AudioStream {
let info = source.info();
let out_rate = out_rate.max(1);
AudioStream {
resampler: Resampler::new(info, out_rate),
buffer: AudioBuffer::new(format, info.channels),
scratch: Vec::new(),
source,
out_rate,
limit: DEFAULT_QUEUE_FRAMES,
overflowed: 0,
produced: 0,
}
}
#[must_use]
pub fn source_info(&self) -> StreamInfo {
self.source.info()
}
#[must_use]
pub fn info(&self) -> StreamInfo {
let source = self.source.info();
StreamInfo::new(
u64::from(self.out_rate),
1,
source.channels,
self.buffer.format(),
)
}
#[inline]
#[must_use]
pub const fn rate(&self) -> u32 {
self.out_rate
}
pub fn set_rate(&mut self, out_rate: u32) {
let out_rate = out_rate.max(1);
if out_rate == self.out_rate {
return;
}
self.out_rate = out_rate;
self.resampler = Resampler::new(self.source.info(), out_rate);
self.buffer.clear();
}
pub const fn set_limit_frames(&mut self, frames: u64) {
self.limit = frames;
}
#[inline]
#[must_use]
pub const fn buffer(&self) -> &AudioBuffer {
&self.buffer
}
pub fn consume(&mut self, frames: u64) -> u64 {
self.buffer.consume(frames)
}
#[inline]
#[must_use]
pub const fn produced(&self) -> u64 {
self.produced
}
#[must_use]
pub fn dropped(&self) -> u64 {
self.source.dropped().saturating_add(self.overflowed)
}
pub fn pull(&mut self) -> u64 {
self.scratch.clear();
let taken = self.source.drain(&mut self.scratch);
if taken == 0 {
return 0;
}
let before = self.buffer.frames();
self.resampler.process(&self.scratch, &mut self.buffer);
let appended = self.buffer.frames().saturating_sub(before);
self.produced = self.produced.saturating_add(appended);
let queued = self.buffer.frames();
if queued > self.limit {
self.overflowed = self
.overflowed
.saturating_add(self.buffer.consume(queued - self.limit));
}
appended
}
pub fn drain_to(&mut self, sink: &mut dyn Sink) -> u64 {
let accepted = sink.write(&self.buffer);
self.buffer.consume(accepted);
accepted
}
}