From cab4217e36cc40cd6eaf9e06cee1bb76f14b34ae Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Tue, 9 Jun 2026 13:08:47 -0700 Subject: [PATCH] Move linear model to separate files --- NAM/dsp.cpp | 87 -------------------------------------------------- NAM/dsp.h | 55 ++----------------------------- NAM/linear.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ NAM/linear.h | 61 +++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 140 deletions(-) create mode 100644 NAM/linear.cpp create mode 100644 NAM/linear.h diff --git a/NAM/dsp.cpp b/NAM/dsp.cpp index e975001b..a4040c34 100644 --- a/NAM/dsp.cpp +++ b/NAM/dsp.cpp @@ -9,8 +9,6 @@ #include #include "dsp.h" -#include "registry.h" - #define tanh_impl_ std::tanh // #define tanh_impl_ fast_tanh_ @@ -250,91 +248,6 @@ void nam::Buffer::_advance_input_buffer_(const int num_frames) this->_input_buffer_offset += num_frames; } -// Linear ===================================================================== - -nam::Linear::Linear(const int in_channels, const int out_channels, const int receptive_field, const bool _bias, - const std::vector& weights, const double expected_sample_rate) -: nam::Buffer(in_channels, out_channels, receptive_field, expected_sample_rate) -{ - if ((int)weights.size() != (receptive_field + (_bias ? 1 : 0))) - throw std::runtime_error( - "Params vector does not match expected size based " - "on architecture parameters"); - - this->_weight.resize(this->_receptive_field); - // Pass in in reverse order so that dot products work out of the box. - for (int i = 0; i < this->_receptive_field; i++) - this->_weight(i) = weights[receptive_field - 1 - i]; - this->_bias = _bias ? weights[receptive_field] : (float)0.0; -} - -void nam::Linear::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) -{ - this->nam::Buffer::_update_buffers_(input, num_frames); - - const int in_channels = NumInputChannels(); - const int out_channels = NumOutputChannels(); - - // For now, Linear processes each input channel independently to corresponding output channel - // This is a simple implementation - can be extended later for cross-channel mixing - const int channelsToProcess = std::min(in_channels, out_channels); - - // Main computation! - for (int ch = 0; ch < channelsToProcess; ch++) - { - for (int i = 0; i < num_frames; i++) - { - const long offset = this->_input_buffer_offset - this->_weight.size() + i + 1; - auto input_vec = Eigen::Map(&this->_input_buffers[ch][offset], this->_receptive_field); - output[ch][i] = this->_bias + this->_weight.dot(input_vec); - } - } - - // Zero out any extra output channels - for (int ch = channelsToProcess; ch < out_channels; ch++) - { - for (int i = 0; i < num_frames; i++) - output[ch][i] = (NAM_SAMPLE)0.0; - } - - // Prepare for next call: - nam::Buffer::_advance_input_buffer_(num_frames); -} - -// Config parser -nam::linear::LinearConfig nam::linear::parse_config_json(const nlohmann::json& config) -{ - LinearConfig c; - c.receptive_field = config["receptive_field"]; - c.bias = config["bias"]; - // Default to 1 channel in/out for backward compatibility - c.in_channels = config.value("in_channels", 1); - c.out_channels = config.value("out_channels", 1); - return c; -} - -// LinearConfig::create() -std::unique_ptr nam::linear::LinearConfig::create(std::vector weights, double sampleRate) -{ - return std::make_unique(in_channels, out_channels, receptive_field, bias, weights, sampleRate); -} - -// Config parser for ConfigParserRegistry -std::unique_ptr nam::linear::create_config(const nlohmann::json& config, double sampleRate) -{ - (void)sampleRate; - auto c = std::make_unique(); - auto parsed = parse_config_json(config); - *c = parsed; - return c; -} - -// Register the config parser -namespace -{ -static nam::ConfigParserHelper _register_Linear("Linear", nam::linear::create_config); -} - // NN modules ================================================================= // Conv1x1 ==================================================================== diff --git a/NAM/dsp.h b/NAM/dsp.h index 1fadcf70..7f661347 100644 --- a/NAM/dsp.h +++ b/NAM/dsp.h @@ -240,59 +240,6 @@ class Buffer : public DSP virtual void _rewind_buffers_(); }; -/// \brief Basic linear model -/// -/// Implements a simple linear convolution, (i.e. an impulse response). -class Linear : public Buffer -{ -public: - /// \brief Constructor - /// \param in_channels Number of input channels - /// \param out_channels Number of output channels - /// \param receptive_field Size of the impulse response - /// \param _bias Whether to use bias - /// \param weights Model weights (impulse response coefficients) - /// \param expected_sample_rate Expected sample rate in Hz (-1.0 if unknown) - Linear(const int in_channels, const int out_channels, const int receptive_field, const bool _bias, - const std::vector& weights, const double expected_sample_rate = -1.0); - - /// \brief Process audio frames - /// \param input Input audio buffers - /// \param output Output audio buffers - /// \param num_frames Number of frames to process - void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override; - -protected: - Eigen::VectorXf _weight; - float _bias; -}; - -namespace linear -{ - -/// \brief Configuration for a Linear model -struct LinearConfig : public ModelConfig -{ - int receptive_field; - bool bias; - int in_channels; - int out_channels; - - std::unique_ptr create(std::vector weights, double sampleRate) override; -}; - -/// \brief Parse Linear configuration from JSON -/// \param config JSON configuration object -/// \return LinearConfig -LinearConfig parse_config_json(const nlohmann::json& config); - -/// \brief Config parser for ConfigParserRegistry -/// \param config JSON configuration object -/// \param sampleRate Expected sample rate in Hz -/// \return unique_ptr wrapping a LinearConfig -std::unique_ptr create_config(const nlohmann::json& config, double sampleRate); -} // namespace linear - // NN modules ================================================================= /// \brief 1x1 convolution (really just a fully-connected linear layer operating per-sample) @@ -396,3 +343,5 @@ void verify_config_version(const std::string version); /// \return Unique pointer to a DSP object std::unique_ptr get_dsp_legacy(const std::filesystem::path dirname); }; // namespace nam + +#include "linear.h" diff --git a/NAM/linear.cpp b/NAM/linear.cpp new file mode 100644 index 00000000..6ffe040e --- /dev/null +++ b/NAM/linear.cpp @@ -0,0 +1,85 @@ +#include "linear.h" + +#include +#include + +#include "registry.h" + +nam::Linear::Linear(const int in_channels, const int out_channels, const int receptive_field, const bool _bias, + const std::vector& weights, const double expected_sample_rate) +: nam::Buffer(in_channels, out_channels, receptive_field, expected_sample_rate) +{ + if ((int)weights.size() != (receptive_field + (_bias ? 1 : 0))) + throw std::runtime_error( + "Params vector does not match expected size based " + "on architecture parameters"); + + this->_weight.resize(this->_receptive_field); + // Pass in in reverse order so that dot products work out of the box. + for (int i = 0; i < this->_receptive_field; i++) + this->_weight(i) = weights[receptive_field - 1 - i]; + this->_bias = _bias ? weights[receptive_field] : (float)0.0; +} + +void nam::Linear::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) +{ + this->nam::Buffer::_update_buffers_(input, num_frames); + + const int in_channels = NumInputChannels(); + const int out_channels = NumOutputChannels(); + + // For now, Linear processes each input channel independently to corresponding output channel + // This is a simple implementation - can be extended later for cross-channel mixing + const int channelsToProcess = std::min(in_channels, out_channels); + + // Main computation! + for (int ch = 0; ch < channelsToProcess; ch++) + { + for (int i = 0; i < num_frames; i++) + { + const long offset = this->_input_buffer_offset - this->_weight.size() + i + 1; + auto input_vec = Eigen::Map(&this->_input_buffers[ch][offset], this->_receptive_field); + output[ch][i] = this->_bias + this->_weight.dot(input_vec); + } + } + + // Zero out any extra output channels + for (int ch = channelsToProcess; ch < out_channels; ch++) + { + for (int i = 0; i < num_frames; i++) + output[ch][i] = (NAM_SAMPLE)0.0; + } + + // Prepare for next call: + nam::Buffer::_advance_input_buffer_(num_frames); +} + +nam::linear::LinearConfig nam::linear::parse_config_json(const nlohmann::json& config) +{ + LinearConfig c; + c.receptive_field = config["receptive_field"]; + c.bias = config["bias"]; + // Default to 1 channel in/out for backward compatibility + c.in_channels = config.value("in_channels", 1); + c.out_channels = config.value("out_channels", 1); + return c; +} + +std::unique_ptr nam::linear::LinearConfig::create(std::vector weights, double sampleRate) +{ + return std::make_unique(in_channels, out_channels, receptive_field, bias, weights, sampleRate); +} + +std::unique_ptr nam::linear::create_config(const nlohmann::json& config, double sampleRate) +{ + (void)sampleRate; + auto c = std::make_unique(); + auto parsed = parse_config_json(config); + *c = parsed; + return c; +} + +namespace +{ +static nam::ConfigParserHelper _register_Linear("Linear", nam::linear::create_config); +} diff --git a/NAM/linear.h b/NAM/linear.h new file mode 100644 index 00000000..dbe94549 --- /dev/null +++ b/NAM/linear.h @@ -0,0 +1,61 @@ +#pragma once + +#include "dsp.h" + +namespace nam +{ + +/// \brief Basic linear model +/// +/// Implements a simple linear convolution, (i.e. an impulse response). +class Linear : public Buffer +{ +public: + /// \brief Constructor + /// \param in_channels Number of input channels + /// \param out_channels Number of output channels + /// \param receptive_field Size of the impulse response + /// \param _bias Whether to use bias + /// \param weights Model weights (impulse response coefficients) + /// \param expected_sample_rate Expected sample rate in Hz (-1.0 if unknown) + Linear(const int in_channels, const int out_channels, const int receptive_field, const bool _bias, + const std::vector& weights, const double expected_sample_rate = -1.0); + + /// \brief Process audio frames + /// \param input Input audio buffers + /// \param output Output audio buffers + /// \param num_frames Number of frames to process + void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override; + +protected: + Eigen::VectorXf _weight; + float _bias; +}; + +namespace linear +{ + +/// \brief Configuration for a Linear model +struct LinearConfig : public ModelConfig +{ + int receptive_field; + bool bias; + int in_channels; + int out_channels; + + std::unique_ptr create(std::vector weights, double sampleRate) override; +}; + +/// \brief Parse Linear configuration from JSON +/// \param config JSON configuration object +/// \return LinearConfig +LinearConfig parse_config_json(const nlohmann::json& config); + +/// \brief Config parser for ConfigParserRegistry +/// \param config JSON configuration object +/// \param sampleRate Expected sample rate in Hz +/// \return unique_ptr wrapping a LinearConfig +std::unique_ptr create_config(const nlohmann::json& config, double sampleRate); +} // namespace linear + +} // namespace nam