use crate::gain_control_data::{GainBand, GainControlData};
use crate::ics_info::WindowSequence;
#[must_use]
pub fn adj_loc(ac: u8) -> u32 {
8 * u32::from(ac)
}
#[must_use]
pub fn adj_lev(av: u8) -> i32 {
i32::from(av) - 4
}
#[must_use]
pub fn num_windows(seq: WindowSequence) -> usize {
match seq {
WindowSequence::OnlyLong => 1,
WindowSequence::LongStart | WindowSequence::LongStop => 2,
WindowSequence::EightShort => 8,
}
}
#[must_use]
pub fn endpoint_aloc(seq: WindowSequence, w: usize) -> u32 {
match seq {
WindowSequence::OnlyLong => 256,
WindowSequence::LongStart => {
if w == 0 {
112
} else {
32
}
}
WindowSequence::EightShort => 32,
WindowSequence::LongStop => {
if w == 0 {
112
} else {
256
}
}
}
}
#[must_use]
fn fmd_last_j(seq: WindowSequence, w: usize) -> usize {
match seq {
WindowSequence::OnlyLong => 255,
WindowSequence::LongStart => {
if w == 0 {
111
} else {
31
}
}
WindowSequence::EightShort => 31,
WindowSequence::LongStop => {
if w == 0 {
111
} else {
255
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
struct Ladder {
aloc: Vec<u32>,
alev: Vec<f64>,
}
impl Ladder {
fn reconstruct(
window: &crate::gain_control_data::GainWindow,
seq: WindowSequence,
w: usize,
) -> Self {
let nadw = window.adjustments.len();
let mut aloc = Vec::with_capacity(nadw + 2);
let mut alev = Vec::with_capacity(nadw + 2);
aloc.push(0);
alev.push(1.0);
for adj in &window.adjustments {
aloc.push(adj_loc(adj.aloccode));
alev.push(2f64.powi(adj_lev(adj.alevcode)));
}
aloc.push(endpoint_aloc(seq, w));
alev.push(1.0);
if nadw > 0 {
alev[0] = alev[1];
}
Ladder { aloc, alev }
}
fn m_at(&self, j: u32) -> usize {
let mut m = 0usize;
for (idx, &loc) in self.aloc.iter().enumerate() {
if loc <= j {
m = idx;
} else {
break;
}
}
m
}
}
#[must_use]
fn inter(a: f64, b: f64, j: u32) -> f64 {
let la = a.log2();
let lb = b.log2();
let jf = j as f64;
let exp = ((8.0 - jf) * la + jf * lb) / 8.0;
2f64.powf(exp)
}
#[derive(Debug, Clone, PartialEq)]
pub struct BandGainFunction {
pub ad: Vec<Vec<f64>>,
pub pfmd_next: Vec<f64>,
}
fn fmd_window(ladder: &Ladder, seq: WindowSequence, w: usize) -> Vec<f64> {
let last = fmd_last_j(seq, w);
let mut fmd = vec![0.0f64; last + 1];
for (j, slot) in fmd.iter_mut().enumerate() {
let m = ladder.m_at(j as u32);
let loc_m = ladder.aloc[m];
let alev_m = ladder.alev[m];
let alev_m1 = ladder.alev[m + 1];
let jj = j as u32;
*slot = if jj <= loc_m + 7 {
inter(alev_m, alev_m1, jj - loc_m)
} else {
alev_m1
};
}
fmd
}
fn alev0(band: &GainBand, w: usize) -> f64 {
let window = &band.windows[w];
if window.adjustments.is_empty() {
1.0
} else {
2f64.powi(adj_lev(window.adjustments[0].alevcode))
}
}
fn gmf_long(
fmd: &[Vec<f64>],
band: &GainBand,
pfmd_prev: &[f64],
seq: WindowSequence,
) -> (Vec<f64>, Vec<f64>) {
let mut gmf = vec![0.0f64; 512];
let pfmd_next = match seq {
WindowSequence::OnlyLong => {
let a0 = alev0(band, 0);
for (j, slot) in gmf.iter_mut().enumerate() {
*slot = if j <= 255 {
a0 * pfmd_prev[j]
} else {
fmd[0][j - 256]
};
}
fmd[0][..256].to_vec()
}
WindowSequence::LongStart => {
let a0 = alev0(band, 0);
let a1 = alev0(band, 1);
for (j, slot) in gmf.iter_mut().enumerate() {
*slot = if j <= 255 {
a0 * a1 * pfmd_prev[j]
} else if j <= 367 {
a1 * fmd[0][j - 256]
} else if j <= 399 {
fmd[1][j - 368]
} else {
1.0
};
}
fmd[1][..32].to_vec()
}
WindowSequence::LongStop => {
let a0 = alev0(band, 0);
let a1 = alev0(band, 1);
for (j, slot) in gmf.iter_mut().enumerate() {
*slot = if j <= 111 {
1.0
} else if j <= 143 {
a0 * a1 * pfmd_prev[j - 112]
} else if j <= 255 {
a1 * fmd[0][j - 144]
} else {
fmd[1][j - 256]
};
}
fmd[1][..256].to_vec()
}
WindowSequence::EightShort => unreachable!("gmf_long called for short sequence"),
};
(gmf, pfmd_next)
}
fn gmf_short(fmd: &[Vec<f64>], band: &GainBand, pfmd_prev: &[f64]) -> (Vec<Vec<f64>>, Vec<f64>) {
let mut gmf: Vec<Vec<f64>> = Vec::with_capacity(8);
for w in 0..8 {
let a0 = alev0(band, w);
let mut g = vec![0.0f64; 64];
for (j, slot) in g.iter_mut().enumerate() {
*slot = if j <= 31 {
if w == 0 {
a0 * pfmd_prev[j]
} else {
a0 * fmd[w - 1][j]
}
} else {
fmd[w][j - 32]
};
}
gmf.push(g);
}
let pfmd_next = fmd[7][..32].to_vec();
(gmf, pfmd_next)
}
impl BandGainFunction {
#[must_use]
pub fn reconstruct(band: &GainBand, seq: WindowSequence, pfmd_prev: &[f64]) -> Self {
let n_win = num_windows(seq);
let fmd: Vec<Vec<f64>> = (0..n_win)
.map(|w| {
let ladder = Ladder::reconstruct(&band.windows[w], seq, w);
fmd_window(&ladder, seq, w)
})
.collect();
match seq {
WindowSequence::EightShort => {
let (gmf, pfmd_next) = gmf_short(&fmd, band, pfmd_prev);
let ad = gmf
.iter()
.map(|g| g.iter().map(|&v| 1.0 / v).collect())
.collect();
BandGainFunction { ad, pfmd_next }
}
_ => {
let (gmf, pfmd_next) = gmf_long(&fmd, band, pfmd_prev, seq);
let ad = vec![gmf.iter().map(|&v| 1.0 / v).collect()];
BandGainFunction { ad, pfmd_next }
}
}
}
#[must_use]
pub fn identity(seq: WindowSequence) -> Self {
match seq {
WindowSequence::EightShort => BandGainFunction {
ad: vec![vec![1.0; 64]; 8],
pfmd_next: vec![1.0; 32],
},
_ => BandGainFunction {
ad: vec![vec![1.0; 512]],
pfmd_next: vec![1.0; 256],
},
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GainBandState {
pfmd: Vec<f64>,
pt: Vec<f64>,
}
impl Default for GainBandState {
fn default() -> Self {
Self::new()
}
}
impl GainBandState {
#[must_use]
pub fn new() -> Self {
GainBandState {
pfmd: vec![1.0; 256],
pt: vec![0.0; 256],
}
}
#[must_use]
pub fn window_overlap(
&mut self,
band: Option<&GainBand>,
u: &[f64],
seq: WindowSequence,
) -> Vec<f64> {
let t = match band {
Some(b) => {
let g = BandGainFunction::reconstruct(b, seq, &self.pfmd);
self.store_pfmd(&g.pfmd_next);
apply_gain(&g.ad, u, seq)
}
None => {
let g = BandGainFunction::identity(seq);
self.store_pfmd(&g.pfmd_next);
u.to_vec()
}
};
self.overlap(&t, seq)
}
fn store_pfmd(&mut self, produced: &[f64]) {
self.pfmd[..produced.len()].copy_from_slice(produced);
}
fn overlap(&mut self, t: &[f64], seq: WindowSequence) -> Vec<f64> {
match seq {
WindowSequence::OnlyLong => {
let v = add_slices(&self.pt[..256], &t[..256]);
self.pt[..256].copy_from_slice(&t[256..512]);
v
}
WindowSequence::LongStart => {
let mut v = vec![0.0f64; 368];
add_into(&mut v[..256], &self.pt[..256], &t[..256]);
v[256..368].copy_from_slice(&t[256..368]);
self.pt[..32].copy_from_slice(&t[368..400]);
v
}
WindowSequence::EightShort => {
let mut v = vec![0.0f64; 256];
add_into(&mut v[..32], &self.pt[..32], &t[..32]);
for w in 1..=7 {
let prev = &t[64 * (w - 1) + 32..64 * (w - 1) + 64];
let cur = &t[64 * w..64 * w + 32];
add_into(&mut v[32 * w..32 * w + 32], prev, cur);
}
self.pt[..32].copy_from_slice(&t[64 * 7 + 32..64 * 7 + 64]);
v
}
WindowSequence::LongStop => {
let mut v = vec![0.0f64; 144];
add_into(&mut v[..32], &self.pt[..32], &t[112..144]);
v[32..144].copy_from_slice(&t[144..256]);
self.pt[..256].copy_from_slice(&t[256..512]);
v
}
}
}
}
fn add_slices(a: &[f64], b: &[f64]) -> Vec<f64> {
a.iter().zip(b.iter()).map(|(&x, &y)| x + y).collect()
}
fn add_into(dst: &mut [f64], a: &[f64], b: &[f64]) {
for (d, (&x, &y)) in dst.iter_mut().zip(a.iter().zip(b.iter())) {
*d = x + y;
}
}
fn apply_gain(ad: &[Vec<f64>], u: &[f64], seq: WindowSequence) -> Vec<f64> {
match seq {
WindowSequence::EightShort => {
let mut t = vec![0.0f64; u.len()];
for (w, ad_w) in ad.iter().enumerate() {
for (j, &g) in ad_w.iter().enumerate() {
let idx = 64 * w + j;
t[idx] = g * u[idx];
}
}
t
}
_ => ad[0].iter().zip(u.iter()).map(|(&g, &x)| g * x).collect(),
}
}
#[must_use]
pub fn pfmd_len(seq: WindowSequence) -> usize {
match seq {
WindowSequence::OnlyLong | WindowSequence::LongStart => 256,
WindowSequence::LongStop | WindowSequence::EightShort => 32,
}
}
#[must_use]
pub fn pfmd_produced_len(seq: WindowSequence) -> usize {
match seq {
WindowSequence::OnlyLong | WindowSequence::LongStop => 256,
WindowSequence::LongStart | WindowSequence::EightShort => 32,
}
}
#[must_use]
pub fn band_record(gcd: &GainControlData, b: usize) -> Option<&GainBand> {
if b == 0 || b > gcd.max_band as usize {
None
} else {
gcd.bands.get(b - 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adj_loc_is_eight_times() {
assert_eq!(adj_loc(0), 0);
assert_eq!(adj_loc(1), 8);
assert_eq!(adj_loc(15), 120);
assert_eq!(adj_loc(31), 248);
}
#[test]
fn adj_lev_is_offset_minus_four() {
assert_eq!(adj_lev(0), -4);
assert_eq!(adj_lev(4), 0);
assert_eq!(adj_lev(15), 11);
}
#[test]
fn endpoint_aloc_per_sequence() {
assert_eq!(endpoint_aloc(WindowSequence::OnlyLong, 0), 256);
assert_eq!(endpoint_aloc(WindowSequence::LongStart, 0), 112);
assert_eq!(endpoint_aloc(WindowSequence::LongStart, 1), 32);
assert_eq!(endpoint_aloc(WindowSequence::EightShort, 3), 32);
assert_eq!(endpoint_aloc(WindowSequence::LongStop, 0), 112);
assert_eq!(endpoint_aloc(WindowSequence::LongStop, 1), 256);
}
#[test]
fn inter_endpoints_are_exact() {
assert!((inter(2.0, 8.0, 0) - 2.0).abs() < 1e-12);
assert!((inter(2.0, 8.0, 8) - 8.0).abs() < 1e-12);
assert!((inter(2.0, 8.0, 4) - (2.0f64 * 8.0).sqrt()).abs() < 1e-12);
}
#[test]
fn empty_ladder_gives_unit_gain() {
let band = GainBand {
windows: vec![crate::gain_control_data::GainWindow::default()],
};
let pfmd = vec![1.0f64; 256];
let g = BandGainFunction::reconstruct(&band, WindowSequence::OnlyLong, &pfmd);
assert_eq!(g.ad.len(), 1);
assert_eq!(g.ad[0].len(), 512);
for &v in &g.ad[0] {
assert!((v - 1.0).abs() < 1e-12, "expected unit gain, got {v}");
}
assert!(g.pfmd_next.iter().all(|&v| (v - 1.0).abs() < 1e-12));
}
#[test]
fn identity_matches_empty_ladder() {
let band = GainBand {
windows: vec![crate::gain_control_data::GainWindow::default(); 8],
};
let pfmd = vec![1.0f64; 32];
let recon = BandGainFunction::reconstruct(&band, WindowSequence::EightShort, &pfmd);
let ident = BandGainFunction::identity(WindowSequence::EightShort);
assert_eq!(recon.ad.len(), ident.ad.len());
for (r, i) in recon.ad.iter().zip(ident.ad.iter()) {
for (&rv, &iv) in r.iter().zip(i.iter()) {
assert!((rv - iv).abs() < 1e-12);
}
}
}
#[test]
fn overlap_only_long_is_tdac_add() {
let mut st = GainBandState::new();
let u: Vec<f64> = (0..512).map(|j| (j as f64) * 0.01).collect();
let v0 = st.window_overlap(None, &u, WindowSequence::OnlyLong);
assert_eq!(v0.len(), 256);
for j in 0..256 {
assert!((v0[j] - u[j]).abs() < 1e-12);
}
let v1 = st.window_overlap(None, &u, WindowSequence::OnlyLong);
for j in 0..256 {
assert!((v1[j] - (u[256 + j] + u[j])).abs() < 1e-12);
}
}
#[test]
fn overlap_lengths_per_sequence() {
let u_long = vec![1.0f64; 512];
let u_short = vec![1.0f64; 512]; assert_eq!(
GainBandState::new()
.window_overlap(None, &u_long, WindowSequence::OnlyLong)
.len(),
256
);
assert_eq!(
GainBandState::new()
.window_overlap(None, &u_long, WindowSequence::LongStart)
.len(),
368
);
assert_eq!(
GainBandState::new()
.window_overlap(None, &u_short, WindowSequence::EightShort)
.len(),
256
);
assert_eq!(
GainBandState::new()
.window_overlap(None, &u_long, WindowSequence::LongStop)
.len(),
144
);
}
#[test]
fn overlap_eight_short_overlaps_adjacent_windows() {
let mut st = GainBandState::new();
let mut u = vec![0.0f64; 512];
for w in 0..8 {
for j in 0..64 {
u[64 * w + j] = (w as f64) + 1.0;
}
}
let v = st.window_overlap(None, &u, WindowSequence::EightShort);
assert_eq!(v.len(), 256);
assert!((v[0] - 1.0).abs() < 1e-12);
assert!((v[32] - 3.0).abs() < 1e-12);
assert!((v[32 * 7] - 15.0).abs() < 1e-12);
assert!((st.pt[0] - 8.0).abs() < 1e-12);
}
#[test]
fn gain_then_overlap_scales_band() {
use crate::gain_control_data::{GainAdjust, GainWindow};
let band = GainBand {
windows: vec![GainWindow {
adjustments: vec![GainAdjust {
alevcode: 6, aloccode: 0, }],
}],
};
let mut st = GainBandState::new();
let u = vec![1.0f64; 512];
let v = st.window_overlap(Some(&band), &u, WindowSequence::OnlyLong);
assert_eq!(v.len(), 256);
assert!(v.iter().all(|x| x.is_finite()));
}
#[test]
fn single_gain_change_scales_segment() {
use crate::gain_control_data::{GainAdjust, GainWindow};
let band = GainBand {
windows: vec![GainWindow {
adjustments: vec![GainAdjust {
alevcode: 6,
aloccode: 2,
}],
}],
};
let pfmd = vec![1.0f64; 256];
let g = BandGainFunction::reconstruct(&band, WindowSequence::OnlyLong, &pfmd);
assert!((g.ad[0][256] - 0.25).abs() < 1e-9, "AD={}", g.ad[0][256]);
assert!((g.ad[0][511] - 1.0).abs() < 1e-9, "AD={}", g.ad[0][511]);
}
#[test]
fn ad_is_finite_positive_all_sequences() {
use crate::gain_control_data::{GainAdjust, GainWindow};
for &seq in &[
WindowSequence::OnlyLong,
WindowSequence::LongStart,
WindowSequence::LongStop,
WindowSequence::EightShort,
] {
let n_win = num_windows(seq);
let windows = (0..n_win)
.map(|_| GainWindow {
adjustments: vec![GainAdjust {
alevcode: 7, aloccode: 1, }],
})
.collect();
let band = GainBand { windows };
let pfmd = vec![1.0f64; pfmd_len(seq)];
let g = BandGainFunction::reconstruct(&band, seq, &pfmd);
for win in &g.ad {
for &v in win {
assert!(v.is_finite() && v > 0.0, "AD={v} for {seq:?}");
}
}
assert_eq!(g.pfmd_next.len(), pfmd_produced_len(seq));
}
}
#[test]
fn ad_times_gmf_is_one() {
use crate::gain_control_data::{GainAdjust, GainWindow};
let band = GainBand {
windows: vec![GainWindow {
adjustments: vec![
GainAdjust {
alevcode: 8,
aloccode: 2,
},
GainAdjust {
alevcode: 2,
aloccode: 10,
},
],
}],
};
let pfmd = vec![1.0f64; 256];
let g = BandGainFunction::reconstruct(&band, WindowSequence::OnlyLong, &pfmd);
for &ad in &g.ad[0] {
let gmf = 1.0 / ad;
assert!((ad * gmf - 1.0).abs() < 1e-12);
}
}
#[test]
fn long_left_half_scaled_by_alev0() {
use crate::gain_control_data::{GainAdjust, GainWindow};
let band = GainBand {
windows: vec![GainWindow {
adjustments: vec![GainAdjust {
alevcode: 7,
aloccode: 0,
}],
}],
};
let pfmd = vec![1.0f64; 256];
let g = BandGainFunction::reconstruct(&band, WindowSequence::OnlyLong, &pfmd);
for &ad in &g.ad[0][..256] {
assert!((ad - 0.125).abs() < 1e-12, "AD={ad}");
}
}
}