use std::fmt;
use std::slice;
use byteorder::NetworkEndian;
use crate::protocol::ProtocolError;
use super::sample_spec::MAX_CHANNELS;
use super::*;
const VOLUME_NORM: u32 = 0x10000;
const VOLUME_MUTED: u32 = 0;
const VOLUME_MAX: u32 = u32::MAX / 2;
#[derive(Copy, Clone, Default, PartialEq, Eq)]
pub struct Volume(u32);
impl Volume {
pub const NORM: Self = Volume(VOLUME_NORM);
pub const MUTED: Self = Volume(VOLUME_MUTED);
pub fn as_u32(&self) -> u32 {
self.0
}
pub fn from_u32_clamped(raw: u32) -> Self {
Volume(raw.min(VOLUME_MAX))
}
pub fn to_db(&self) -> f32 {
self.to_linear().log10() * 20.0
}
pub fn to_linear(&self) -> f32 {
let f = self.0 as f32 / VOLUME_NORM as f32;
f * f * f
}
pub fn from_linear(linear: f32) -> Self {
let raw = (linear.cbrt() * VOLUME_NORM as f32) as u32;
Volume(match raw {
_ if raw > VOLUME_MAX => VOLUME_MAX,
_ => raw,
})
}
}
impl fmt::Display for Volume {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1} dB", self.to_db())
}
}
impl fmt::Debug for Volume {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Volume")
.field(&format!(
"raw={:.1}, linear={:.1}, {:.1} dB",
self.0 as f32 / VOLUME_NORM as f32,
self.to_linear(),
self.to_db()
))
.finish()
}
}
impl TagStructRead for Volume {
fn read(ts: &mut TagStructReader<'_>, _protocol_version: u16) -> Result<Self, ProtocolError> {
ts.expect_tag(Tag::Volume)?;
Ok(Volume::from_u32_clamped(
ts.inner.read_u32::<NetworkEndian>()?,
))
}
}
impl TagStructWrite for Volume {
fn write(
&self,
w: &mut TagStructWriter<'_>,
_protocol_version: u16,
) -> Result<(), ProtocolError> {
w.inner.write_u8(Tag::Volume as u8)?;
w.inner.write_u32::<NetworkEndian>(self.as_u32())?;
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ChannelVolume {
channels: u8,
volumes: [Volume; MAX_CHANNELS as usize],
}
impl Default for ChannelVolume {
fn default() -> Self {
Self {
channels: 1,
volumes: [Volume::MUTED; MAX_CHANNELS as usize],
}
}
}
impl ChannelVolume {
pub fn empty() -> Self {
Self {
channels: 0,
volumes: [Volume::MUTED; MAX_CHANNELS as usize],
}
}
pub fn muted(channels: u8) -> ChannelVolume {
Self {
channels,
volumes: [Volume::MUTED; MAX_CHANNELS as usize],
}
}
pub fn norm(channels: u8) -> ChannelVolume {
Self {
channels,
volumes: [Volume::NORM; MAX_CHANNELS as usize],
}
}
pub fn push(&mut self, volume: Volume) {
if self.channels < MAX_CHANNELS {
self.volumes[self.channels as usize] = volume;
self.channels += 1;
}
}
pub fn channels(&self) -> &[Volume] {
&self.volumes[..self.channels as usize]
}
}
impl fmt::Debug for ChannelVolume {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.volumes[..self.channels.into()].fmt(f)
}
}
#[derive(Debug)]
pub struct Iter<'a> {
inner: slice::Iter<'a, Volume>,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a Volume;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
self.inner.next()
}
}
impl TagStructRead for ChannelVolume {
fn read(ts: &mut TagStructReader<'_>, _protocol_version: u16) -> Result<Self, ProtocolError> {
ts.expect_tag(Tag::CVolume)?;
let n_channels = ts.inner.read_u8()?;
if n_channels == 0 || n_channels > MAX_CHANNELS {
return Err(ProtocolError::Invalid(format!(
"invalid cvolume channel count {n_channels}, must be between 1 and {MAX_CHANNELS}"
)));
}
let mut cvolume = ChannelVolume::empty();
for _ in 0..n_channels {
let raw = ts.inner.read_u32::<NetworkEndian>()?;
cvolume.push(Volume::from_u32_clamped(raw))
}
Ok(cvolume)
}
}
impl TagStructWrite for ChannelVolume {
fn write(
&self,
w: &mut TagStructWriter<'_>,
_protocol_version: u16,
) -> Result<(), ProtocolError> {
w.inner.write_u8(Tag::CVolume as u8)?;
w.inner.write_u8(self.channels().len() as u8)?;
for volume in self.channels() {
w.inner.write_u32::<NetworkEndian>(volume.as_u32())?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::protocol::{test_util::test_serde_version, MAX_VERSION};
use super::*;
use std::f32;
#[test]
fn volume_serde() -> anyhow::Result<()> {
let v = Volume::from_linear(0.5);
test_serde_version(&v, MAX_VERSION)?;
Ok(())
}
#[test]
fn cvolume_serde() -> anyhow::Result<()> {
let mut cv = ChannelVolume::default();
cv.push(Volume::from_linear(0.5));
cv.push(Volume::from_linear(0.5));
test_serde_version(&cv, MAX_VERSION)?;
Ok(())
}
#[test]
fn volume_conversions() {
assert_eq!(Volume::NORM.to_linear(), 1.0);
assert_eq!(Volume::MUTED.to_linear(), 0.0);
assert_eq!(Volume::from_linear(-43.0).to_linear(), 0.0);
assert_eq!(Volume::NORM.to_db(), 0.0);
assert_eq!(Volume::MUTED.to_db(), -f32::INFINITY);
}
}