#[cfg(not(feature = "std"))]
use num_traits::Float;
pub const DEFAULT_AMP_EPSILON: f32 = 0.00001;
pub const DEFAULT_DB_EPSILON: f32 = -100.0;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Volume {
Linear(f32),
Decibels(f32),
}
impl Volume {
pub const UNITY_GAIN: Self = Self::Linear(1.0);
pub const SILENT: Self = Self::Linear(0.0);
pub const fn from_percent(percent: f32) -> Self {
Self::Linear(percent / 100.0)
}
pub fn amp(&self) -> f32 {
match *self {
Self::Linear(volume) => linear_volume_to_amp_clamped(volume, 0.0),
Self::Decibels(db) => db_to_amp(db),
}
}
pub fn amp_clamped(&self, amp_epsilon: f32) -> f32 {
match *self {
Self::Linear(volume) => linear_volume_to_amp_clamped(volume, amp_epsilon),
Self::Decibels(db) => {
if db == f32::NEG_INFINITY {
0.0
} else {
let amp = db_to_amp(db);
if amp <= amp_epsilon {
0.0
} else {
amp
}
}
}
}
}
pub fn decibels(&self) -> f32 {
match *self {
Self::Linear(volume) => {
if volume == 0.0 {
f32::NEG_INFINITY
} else {
let amp = linear_volume_to_amp_clamped(volume, 0.0);
amp_to_db(amp)
}
}
Self::Decibels(db) => db,
}
}
pub fn decibels_clamped(&self, db_epsilon: f32) -> f32 {
match *self {
Self::Linear(volume) => {
if volume == 0.0 {
f32::NEG_INFINITY
} else {
let amp = linear_volume_to_amp_clamped(volume, 0.0);
let db = amp_to_db(amp);
if db <= db_epsilon {
f32::NEG_INFINITY
} else {
db
}
}
}
Self::Decibels(db) => {
if db <= db_epsilon {
f32::NEG_INFINITY
} else {
db
}
}
}
}
pub fn linear(&self) -> f32 {
match *self {
Self::Linear(volume) => volume,
Self::Decibels(db) => amp_to_linear_volume_clamped(db_to_amp(db), 0.0),
}
}
pub fn percent(&self) -> f32 {
self.linear() * 100.0
}
pub fn as_linear_variant(&self) -> Self {
Self::Linear(self.linear())
}
pub fn as_decibel_variant(&self) -> Self {
Self::Decibels(self.decibels())
}
}
impl Default for Volume {
fn default() -> Self {
Self::UNITY_GAIN
}
}
impl core::ops::Add<Self> for Volume {
type Output = Self;
fn add(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a + b),
(Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) + db_to_amp(b))),
(Linear(..), Decibels(..)) => self + rhs.as_linear_variant(),
(Decibels(..), Linear(..)) => self + rhs.as_decibel_variant(),
}
}
}
impl core::ops::Sub<Self> for Volume {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a - b),
(Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) - db_to_amp(b))),
(Linear(..), Decibels(..)) => self - rhs.as_linear_variant(),
(Decibels(..), Linear(..)) => self - rhs.as_decibel_variant(),
}
}
}
impl core::ops::Mul<Self> for Volume {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a * b),
(Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) * db_to_amp(b))),
(Linear(..), Decibels(..)) => self * rhs.as_linear_variant(),
(Decibels(..), Linear(..)) => self * rhs.as_decibel_variant(),
}
}
}
impl core::ops::Div<Self> for Volume {
type Output = Self;
fn div(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a / b),
(Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) / db_to_amp(b))),
(Linear(..), Decibels(..)) => self / rhs.as_linear_variant(),
(Decibels(..), Linear(..)) => self / rhs.as_decibel_variant(),
}
}
}
impl core::ops::AddAssign<Self> for Volume {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl core::ops::SubAssign<Self> for Volume {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl core::ops::MulAssign<Self> for Volume {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl core::ops::DivAssign<Self> for Volume {
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
#[inline]
pub fn db_to_amp(db: f32) -> f32 {
if db == f32::NEG_INFINITY {
0.0
} else {
10.0f32.powf(0.05 * db)
}
}
#[inline]
pub fn amp_to_db(amp: f32) -> f32 {
if amp == 0.0 {
f32::NEG_INFINITY
} else {
20.0 * amp.log10()
}
}
#[inline]
pub fn db_to_amp_clamped(db: f32, db_epsilon: f32) -> f32 {
if db == f32::NEG_INFINITY || db <= db_epsilon {
0.0
} else {
db_to_amp(db)
}
}
#[inline]
pub fn amp_to_db_clamped(amp: f32, amp_epsilon: f32) -> f32 {
if amp <= amp_epsilon {
f32::NEG_INFINITY
} else {
amp_to_db(amp)
}
}
#[inline]
pub fn linear_volume_to_amp_clamped(linear_volume: f32, amp_epsilon: f32) -> f32 {
let v = linear_volume * linear_volume;
if v <= amp_epsilon {
0.0
} else {
v
}
}
#[inline]
pub fn amp_to_linear_volume_clamped(amp: f32, amp_epsilon: f32) -> f32 {
if amp <= amp_epsilon {
0.0
} else {
amp.sqrt()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DbMeterNormalizer {
min_db: f32,
range_recip: f32,
factor: f32,
}
impl DbMeterNormalizer {
pub fn new(min_db: f32, max_db: f32, center_db: f32) -> Self {
assert!(max_db > min_db);
assert!(center_db > min_db && center_db < max_db);
let range_recip = (max_db - min_db).recip();
let center_normalized = ((center_db - min_db) * range_recip).clamp(0.0, 1.0);
Self {
min_db,
range_recip,
factor: 0.5_f32.log(center_normalized),
}
}
#[inline]
pub fn normalize(&self, db: f32) -> f32 {
((db - self.min_db) * self.range_recip)
.clamp(0.0, 1.0)
.powf(self.factor)
}
}
impl Default for DbMeterNormalizer {
fn default() -> Self {
Self::new(-100.0, 0.0, -22.0)
}
}
pub fn is_buffer_silent(buffer: &[f32], amp_epsilon: f32) -> bool {
let mut silent = true;
for &s in buffer.iter() {
if s.abs() > amp_epsilon {
silent = false;
break;
}
}
silent
}