use crate::EvaluationError;
use scirs2_core::Complex;
use scirs2_fft::{RealFftPlanner, RealToComplex};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::f32::consts::PI;
use std::sync::Mutex;
use voirs_sdk::AudioBuffer;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpectralAnalysisConfig {
pub num_gammatone_channels: usize,
pub min_frequency: f32,
pub max_frequency: f32,
pub num_plp_coefficients: usize,
pub plp_window_length: usize,
pub plp_frame_shift: usize,
pub enable_ci_simulation: bool,
pub ci_num_electrodes: usize,
pub ci_strategy: CochlearImplantStrategy,
pub enable_hearing_aid: bool,
pub hearing_aid_type: HearingAidType,
pub hearing_loss_profile: Vec<f32>,
}
impl Default for SpectralAnalysisConfig {
fn default() -> Self {
Self {
num_gammatone_channels: 64,
min_frequency: 80.0,
max_frequency: 8000.0,
num_plp_coefficients: 13,
plp_window_length: 2048,
plp_frame_shift: 512,
enable_ci_simulation: false,
ci_num_electrodes: 22,
ci_strategy: CochlearImplantStrategy::ACE,
enable_hearing_aid: false,
hearing_aid_type: HearingAidType::LinearAmplification,
hearing_loss_profile: vec![0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0],
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum CochlearImplantStrategy {
ACE,
CIS,
FSP,
HDCIS,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum HearingAidType {
LinearAmplification,
WDRC,
MultiChannelCompression,
NoiseReductionAmplification,
DirectionalProcessing,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdvancedSpectralAnalysis {
pub gammatone_responses: Vec<GammatoneChannelResponse>,
pub plp_features: Vec<Vec<f32>>,
pub auditory_scene: AuditorySceneAnalysis,
pub cochlear_implant: Option<CochlearImplantAnalysis>,
pub hearing_aid: Option<HearingAidAnalysis>,
pub spectral_complexity: SpectralComplexityMetrics,
pub temporal_envelope: TemporalEnvelopeAnalysis,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GammatoneChannelResponse {
pub center_frequency: f32,
pub bandwidth: f32,
pub envelope: Vec<f32>,
pub instantaneous_frequency: Vec<f32>,
pub energy: f32,
pub peak_time: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditorySceneAnalysis {
pub num_sources: usize,
pub separation_confidence: f32,
pub spectral_coherence: Vec<f32>,
pub temporal_coherence: f32,
pub harmonicity: Vec<f32>,
pub common_onsets: Vec<f32>,
pub fm_coherence: Vec<f32>,
pub am_coherence: Vec<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CochlearImplantAnalysis {
pub electrode_patterns: Vec<Vec<f32>>,
pub channel_selection: Vec<usize>,
pub stimulation_levels: Vec<f32>,
pub fine_structure_preservation: f32,
pub spectral_resolution: f32,
pub dynamic_range_usage: f32,
pub channel_interactions: Vec<Vec<f32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HearingAidAnalysis {
pub frequency_gains: Vec<f32>,
pub compression_ratios: Vec<f32>,
pub noise_reduction_db: f32,
pub intelligibility_improvement: f32,
pub loudness_comfort: f32,
pub audibility_index: f32,
pub distortion_metrics: HearingAidDistortion,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HearingAidDistortion {
pub thd_percent: f32,
pub intermod_distortion: f32,
pub phase_distortion: f32,
pub frequency_deviation: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpectralComplexityMetrics {
pub spectral_entropy: f32,
pub spectral_flatness: f32,
pub spectral_irregularity: f32,
pub spectral_rolloff: f32,
pub spectral_contrast: Vec<f32>,
pub mfcc: Vec<f32>,
pub chroma: Vec<f32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalEnvelopeAnalysis {
pub modulation_spectrum: Vec<f32>,
pub am_depth: f32,
pub fm_depth: f32,
pub attack_time: f32,
pub decay_time: f32,
pub periodicity: f32,
pub modulation_peaks: Vec<f32>,
}
pub struct SpectralAnalyzer {
config: SpectralAnalysisConfig,
gammatone_bank: GammatoneFilterbank,
plp_analyzer: PLPAnalyzer,
fft_planner: Mutex<RealFftPlanner<f32>>,
}
struct GammatoneFilterbank {
filters: Vec<GammatoneFilter>,
sample_rate: f32,
}
struct GammatoneFilter {
center_freq: f32,
bandwidth: f32,
coefficients: [f32; 8], state: [f32; 8], }
struct PLPAnalyzer {
num_coefficients: usize,
window_length: usize,
frame_shift: usize,
mel_filters: Vec<Vec<f32>>,
autocorr_coeffs: Vec<f32>,
}
impl SpectralAnalyzer {
pub fn new() -> Self {
Self::with_config(SpectralAnalysisConfig::default())
}
pub fn with_config(config: SpectralAnalysisConfig) -> Self {
let gammatone_bank = GammatoneFilterbank::new(
config.num_gammatone_channels,
config.min_frequency,
config.max_frequency,
16000.0, );
let plp_analyzer = PLPAnalyzer::new(
config.num_plp_coefficients,
config.plp_window_length,
config.plp_frame_shift,
);
let fft_planner = Mutex::new(RealFftPlanner::<f32>::new());
Self {
config,
gammatone_bank,
plp_analyzer,
fft_planner,
}
}
pub fn analyze_advanced_spectral(
&self,
audio: &AudioBuffer,
) -> Result<AdvancedSpectralAnalysis, EvaluationError> {
let samples = audio.samples();
let mut gammatone_bank = self.gammatone_bank.clone();
gammatone_bank.set_sample_rate(audio.sample_rate() as f32);
let gammatone_responses = gammatone_bank.analyze(samples)?;
let plp_features = self
.plp_analyzer
.extract_features(samples, audio.sample_rate() as f32)?;
let auditory_scene = self.analyze_auditory_scene(samples, &gammatone_responses)?;
let cochlear_implant = if self.config.enable_ci_simulation {
Some(self.simulate_cochlear_implant(&gammatone_responses)?)
} else {
None
};
let hearing_aid = if self.config.enable_hearing_aid {
Some(self.simulate_hearing_aid(samples, audio.sample_rate() as f32)?)
} else {
None
};
let spectral_complexity =
self.analyze_spectral_complexity(samples, audio.sample_rate() as f32)?;
let temporal_envelope = self.analyze_temporal_envelope(samples, &gammatone_responses)?;
Ok(AdvancedSpectralAnalysis {
gammatone_responses,
plp_features,
auditory_scene,
cochlear_implant,
hearing_aid,
spectral_complexity,
temporal_envelope,
})
}
fn analyze_auditory_scene(
&self,
samples: &[f32],
gammatone_responses: &[GammatoneChannelResponse],
) -> Result<AuditorySceneAnalysis, EvaluationError> {
let num_channels = gammatone_responses.len();
let mut spectral_coherence = vec![0.0; num_channels - 1];
for i in 0..num_channels - 1 {
let coherence = self.calculate_coherence(
&gammatone_responses[i].envelope,
&gammatone_responses[i + 1].envelope,
)?;
spectral_coherence[i] = coherence;
}
let temporal_coherence = self.calculate_temporal_coherence(gammatone_responses)?;
let harmonicity = self.analyze_harmonicity(gammatone_responses)?;
let common_onsets = self.detect_common_onsets(gammatone_responses)?;
let fm_coherence = self.analyze_fm_coherence(gammatone_responses)?;
let am_coherence = self.analyze_am_coherence(gammatone_responses)?;
let num_sources = self.estimate_source_count(&spectral_coherence, temporal_coherence)?;
let separation_confidence =
self.calculate_separation_confidence(&spectral_coherence, &harmonicity)?;
Ok(AuditorySceneAnalysis {
num_sources,
separation_confidence,
spectral_coherence,
temporal_coherence,
harmonicity,
common_onsets,
fm_coherence,
am_coherence,
})
}
fn simulate_cochlear_implant(
&self,
gammatone_responses: &[GammatoneChannelResponse],
) -> Result<CochlearImplantAnalysis, EvaluationError> {
let num_electrodes = self.config.ci_num_electrodes;
let num_channels = gammatone_responses.len();
let channel_selection = self.select_ci_channels(gammatone_responses)?;
let electrode_patterns =
self.map_channels_to_electrodes(gammatone_responses, &channel_selection)?;
let stimulation_levels = self.calculate_stimulation_levels(&electrode_patterns)?;
let fine_structure_preservation =
self.assess_fine_structure_preservation(gammatone_responses)?;
let spectral_resolution =
self.estimate_ci_spectral_resolution(num_electrodes, num_channels);
let dynamic_range_usage = self.calculate_dynamic_range_usage(&stimulation_levels)?;
let channel_interactions = self.model_channel_interactions(num_electrodes)?;
Ok(CochlearImplantAnalysis {
electrode_patterns,
channel_selection,
stimulation_levels,
fine_structure_preservation,
spectral_resolution,
dynamic_range_usage,
channel_interactions,
})
}
fn simulate_hearing_aid(
&self,
samples: &[f32],
sample_rate: f32,
) -> Result<HearingAidAnalysis, EvaluationError> {
let frequency_gains = self.calculate_hearing_aid_gains()?;
let compression_ratios = self.calculate_compression_ratios()?;
let noise_reduction_db = self.assess_noise_reduction(samples, sample_rate)?;
let intelligibility_improvement = self.assess_intelligibility_improvement(samples)?;
let loudness_comfort = self.assess_loudness_comfort(samples)?;
let audibility_index = self.calculate_audibility_index(samples, &frequency_gains)?;
let distortion_metrics = self.analyze_hearing_aid_distortion(samples, sample_rate)?;
Ok(HearingAidAnalysis {
frequency_gains,
compression_ratios,
noise_reduction_db,
intelligibility_improvement,
loudness_comfort,
audibility_index,
distortion_metrics,
})
}
fn analyze_spectral_complexity(
&self,
samples: &[f32],
sample_rate: f32,
) -> Result<SpectralComplexityMetrics, EvaluationError> {
let spectrum = self.compute_fft(samples)?;
let spectral_entropy = self.calculate_spectral_entropy(&spectrum)?;
let spectral_flatness = self.calculate_spectral_flatness(&spectrum)?;
let spectral_irregularity = self.calculate_spectral_irregularity(&spectrum)?;
let spectral_rolloff = self.calculate_spectral_rolloff(&spectrum, sample_rate)?;
let spectral_contrast = self.calculate_spectral_contrast(&spectrum)?;
let mfcc = self.calculate_mfcc(samples, sample_rate)?;
let chroma = self.calculate_chroma(&spectrum, sample_rate)?;
Ok(SpectralComplexityMetrics {
spectral_entropy,
spectral_flatness,
spectral_irregularity,
spectral_rolloff,
spectral_contrast,
mfcc,
chroma,
})
}
fn analyze_temporal_envelope(
&self,
samples: &[f32],
gammatone_responses: &[GammatoneChannelResponse],
) -> Result<TemporalEnvelopeAnalysis, EvaluationError> {
let envelope = self.extract_envelope(samples)?;
let modulation_spectrum = self.compute_modulation_spectrum(&envelope)?;
let am_depth = self.calculate_am_depth(&envelope)?;
let fm_depth = self.calculate_fm_depth(samples)?;
let attack_time = self.calculate_attack_time(&envelope)?;
let decay_time = self.calculate_decay_time(&envelope)?;
let periodicity = self.calculate_envelope_periodicity(&envelope)?;
let modulation_peaks = self.find_modulation_peaks(&modulation_spectrum)?;
Ok(TemporalEnvelopeAnalysis {
modulation_spectrum,
am_depth,
fm_depth,
attack_time,
decay_time,
periodicity,
modulation_peaks,
})
}
fn calculate_coherence(
&self,
signal1: &[f32],
signal2: &[f32],
) -> Result<f32, EvaluationError> {
if signal1.len() != signal2.len() {
return Err(EvaluationError::InvalidInput {
message: "Signals must have same length for coherence calculation".to_string(),
});
}
let cross_power = signal1
.iter()
.zip(signal2.iter())
.map(|(&a, &b)| a * b)
.sum::<f32>();
let power1 = signal1.iter().map(|&x| x * x).sum::<f32>();
let power2 = signal2.iter().map(|&x| x * x).sum::<f32>();
if power1 > 0.0 && power2 > 0.0 {
Ok(cross_power / (power1 * power2).sqrt())
} else {
Ok(0.0)
}
}
fn calculate_temporal_coherence(
&self,
responses: &[GammatoneChannelResponse],
) -> Result<f32, EvaluationError> {
if responses.len() < 2 {
return Ok(0.0);
}
let mut coherence_sum = 0.0;
let mut count = 0;
for i in 0..responses.len() - 1 {
for j in i + 1..responses.len() {
let coherence =
self.calculate_coherence(&responses[i].envelope, &responses[j].envelope)?;
coherence_sum += coherence;
count += 1;
}
}
Ok(if count > 0 {
coherence_sum / count as f32
} else {
0.0
})
}
fn analyze_harmonicity(
&self,
responses: &[GammatoneChannelResponse],
) -> Result<Vec<f32>, EvaluationError> {
let mut harmonicity = Vec::with_capacity(responses.len());
for response in responses {
let energy_ratio = if response.energy > 0.0 {
let peak_energy = response.envelope.iter().fold(0.0f32, |a, &b| a.max(b));
peak_energy / response.energy
} else {
0.0
};
harmonicity.push(energy_ratio.min(1.0));
}
Ok(harmonicity)
}
fn detect_common_onsets(
&self,
responses: &[GammatoneChannelResponse],
) -> Result<Vec<f32>, EvaluationError> {
let frame_length = if !responses.is_empty() && !responses[0].envelope.is_empty() {
responses[0].envelope.len()
} else {
return Ok(Vec::new());
};
let mut onsets = Vec::with_capacity(frame_length);
for frame in 0..frame_length {
let mut onset_strength = 0.0;
for response in responses {
if frame < response.envelope.len() && frame > 0 {
let current = response.envelope[frame];
let previous = response.envelope[frame - 1];
let diff = (current - previous).max(0.0);
onset_strength += diff;
}
}
onsets.push(onset_strength);
}
Ok(onsets)
}
fn analyze_fm_coherence(
&self,
responses: &[GammatoneChannelResponse],
) -> Result<Vec<f32>, EvaluationError> {
let mut fm_coherence = Vec::with_capacity(responses.len());
for response in responses {
if response.instantaneous_frequency.len() > 1 {
let mut freq_variance = 0.0;
let mean_freq = response.instantaneous_frequency.iter().sum::<f32>()
/ response.instantaneous_frequency.len() as f32;
for &freq in &response.instantaneous_frequency {
freq_variance += (freq - mean_freq).powi(2);
}
freq_variance /= response.instantaneous_frequency.len() as f32;
let coherence = 1.0 / (1.0 + freq_variance);
fm_coherence.push(coherence);
} else {
fm_coherence.push(0.0);
}
}
Ok(fm_coherence)
}
fn analyze_am_coherence(
&self,
responses: &[GammatoneChannelResponse],
) -> Result<Vec<f32>, EvaluationError> {
let mut am_coherence = Vec::with_capacity(responses.len());
for response in responses {
if response.envelope.len() > 1 {
let mut envelope_variance = 0.0;
let mean_envelope =
response.envelope.iter().sum::<f32>() / response.envelope.len() as f32;
for & in &response.envelope {
envelope_variance += (amp - mean_envelope).powi(2);
}
envelope_variance /= response.envelope.len() as f32;
let coherence = envelope_variance / (mean_envelope * mean_envelope + 1e-10);
am_coherence.push(coherence.min(1.0));
} else {
am_coherence.push(0.0);
}
}
Ok(am_coherence)
}
fn estimate_source_count(
&self,
coherence: &[f32],
temporal_coherence: f32,
) -> Result<usize, EvaluationError> {
let avg_coherence = if !coherence.is_empty() {
coherence.iter().sum::<f32>() / coherence.len() as f32
} else {
0.0
};
let estimated_sources = if avg_coherence < 0.3 {
3 + (temporal_coherence * 2.0) as usize
} else if avg_coherence < 0.6 {
2
} else {
1
};
Ok(estimated_sources.min(5)) }
fn calculate_separation_confidence(
&self,
coherence: &[f32],
harmonicity: &[f32],
) -> Result<f32, EvaluationError> {
let avg_coherence = if !coherence.is_empty() {
coherence.iter().sum::<f32>() / coherence.len() as f32
} else {
0.0
};
let avg_harmonicity = if !harmonicity.is_empty() {
harmonicity.iter().sum::<f32>() / harmonicity.len() as f32
} else {
0.0
};
let confidence = (1.0 - avg_coherence) * 0.7 + avg_harmonicity * 0.3;
Ok(confidence.min(1.0).max(0.0))
}
fn select_ci_channels(
&self,
responses: &[GammatoneChannelResponse],
) -> Result<Vec<usize>, EvaluationError> {
let mut channel_energies: Vec<(usize, f32)> = responses
.iter()
.enumerate()
.map(|(i, response)| (i, response.energy))
.collect();
channel_energies.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let selected = channel_energies
.into_iter()
.take(self.config.ci_num_electrodes.min(responses.len()))
.map(|(idx, _)| idx)
.collect();
Ok(selected)
}
fn map_channels_to_electrodes(
&self,
responses: &[GammatoneChannelResponse],
selection: &[usize],
) -> Result<Vec<Vec<f32>>, EvaluationError> {
let mut patterns = Vec::with_capacity(self.config.ci_num_electrodes);
for &channel_idx in selection {
if channel_idx < responses.len() {
patterns.push(responses[channel_idx].envelope.clone());
}
}
Ok(patterns)
}
fn calculate_stimulation_levels(
&self,
patterns: &[Vec<f32>],
) -> Result<Vec<f32>, EvaluationError> {
let mut levels = Vec::with_capacity(patterns.len());
for pattern in patterns {
let max_level = pattern.iter().fold(0.0f32, |a, &b| a.max(b));
levels.push(max_level);
}
Ok(levels)
}
fn compute_fft(&self, samples: &[f32]) -> Result<Vec<f32>, EvaluationError> {
let n = samples.len();
if n == 0 {
return Ok(Vec::new());
}
let mut planner = self
.fft_planner
.lock()
.expect("lock should not be poisoned");
let fft = planner.plan_fft_forward(n);
let mut input_buffer = samples.to_vec();
let mut output_buffer = vec![Complex::new(0.0, 0.0); n / 2 + 1];
fft.process(&input_buffer, &mut output_buffer);
let magnitude_spectrum: Vec<f32> = output_buffer.iter().map(|c| c.norm()).collect();
Ok(magnitude_spectrum)
}
fn calculate_spectral_entropy(&self, spectrum: &[f32]) -> Result<f32, EvaluationError> {
let total_energy: f32 = spectrum.iter().sum();
if total_energy <= 0.0 {
return Ok(0.0);
}
let mut entropy = 0.0;
for &power in spectrum {
if power > 0.0 {
let probability = power / total_energy;
entropy -= probability * probability.log2();
}
}
Ok(entropy)
}
fn calculate_spectral_flatness(&self, spectrum: &[f32]) -> Result<f32, EvaluationError> {
let positive_values: Vec<f32> = spectrum.iter().filter(|&&x| x > 0.0).copied().collect();
if positive_values.is_empty() {
return Ok(0.0);
}
let geometric_mean =
positive_values.iter().map(|&x| x.ln()).sum::<f32>() / positive_values.len() as f32;
let arithmetic_mean = positive_values.iter().sum::<f32>() / positive_values.len() as f32;
if arithmetic_mean > 0.0 {
let flatness = geometric_mean.exp() / arithmetic_mean;
Ok(flatness.min(1.0).max(0.0))
} else {
Ok(0.0)
}
}
fn apply_pre_emphasis(&self, samples: &[f32]) -> Result<Vec<f32>, EvaluationError> {
if samples.is_empty() {
return Ok(Vec::new());
}
const PRE_EMPHASIS_COEFF: f32 = 0.97;
let mut pre_emphasized = Vec::with_capacity(samples.len());
pre_emphasized.push(samples[0]);
for i in 1..samples.len() {
pre_emphasized.push(samples[i] - PRE_EMPHASIS_COEFF * samples[i - 1]);
}
Ok(pre_emphasized)
}
fn apply_hamming_window(&self, samples: &[f32]) -> Result<Vec<f32>, EvaluationError> {
let n = samples.len();
if n == 0 {
return Ok(Vec::new());
}
let windowed: Vec<f32> = samples
.iter()
.enumerate()
.map(|(i, &sample)| {
let window_val = 0.54 - 0.46 * (2.0 * PI * i as f32 / (n - 1) as f32).cos();
sample * window_val
})
.collect();
Ok(windowed)
}
fn apply_mel_filterbank(
&self,
power_spectrum: &[f32],
sample_rate: f32,
) -> Result<Vec<f32>, EvaluationError> {
const NUM_MEL_FILTERS: usize = 26;
const MIN_MEL_FREQ: f32 = 0.0;
let max_mel_freq = Self::hz_to_mel(sample_rate / 2.0);
let mel_filters = self.create_mel_filters(
NUM_MEL_FILTERS,
MIN_MEL_FREQ,
max_mel_freq,
power_spectrum.len(),
sample_rate,
)?;
let mut mel_energies = Vec::with_capacity(NUM_MEL_FILTERS);
for filter in &mel_filters {
let energy: f32 = filter
.iter()
.zip(power_spectrum.iter())
.map(|(f, p)| f * p)
.sum();
mel_energies.push(energy);
}
Ok(mel_energies)
}
fn create_mel_filters(
&self,
num_filters: usize,
min_mel: f32,
max_mel: f32,
spectrum_length: usize,
sample_rate: f32,
) -> Result<Vec<Vec<f32>>, EvaluationError> {
let mut filters = Vec::with_capacity(num_filters);
let mel_points: Vec<f32> = (0..=num_filters + 1)
.map(|i| min_mel + (max_mel - min_mel) * i as f32 / (num_filters + 1) as f32)
.collect();
let hz_points: Vec<f32> = mel_points.iter().map(|&mel| Self::mel_to_hz(mel)).collect();
let bin_points: Vec<usize> = hz_points
.iter()
.map(|&hz| ((hz * 2.0 * spectrum_length as f32) / sample_rate).round() as usize)
.collect();
for i in 0..num_filters {
let mut filter = vec![0.0; spectrum_length];
let start_bin = bin_points[i];
let center_bin = bin_points[i + 1];
let end_bin = bin_points[i + 2];
for bin in start_bin..=center_bin {
if bin < spectrum_length && start_bin != center_bin {
filter[bin] = (bin - start_bin) as f32 / (center_bin - start_bin) as f32;
}
}
for bin in center_bin..=end_bin {
if bin < spectrum_length && center_bin != end_bin {
filter[bin] = (end_bin - bin) as f32 / (end_bin - center_bin) as f32;
}
}
filters.push(filter);
}
Ok(filters)
}
fn hz_to_mel(hz: f32) -> f32 {
2595.0 * (1.0 + hz / 700.0).log10()
}
fn mel_to_hz(mel: f32) -> f32 {
700.0 * (10.0_f32.powf(mel / 2595.0) - 1.0)
}
fn apply_dct(
&self,
log_mel_energies: &[f32],
num_coeffs: usize,
) -> Result<Vec<f32>, EvaluationError> {
let n = log_mel_energies.len();
let mut mfcc = Vec::with_capacity(num_coeffs);
for k in 0..num_coeffs {
let mut sum = 0.0;
for i in 0..n {
sum += log_mel_energies[i] * (PI * k as f32 * (i as f32 + 0.5) / n as f32).cos();
}
let normalization = if k == 0 {
(1.0 / n as f32).sqrt()
} else {
(2.0 / n as f32).sqrt()
};
mfcc.push(sum * normalization);
}
Ok(mfcc)
}
fn calculate_spectral_irregularity(&self, _spectrum: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.5)
}
fn calculate_spectral_rolloff(
&self,
_spectrum: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(4000.0)
}
fn calculate_spectral_contrast(&self, _spectrum: &[f32]) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![0.5; 7])
}
fn calculate_mfcc(
&self,
samples: &[f32],
sample_rate: f32,
) -> Result<Vec<f32>, EvaluationError> {
const NUM_MFCC_COEFFS: usize = 13;
const NUM_MEL_FILTERS: usize = 26;
const FRAME_SIZE: usize = 2048;
if samples.len() < FRAME_SIZE {
return Ok(vec![0.0; NUM_MFCC_COEFFS]);
}
let frame = &samples[..FRAME_SIZE];
let pre_emphasized = self.apply_pre_emphasis(frame)?;
let windowed = self.apply_hamming_window(&pre_emphasized)?;
let spectrum = self.compute_fft(&windowed)?;
let power_spectrum: Vec<f32> = spectrum.iter().map(|x| x * x).collect();
let mel_energies = self.apply_mel_filterbank(&power_spectrum, sample_rate)?;
let log_mel_energies: Vec<f32> = mel_energies
.iter()
.map(|&energy| (energy.max(1e-10)).ln())
.collect();
let mfcc = self.apply_dct(&log_mel_energies, NUM_MFCC_COEFFS)?;
Ok(mfcc)
}
fn calculate_chroma(
&self,
spectrum: &[f32],
sample_rate: f32,
) -> Result<Vec<f32>, EvaluationError> {
const NUM_CHROMA_BINS: usize = 12;
const MIN_FREQ: f32 = 80.0; const MAX_FREQ: f32 = 8000.0;
if spectrum.is_empty() {
return Ok(vec![0.0; NUM_CHROMA_BINS]);
}
let nyquist_freq = sample_rate / 2.0;
let mut chroma = vec![0.0; NUM_CHROMA_BINS];
for (bin_idx, &magnitude) in spectrum.iter().enumerate() {
let freq = bin_idx as f32 * nyquist_freq / spectrum.len() as f32;
if freq < MIN_FREQ || freq > MAX_FREQ {
continue;
}
let pitch_class = self.freq_to_pitch_class(freq);
chroma[pitch_class] += magnitude;
}
let total_energy: f32 = chroma.iter().sum();
if total_energy > 0.0 {
for chroma_val in &mut chroma {
*chroma_val /= total_energy;
}
}
Ok(chroma)
}
fn freq_to_pitch_class(&self, freq: f32) -> usize {
let midi_note = 69.0 + 12.0 * (freq / 440.0).log2();
let pitch_class = (midi_note.round() as i32) % 12;
if pitch_class < 0 {
(pitch_class + 12) as usize
} else {
pitch_class as usize
}
}
fn extract_envelope(&self, samples: &[f32]) -> Result<Vec<f32>, EvaluationError> {
Ok(samples
.windows(128)
.map(|window| window.iter().map(|x| x.abs()).sum::<f32>() / window.len() as f32)
.collect())
}
fn compute_modulation_spectrum(&self, _envelope: &[f32]) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![0.0; 64])
}
fn calculate_am_depth(&self, _envelope: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.3)
}
fn calculate_fm_depth(&self, _samples: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.2)
}
fn calculate_attack_time(&self, _envelope: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.01)
}
fn calculate_decay_time(&self, _envelope: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.1)
}
fn calculate_envelope_periodicity(&self, _envelope: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.4)
}
fn find_modulation_peaks(&self, _spectrum: &[f32]) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![4.0, 8.0, 16.0])
}
fn calculate_hearing_aid_gains(&self) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0])
}
fn calculate_compression_ratios(&self) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![2.0, 3.0, 4.0, 3.5, 3.0, 2.5, 2.0, 1.5])
}
fn assess_noise_reduction(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(6.0)
}
fn assess_intelligibility_improvement(&self, _samples: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.15)
}
fn assess_loudness_comfort(&self, _samples: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.8)
}
fn calculate_audibility_index(
&self,
_samples: &[f32],
_gains: &[f32],
) -> Result<f32, EvaluationError> {
Ok(0.7)
}
fn analyze_hearing_aid_distortion(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<HearingAidDistortion, EvaluationError> {
Ok(HearingAidDistortion {
thd_percent: 1.5,
intermod_distortion: 0.8,
phase_distortion: 2.0,
frequency_deviation: 1.2,
})
}
fn assess_fine_structure_preservation(
&self,
_responses: &[GammatoneChannelResponse],
) -> Result<f32, EvaluationError> {
Ok(0.3)
}
fn estimate_ci_spectral_resolution(&self, num_electrodes: usize, num_channels: usize) -> f32 {
num_electrodes as f32 / num_channels as f32
}
fn calculate_dynamic_range_usage(&self, _levels: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.6)
}
fn model_channel_interactions(
&self,
num_electrodes: usize,
) -> Result<Vec<Vec<f32>>, EvaluationError> {
Ok((0..num_electrodes)
.map(|_| vec![0.1; num_electrodes])
.collect())
}
}
impl GammatoneFilterbank {
fn new(num_channels: usize, min_freq: f32, max_freq: f32, sample_rate: f32) -> Self {
let mut filters = Vec::with_capacity(num_channels);
for i in 0..num_channels {
let erb_scale = (min_freq / 24.7).ln()
+ i as f32 * ((max_freq / 24.7).ln() - (min_freq / 24.7).ln())
/ (num_channels - 1) as f32;
let center_freq = 24.7 * erb_scale.exp();
let bandwidth = 24.7 * (0.108 * center_freq + 4.5);
filters.push(GammatoneFilter::new(center_freq, bandwidth, sample_rate));
}
Self {
filters,
sample_rate,
}
}
fn set_sample_rate(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
for filter in &mut self.filters {
filter.update_sample_rate(sample_rate);
}
}
fn analyze(
&mut self,
samples: &[f32],
) -> Result<Vec<GammatoneChannelResponse>, EvaluationError> {
let mut responses = Vec::with_capacity(self.filters.len());
for filter in &mut self.filters {
let response = filter.process(samples)?;
responses.push(response);
}
Ok(responses)
}
}
impl Clone for GammatoneFilterbank {
fn clone(&self) -> Self {
Self {
filters: self.filters.clone(),
sample_rate: self.sample_rate,
}
}
}
impl GammatoneFilter {
fn new(center_freq: f32, bandwidth: f32, sample_rate: f32) -> Self {
let mut filter = Self {
center_freq,
bandwidth,
coefficients: [0.0; 8],
state: [0.0; 8],
};
filter.calculate_coefficients(sample_rate);
filter
}
fn calculate_coefficients(&mut self, sample_rate: f32) {
let dt = 1.0 / sample_rate;
let omega = 2.0 * PI * self.center_freq * dt;
let alpha = self.bandwidth * dt;
let cos_omega = omega.cos();
let sin_omega = omega.sin();
let exp_alpha = (-alpha).exp();
self.coefficients[0] = exp_alpha * cos_omega;
self.coefficients[1] = exp_alpha * sin_omega;
self.coefficients[2] = exp_alpha;
self.coefficients[3] = alpha;
for i in 4..8 {
self.coefficients[i] = self.coefficients[i - 4] * 0.5;
}
}
fn update_sample_rate(&mut self, sample_rate: f32) {
self.calculate_coefficients(sample_rate);
self.state = [0.0; 8]; }
fn process(&mut self, samples: &[f32]) -> Result<GammatoneChannelResponse, EvaluationError> {
let mut envelope = Vec::with_capacity(samples.len());
let mut instantaneous_frequency = Vec::with_capacity(samples.len());
let mut total_energy = 0.0;
let mut peak_time = 0.0;
let mut peak_value = 0.0;
for (i, &sample) in samples.iter().enumerate() {
let filtered = self.apply_filter(sample);
let env_val = filtered.abs();
envelope.push(env_val);
let inst_freq = if i > 0 {
let phase_diff = (filtered / envelope[i - 1]).atan2(1.0);
phase_diff / (2.0 * PI)
} else {
self.center_freq
};
instantaneous_frequency.push(inst_freq);
total_energy += env_val * env_val;
if env_val > peak_value {
peak_value = env_val;
peak_time = i as f32 / samples.len() as f32;
}
}
Ok(GammatoneChannelResponse {
center_frequency: self.center_freq,
bandwidth: self.bandwidth,
envelope,
instantaneous_frequency,
energy: total_energy,
peak_time,
})
}
fn apply_filter(&mut self, input: f32) -> f32 {
let output =
self.coefficients[0] * self.state[0] - self.coefficients[1] * self.state[1] + input;
self.state[1] = self.state[0];
self.state[0] = output;
output
}
}
impl Clone for GammatoneFilter {
fn clone(&self) -> Self {
Self {
center_freq: self.center_freq,
bandwidth: self.bandwidth,
coefficients: self.coefficients,
state: [0.0; 8], }
}
}
impl PLPAnalyzer {
fn new(num_coefficients: usize, window_length: usize, frame_shift: usize) -> Self {
let mel_filters = Self::create_mel_filterbank(num_coefficients, window_length);
Self {
num_coefficients,
window_length,
frame_shift,
mel_filters,
autocorr_coeffs: vec![0.0; num_coefficients],
}
}
fn create_mel_filterbank(num_filters: usize, window_length: usize) -> Vec<Vec<f32>> {
let mut filters = Vec::with_capacity(num_filters);
for i in 0..num_filters {
let mut filter = vec![0.0; window_length / 2];
let center = (i + 1) * window_length / (2 * (num_filters + 1));
let width = window_length / (2 * num_filters);
for j in 0..filter.len() {
let dist = (j as i32 - center as i32).abs() as f32;
if dist < width as f32 {
filter[j] = 1.0 - dist / width as f32;
}
}
filters.push(filter);
}
filters
}
fn extract_features(
&self,
samples: &[f32],
_sample_rate: f32,
) -> Result<Vec<Vec<f32>>, EvaluationError> {
if samples.len() < self.window_length {
return Ok(Vec::new());
}
let num_frames = (samples.len() - self.window_length) / self.frame_shift + 1;
let mut features = Vec::with_capacity(num_frames);
for frame_idx in 0..num_frames {
let start = frame_idx * self.frame_shift;
let end = (start + self.window_length).min(samples.len());
if end > start {
let frame = &samples[start..end];
let plp_coeffs = self.compute_plp_coefficients(frame)?;
features.push(plp_coeffs);
}
}
Ok(features)
}
fn compute_plp_coefficients(&self, frame: &[f32]) -> Result<Vec<f32>, EvaluationError> {
let mut coefficients = vec![0.0; self.num_coefficients];
let windowed: Vec<f32> = frame
.iter()
.enumerate()
.map(|(i, &sample)| {
let window_val =
0.54 - 0.46 * (2.0 * PI * i as f32 / (frame.len() - 1) as f32).cos();
sample * window_val
})
.collect();
for i in 0..coefficients.len() {
let mut sum = 0.0;
for j in 0..windowed.len() - i {
sum += windowed[j] * windowed[j + i];
}
coefficients[i] = sum;
}
if coefficients[0] > 0.0 {
for i in 1..coefficients.len() {
coefficients[i] /= coefficients[0];
}
}
Ok(coefficients)
}
}
impl Default for SpectralAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spectral_analyzer_creation() {
let analyzer = SpectralAnalyzer::new();
assert_eq!(analyzer.config.num_gammatone_channels, 64);
}
#[test]
fn test_advanced_spectral_analysis() {
let analyzer = SpectralAnalyzer::new();
let samples = vec![0.1; 4096]; let audio = AudioBuffer::new(samples, 16000, 1);
let result = analyzer.analyze_advanced_spectral(&audio);
assert!(result.is_ok());
let analysis = result.unwrap();
assert_eq!(analysis.gammatone_responses.len(), 64);
assert!(!analysis.plp_features.is_empty());
assert!(analysis.spectral_complexity.spectral_entropy >= 0.0);
}
#[test]
fn test_gammatone_filterbank() {
let mut filterbank = GammatoneFilterbank::new(10, 100.0, 4000.0, 16000.0);
let samples = vec![0.1; 512];
let result = filterbank.analyze(&samples);
assert!(result.is_ok());
let responses = result.unwrap();
assert_eq!(responses.len(), 10);
for response in &responses {
assert!(response.center_frequency > 0.0);
assert!(response.bandwidth > 0.0);
assert_eq!(response.envelope.len(), samples.len());
}
}
#[test]
fn test_plp_analyzer() {
let analyzer = PLPAnalyzer::new(13, 512, 128);
let samples = vec![0.1; 1024];
let result = analyzer.extract_features(&samples, 16000.0);
assert!(result.is_ok());
let features = result.unwrap();
assert!(!features.is_empty());
for feature_vec in &features {
assert_eq!(feature_vec.len(), 13);
}
}
#[test]
fn test_cochlear_implant_simulation() {
let config = SpectralAnalysisConfig {
enable_ci_simulation: true,
ci_num_electrodes: 16,
..Default::default()
};
let analyzer = SpectralAnalyzer::with_config(config);
let samples = vec![0.1; 1024];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = analyzer.analyze_advanced_spectral(&audio);
assert!(result.is_ok());
let analysis = result.unwrap();
assert!(analysis.cochlear_implant.is_some());
let ci_analysis = analysis.cochlear_implant.unwrap();
assert!(!ci_analysis.electrode_patterns.is_empty());
assert!(!ci_analysis.stimulation_levels.is_empty());
assert!(ci_analysis.spectral_resolution > 0.0);
}
#[test]
fn test_hearing_aid_simulation() {
let config = SpectralAnalysisConfig {
enable_hearing_aid: true,
hearing_aid_type: HearingAidType::WDRC,
..Default::default()
};
let analyzer = SpectralAnalyzer::with_config(config);
let samples = vec![0.1; 1024];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = analyzer.analyze_advanced_spectral(&audio);
assert!(result.is_ok());
let analysis = result.unwrap();
assert!(analysis.hearing_aid.is_some());
let ha_analysis = analysis.hearing_aid.unwrap();
assert!(!ha_analysis.frequency_gains.is_empty());
assert!(!ha_analysis.compression_ratios.is_empty());
assert!(ha_analysis.audibility_index >= 0.0);
assert!(ha_analysis.audibility_index <= 1.0);
}
#[test]
fn test_auditory_scene_analysis() {
let analyzer = SpectralAnalyzer::new();
let samples = vec![0.1; 1024];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = analyzer.analyze_advanced_spectral(&audio);
assert!(result.is_ok());
let analysis = result.unwrap();
assert!(analysis.auditory_scene.num_sources > 0);
assert!(analysis.auditory_scene.separation_confidence >= 0.0);
assert!(analysis.auditory_scene.separation_confidence <= 1.0);
assert!(!analysis.auditory_scene.spectral_coherence.is_empty());
}
#[test]
fn test_spectral_complexity_metrics() {
let analyzer = SpectralAnalyzer::new();
let samples = vec![0.1; 1024];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = analyzer.analyze_advanced_spectral(&audio);
assert!(result.is_ok());
let analysis = result.unwrap();
let complexity = &analysis.spectral_complexity;
assert!(complexity.spectral_entropy >= 0.0);
assert!(complexity.spectral_flatness >= 0.0);
assert!(complexity.spectral_flatness <= 1.0);
assert!(complexity.spectral_rolloff > 0.0);
assert_eq!(complexity.mfcc.len(), 13);
assert_eq!(complexity.chroma.len(), 12);
}
#[test]
fn test_temporal_envelope_analysis() {
let analyzer = SpectralAnalyzer::new();
let samples = vec![0.1; 1024];
let audio = AudioBuffer::new(samples, 16000, 1);
let result = analyzer.analyze_advanced_spectral(&audio);
assert!(result.is_ok());
let analysis = result.unwrap();
let temporal = &analysis.temporal_envelope;
assert!(!temporal.modulation_spectrum.is_empty());
assert!(temporal.am_depth >= 0.0);
assert!(temporal.fm_depth >= 0.0);
assert!(temporal.attack_time > 0.0);
assert!(temporal.decay_time > 0.0);
assert!(!temporal.modulation_peaks.is_empty());
}
}