Skip to main content

nam_rs/models/linear/
process.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 Fábio Henrique de Lima Silva (fhl.bsb@gmail.com) All rights reserved.
3
4//! Hot-path audio processing methods for the Linear model.
5//!
6//! Separated from the model definition to keep the core struct and
7//! constructors in `linear.rs` while isolating the RT-critical
8//! process/prewarm/reset logic.
9
10use super::LinearMode;
11
12impl super::LinearModel {
13    /// Processes a single audio sample using the Linear model.
14    ///
15    /// 1. Writes the sample into the ring buffer (`history`).
16    /// 2. Advances the write pointer in the mirrored area.
17    /// 3. Dispatches according to the active `mode`:
18    ///    - **Direct**: dot product over the full receptive field + bias.
19    ///    - **FFT**: dot product over the head (`P` taps) + bias + tail sample
20    ///      from the pre-computed `tail_output_buf`. Every `P` samples, a new
21    ///      tail block is computed via `LinearFftState::process_tail_block`.
22    ///
23    /// # Safety
24    /// `self.weights` must be 64-byte aligned (guaranteed by `AlignedVec`).
25    #[inline(always)]
26    pub(crate) unsafe fn process_sample(&mut self, input: f32) -> f32 {
27        self.history[self.write_pos] = input;
28
29        self.write_pos += 1;
30        if self.write_pos >= self.double_limit {
31            self.write_pos -= self.history.size();
32        }
33
34        match &mut self.mode {
35            LinearMode::Direct => {
36                let start = self.write_pos - self.receptive_field;
37                let window = &self.history[start..self.write_pos];
38                let dot = unsafe {
39                    crate::math::dsp::stereo::convolve_mono(
40                        self.weights.as_ptr(),
41                        window.as_ptr(),
42                        self.receptive_field,
43                    )
44                };
45                self.bias + dot
46            }
47            LinearMode::Fft(state) => {
48                let p = state.p;
49
50                let head_weights_ptr =
51                    unsafe { self.weights.as_ptr().add(self.receptive_field - p) };
52                let head_start = self.write_pos - p;
53                let head_window = &self.history[head_start..self.write_pos];
54                let head_dot = unsafe {
55                    crate::math::dsp::stereo::convolve_mono(
56                        head_weights_ptr,
57                        head_window.as_ptr(),
58                        p,
59                    )
60                };
61
62                let y_tail = state.tail_output_buf[state.sample_counter];
63                state.sample_counter += 1;
64
65                if state.sample_counter >= p {
66                    let block_start = self.write_pos - 2 * p;
67                    let block_window = &self.history[block_start..self.write_pos];
68                    state.process_tail_block(block_window);
69                    state.sample_counter = 0;
70                }
71
72                self.bias + head_dot + y_tail
73            }
74        }
75    }
76
77    /// Processes a block of audio samples.
78    ///
79    /// # Safety
80    /// `self.weights` must be 64-byte aligned.
81    #[inline(always)]
82    pub unsafe fn process(&mut self, input: &[f32], output: &mut [f32]) {
83        let n = core::cmp::min(input.len(), output.len());
84        for i in 0..n {
85            unsafe {
86                output[i] = self.process_sample(input[i]);
87            }
88        }
89    }
90
91    /// Fills the history buffer with zeros, resets the write pointer, and
92    /// reinitializes the FFT state (if active).
93    #[cold]
94    pub fn prewarm(&mut self, _num_samples: usize) {
95        let size = self.history.size();
96        for i in 0..(size * 2) {
97            self.history[i] = 0.0;
98        }
99        self.write_pos = size;
100        if let LinearMode::Fft(ref mut state) = self.mode {
101            state.reset();
102        }
103    }
104
105    /// Resets internal state: zeroes the history buffer, write pointer,
106    /// and FFT state (if active).
107    #[cold]
108    pub fn reset(&mut self, _sample_rate: u32, _max_buffer_size: usize) {
109        let size = self.history.size();
110        for i in 0..(size * 2) {
111            self.history[i] = 0.0;
112        }
113        self.write_pos = size;
114        if let LinearMode::Fft(ref mut state) = self.mode {
115            state.reset();
116        }
117    }
118}