#[cfg(not(feature = "std"))]
use num_traits::Float;
use core::f32::consts::FRAC_PI_2;
use core::{num::NonZeroU32, ops::Range};
#[cfg(not(feature = "std"))]
use bevy_platform::prelude::Vec;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Declicker {
SettledAt0,
#[default]
SettledAt1,
FadingTo0 {
frames_left: usize,
},
FadingTo1 {
frames_left: usize,
},
}
impl Declicker {
pub fn from_enabled(enabled: bool) -> Self {
if enabled {
Self::SettledAt1
} else {
Self::SettledAt0
}
}
pub fn has_settled(&self) -> bool {
*self == Self::SettledAt0 || *self == Self::SettledAt1
}
pub fn disabled(&self) -> bool {
*self == Self::SettledAt0
}
pub fn fade_to_enabled(&mut self, enabled: bool, declick_values: &DeclickValues) {
if enabled {
self.fade_to_1(declick_values);
} else {
self.fade_to_0(declick_values);
}
}
pub fn fade_to_0(&mut self, declick_values: &DeclickValues) {
match self {
Self::SettledAt1 => {
*self = Self::FadingTo0 {
frames_left: declick_values.frames(),
}
}
Self::FadingTo1 { frames_left } => {
let frames_left = if *frames_left <= declick_values.frames() {
declick_values.frames() - *frames_left
} else {
declick_values.frames()
};
*self = Self::FadingTo0 { frames_left }
}
_ => {}
}
}
pub fn fade_to_1(&mut self, declick_values: &DeclickValues) {
match self {
Self::SettledAt0 => {
*self = Self::FadingTo1 {
frames_left: declick_values.frames(),
}
}
Self::FadingTo0 { frames_left } => {
let frames_left = if *frames_left <= declick_values.frames() {
declick_values.frames() - *frames_left
} else {
declick_values.frames()
};
*self = Self::FadingTo1 { frames_left }
}
_ => {}
}
}
pub fn reset_to_target(&mut self) {
*self = match &self {
Self::FadingTo0 { .. } => Self::SettledAt0,
Self::FadingTo1 { .. } => Self::SettledAt1,
s => **s,
}
}
pub fn reset_to_0(&mut self) {
*self = Self::SettledAt0;
}
pub fn reset_to_1(&mut self) {
*self = Self::SettledAt1;
}
pub fn process_crossfade<VA: AsRef<[f32]>, VB: AsMut<[f32]>>(
&mut self,
buffers_a: &[VA],
buffers_b: &mut [VB],
buffer_a_range: Range<usize>,
buffer_b_range: Range<usize>,
declick_values: &DeclickValues,
fade_curve: DeclickFadeCurve,
) {
let frames = (buffer_a_range.end - buffer_a_range.start)
.min(buffer_b_range.end - buffer_b_range.start);
let mut crossfade_buffers =
|declick_frames_left: &mut usize, values_a: &[f32], values_b: &[f32]| -> usize {
let process_frames = frames.min(*declick_frames_left);
let values_start = values_a.len() - *declick_frames_left;
let values_a = &values_a[values_start..values_start + process_frames];
let values_b = &values_b[values_start..values_start + process_frames];
for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
let slice_a =
&ch_a.as_ref()[buffer_a_range.start..buffer_a_range.start + process_frames];
let slice_b = &mut ch_b.as_mut()
[buffer_b_range.start..buffer_b_range.start + process_frames];
for i in 0..process_frames {
slice_b[i] = (slice_a[i] * values_a[i]) + (slice_b[i] * values_b[i]);
}
}
*declick_frames_left -= process_frames;
process_frames
};
match self {
Self::SettledAt0 => {
for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
let slice_a =
&ch_a.as_ref()[buffer_a_range.start..buffer_a_range.start + frames];
let slice_b =
&mut ch_b.as_mut()[buffer_b_range.start..buffer_b_range.start + frames];
slice_b.copy_from_slice(slice_a);
}
}
Self::FadingTo0 { frames_left } => {
let (values_a, values_b) = match fade_curve {
DeclickFadeCurve::Linear => (
&declick_values.linear_0_to_1_values,
&declick_values.linear_1_to_0_values,
),
DeclickFadeCurve::EqualPower3dB => (
&declick_values.circular_0_to_1_values,
&declick_values.circular_1_to_0_values,
),
};
let frames_processed = crossfade_buffers(frames_left, values_a, values_b);
if frames_processed < frames {
for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
let slice_a = &ch_a.as_ref()[buffer_a_range.start + frames_processed
..buffer_a_range.start + frames];
let slice_b = &mut ch_b.as_mut()[buffer_b_range.start + frames_processed
..buffer_b_range.start + frames];
slice_b.copy_from_slice(slice_a);
}
}
if *frames_left == 0 {
*self = Self::SettledAt0;
}
}
Self::FadingTo1 { frames_left } => {
let (values_a, values_b) = match fade_curve {
DeclickFadeCurve::Linear => (
&declick_values.linear_1_to_0_values,
&declick_values.linear_0_to_1_values,
),
DeclickFadeCurve::EqualPower3dB => (
&declick_values.circular_1_to_0_values,
&declick_values.circular_0_to_1_values,
),
};
crossfade_buffers(frames_left, values_a, values_b);
if *frames_left == 0 {
*self = Self::SettledAt1;
}
}
_ => {}
}
}
pub fn process_into_gain_buffer(
&mut self,
buffer: &mut [f32],
buffer_already_cleared: bool,
declick_values: &DeclickValues,
fade_curve: DeclickFadeCurve,
) {
let fade_buffer = |buffer: &mut [f32],
declick_frames_left: &mut usize,
values: &[f32],
fill_rest_with: f32| {
let process_frames = buffer.len().min(*declick_frames_left);
let start_frame = values.len() - *declick_frames_left;
buffer[..process_frames]
.copy_from_slice(&values[start_frame..start_frame + process_frames]);
if process_frames < buffer.len() {
buffer[process_frames..].fill(fill_rest_with);
}
*declick_frames_left -= process_frames;
};
match self {
Self::SettledAt0 => {
if !buffer_already_cleared {
buffer.fill(0.0);
}
}
Self::SettledAt1 => {
buffer.fill(1.0);
}
Self::FadingTo0 { frames_left } => {
let values = match fade_curve {
DeclickFadeCurve::Linear => &declick_values.linear_1_to_0_values,
DeclickFadeCurve::EqualPower3dB => &declick_values.circular_1_to_0_values,
};
fade_buffer(buffer, frames_left, values, 0.0);
if *frames_left == 0 {
*self = Self::SettledAt0;
}
}
Self::FadingTo1 { frames_left } => {
let values = match fade_curve {
DeclickFadeCurve::Linear => &declick_values.linear_0_to_1_values,
DeclickFadeCurve::EqualPower3dB => &declick_values.circular_0_to_1_values,
};
fade_buffer(buffer, frames_left, values, 1.0);
if *frames_left == 0 {
*self = Self::SettledAt1;
}
}
}
}
pub fn process_into_crossfade_gain_buffers(
&mut self,
wet_buffer: &mut [f32],
dry_buffer: &mut [f32],
buffer_already_cleared: bool,
declick_values: &DeclickValues,
fade_curve: DeclickFadeCurve,
) {
let buffer_frames = wet_buffer.len().min(dry_buffer.len());
let fade_buffer = |buffer: &mut [f32],
proc_frames: usize,
declick_frames_left: usize,
values: &[f32],
fill_rest_with: f32| {
let start_frame = values.len() - declick_frames_left;
buffer[..proc_frames].copy_from_slice(&values[start_frame..start_frame + proc_frames]);
if proc_frames < buffer.len() {
buffer[proc_frames..].fill(fill_rest_with);
}
};
match self {
Self::SettledAt0 => {
if !buffer_already_cleared {
wet_buffer.fill(0.0);
}
dry_buffer.fill(1.0);
}
Self::SettledAt1 => {
wet_buffer.fill(1.0);
if !buffer_already_cleared {
dry_buffer.fill(0.0);
}
}
Self::FadingTo0 { frames_left } => {
let (wet_values, dry_values) = match fade_curve {
DeclickFadeCurve::Linear => (
&declick_values.linear_1_to_0_values,
&declick_values.linear_0_to_1_values,
),
DeclickFadeCurve::EqualPower3dB => (
&declick_values.circular_1_to_0_values,
&declick_values.circular_0_to_1_values,
),
};
let proc_frames = buffer_frames.min(*frames_left);
fade_buffer(wet_buffer, proc_frames, *frames_left, wet_values, 0.0);
fade_buffer(dry_buffer, proc_frames, *frames_left, dry_values, 1.0);
*frames_left -= proc_frames;
if *frames_left == 0 {
*self = Self::SettledAt0;
}
}
Self::FadingTo1 { frames_left } => {
let (wet_values, dry_values) = match fade_curve {
DeclickFadeCurve::Linear => (
&declick_values.linear_0_to_1_values,
&declick_values.linear_1_to_0_values,
),
DeclickFadeCurve::EqualPower3dB => (
&declick_values.circular_0_to_1_values,
&declick_values.circular_1_to_0_values,
),
};
let proc_frames = buffer_frames.min(*frames_left);
fade_buffer(wet_buffer, proc_frames, *frames_left, wet_values, 1.0);
fade_buffer(dry_buffer, proc_frames, *frames_left, dry_values, 0.0);
*frames_left -= proc_frames;
if *frames_left == 0 {
*self = Self::SettledAt1;
}
}
}
}
pub fn process<V: AsMut<[f32]>>(
&mut self,
buffers: &mut [V],
range_in_buffer: Range<usize>,
declick_values: &DeclickValues,
gain: f32,
fade_curve: DeclickFadeCurve,
) {
let mut fade_buffers = |declick_frames_left: &mut usize, values: &[f32]| -> usize {
let buffer_frames = range_in_buffer.end - range_in_buffer.start;
let process_frames = buffer_frames.min(*declick_frames_left);
let start_frame = values.len() - *declick_frames_left;
if gain == 1.0 {
for b in buffers.iter_mut() {
let b = &mut b.as_mut()
[range_in_buffer.start..range_in_buffer.start + process_frames];
for (s, &g) in b
.iter_mut()
.zip(values[start_frame..start_frame + process_frames].iter())
{
*s *= g;
}
}
} else {
for b in buffers.iter_mut() {
let b = &mut b.as_mut()
[range_in_buffer.start..range_in_buffer.start + process_frames];
for (s, &g) in b
.iter_mut()
.zip(values[start_frame..start_frame + process_frames].iter())
{
*s *= g * gain;
}
}
}
*declick_frames_left -= process_frames;
process_frames
};
match self {
Self::SettledAt0 => {
for b in buffers.iter_mut() {
let b = &mut b.as_mut();
b[range_in_buffer.clone()].fill(0.0);
}
}
Self::FadingTo0 { frames_left } => {
let values = match fade_curve {
DeclickFadeCurve::Linear => &declick_values.linear_1_to_0_values,
DeclickFadeCurve::EqualPower3dB => &declick_values.circular_1_to_0_values,
};
let frames_processed = fade_buffers(frames_left, values);
if frames_processed < range_in_buffer.end - range_in_buffer.start {
for b in buffers.iter_mut() {
let b = &mut b.as_mut()
[range_in_buffer.start + frames_processed..range_in_buffer.end];
b.fill(0.0);
}
}
if *frames_left == 0 {
*self = Self::SettledAt0;
}
}
Self::FadingTo1 { frames_left } => {
let values = match fade_curve {
DeclickFadeCurve::Linear => &declick_values.linear_0_to_1_values,
DeclickFadeCurve::EqualPower3dB => &declick_values.circular_0_to_1_values,
};
let frames_processed = fade_buffers(frames_left, values);
if frames_processed < range_in_buffer.end - range_in_buffer.start && gain != 1.0 {
for b in buffers.iter_mut() {
let b = &mut b.as_mut()
[range_in_buffer.start + frames_processed..range_in_buffer.end];
for s in b.iter_mut() {
*s *= gain;
}
}
}
if *frames_left == 0 {
*self = Self::SettledAt1;
}
}
_ => {}
}
}
pub fn trending_towards_zero(&self) -> bool {
matches!(self, Declicker::SettledAt0 | Declicker::FadingTo0 { .. })
}
pub fn trending_towards_one(&self) -> bool {
matches!(self, Declicker::SettledAt1 | Declicker::FadingTo1 { .. })
}
pub fn frames_left(&self) -> usize {
match *self {
Declicker::FadingTo0 { frames_left } => frames_left,
Declicker::FadingTo1 { frames_left } => frames_left,
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeclickFadeCurve {
Linear,
EqualPower3dB,
}
pub struct DeclickValues {
pub linear_0_to_1_values: Vec<f32>,
pub linear_1_to_0_values: Vec<f32>,
pub circular_0_to_1_values: Vec<f32>,
pub circular_1_to_0_values: Vec<f32>,
}
impl DeclickValues {
pub const DEFAULT_FADE_SECONDS: f32 = 3.0 / 1_000.0;
pub fn new(frames: NonZeroU32) -> Self {
let frames = frames.get() as usize;
let frames_recip = (frames as f32).recip();
let mut linear_0_to_1_values = Vec::new();
let mut linear_1_to_0_values = Vec::new();
let mut circular_0_to_1_values = Vec::new();
let mut circular_1_to_0_values = Vec::new();
linear_0_to_1_values.reserve_exact(frames);
linear_1_to_0_values.reserve_exact(frames);
circular_0_to_1_values.reserve_exact(frames);
circular_1_to_0_values.reserve_exact(frames);
linear_0_to_1_values = (0..frames).map(|i| i as f32 * frames_recip).collect();
linear_1_to_0_values = (0..frames).rev().map(|i| i as f32 * frames_recip).collect();
circular_0_to_1_values = linear_0_to_1_values
.iter()
.map(|x| (x * FRAC_PI_2).sin())
.collect();
circular_1_to_0_values = circular_0_to_1_values.iter().rev().copied().collect();
Self {
linear_0_to_1_values,
linear_1_to_0_values,
circular_0_to_1_values,
circular_1_to_0_values,
}
}
pub fn frames(&self) -> usize {
self.linear_0_to_1_values.len()
}
}