Skip to main content

qualia_core_db/audio/
dsp_kernel.rs

1//! Parametric DSP kernel — epistemic τ / FM modulation for U3 AcousticPlane.
2//!
3//! Hot path: stack `ParametricVoiceState`; no heap. Worklet mirrors this logic in JS.
4
5use crate::audio::audio_spectral_sheet::SPECTRAL_PREVIEW_BINS;
6
7/// Single-voice state for parametric sonification (stack-allocated).
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct ParametricVoiceState {
10    pub phase: f32,
11    pub frequency_hz: f32,
12    pub gain: f32,
13    pub fm_index: f32,
14}
15
16impl Default for ParametricVoiceState {
17    fn default() -> Self {
18        Self {
19            phase: 0.0,
20            frequency_hz: 440.0,
21            gain: 0.0,
22            fm_index: 0.0,
23        }
24    }
25}
26
27/// Epistemic temperature τ from superposition index `q` (B4.1 synergy).
28#[inline]
29pub fn epistemic_temperature_from_q(q: f32) -> f32 {
30    (q * q).clamp(0.0, 4.0)
31}
32
33/// FM index from epistemic `q` and phase carrier `μ`.
34#[inline]
35pub fn epistemic_fm_index(q: f32, mu: f32) -> f32 {
36    let tau = epistemic_temperature_from_q(q);
37    (tau * 0.25 + mu.abs() * 0.5).clamp(0.0, 8.0)
38}
39
40/// Map σ preview bin energy to fundamental frequency (Hz).
41#[inline]
42pub fn sigma_dominant_frequency(bins: &[f32; SPECTRAL_PREVIEW_BINS], base_hz: f32) -> f32 {
43    let mut peak = 0usize;
44    let mut max_e = 0.0_f32;
45    for (i, &e) in bins.iter().enumerate() {
46        let a = e.abs();
47        if a > max_e {
48            max_e = a;
49            peak = i;
50        }
51    }
52    let ratio = (peak as f32 + 1.0) / SPECTRAL_PREVIEW_BINS as f32;
53    (base_hz * (0.5 + ratio * 3.0)).clamp(55.0, 8_000.0)
54}
55
56/// Advance voice one sample at `sample_rate` (default 48 kHz).
57#[inline]
58pub fn parametric_sample(state: &mut ParametricVoiceState, sample_rate: f32) -> f32 {
59    let dt = 1.0 / sample_rate.max(1.0);
60    let mod_phase = state.phase * (1.0 + state.fm_index * 0.01);
61    let sample = (mod_phase * std::f32::consts::TAU).sin() * state.gain;
62    state.phase += state.frequency_hz * dt;
63    if state.phase > 1.0 {
64        state.phase -= state.phase.floor();
65    }
66    sample
67}
68
69/// Configure voice from tensor channels and preview bins.
70#[inline]
71pub fn configure_voice_from_tensor(
72    state: &mut ParametricVoiceState,
73    q: f32,
74    mu: f32,
75    alpha: f32,
76    bins: &[f32; SPECTRAL_PREVIEW_BINS],
77) {
78    state.frequency_hz = sigma_dominant_frequency(bins, 220.0);
79    state.gain = alpha.clamp(0.0, 1.0) * 0.35;
80    state.fm_index = epistemic_fm_index(q, mu);
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn epistemic_temperature_monotonic() {
89        assert!(epistemic_temperature_from_q(1.0) > epistemic_temperature_from_q(0.5));
90    }
91
92    #[test]
93    fn parametric_sample_bounded() {
94        let mut v = ParametricVoiceState {
95            gain: 1.0,
96            frequency_hz: 440.0,
97            ..Default::default()
98        };
99        for _ in 0..512 {
100            let s = parametric_sample(&mut v, 48_000.0);
101            assert!(s >= -1.0 && s <= 1.0);
102        }
103    }
104
105    #[test]
106    fn sigma_frequency_in_audible_range() {
107        let bins = [0.0_f32; SPECTRAL_PREVIEW_BINS];
108        let mut hot = bins;
109        hot[32] = 1.0;
110        let hz = sigma_dominant_frequency(&hot, 220.0);
111        assert!(hz >= 55.0 && hz <= 8_000.0);
112    }
113}