From cab4217e36cc40cd6eaf9e06cee1bb76f14b34ae Mon Sep 17 00:00:00 2001
From: Steven Atkinson <steven@atkinson.mn>
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 e975001..a4040c3 100644
--- a/NAM/dsp.cpp
+++ b/NAM/dsp.cpp
@@ -9,8 +9,6 @@
 #include <unordered_set>
 
 #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<float>& 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<const Eigen::VectorXf>(&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::DSP> nam::linear::LinearConfig::create(std::vector<float> weights, double sampleRate)
-{
-  return std::make_unique<nam::Linear>(in_channels, out_channels, receptive_field, bias, weights, sampleRate);
-}
-
-// Config parser for ConfigParserRegistry
-std::unique_ptr<nam::ModelConfig> nam::linear::create_config(const nlohmann::json& config, double sampleRate)
-{
-  (void)sampleRate;
-  auto c = std::make_unique<LinearConfig>();
-  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 1fadcf7..7f66134 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<float>& 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<DSP> create(std::vector<float> 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<ModelConfig> wrapping a LinearConfig
-std::unique_ptr<ModelConfig> 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<DSP> 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 0000000..6ffe040
--- /dev/null
+++ b/NAM/linear.cpp
@@ -0,0 +1,85 @@
+#include "linear.h"
+
+#include <algorithm>
+#include <stdexcept>
+
+#include "registry.h"
+
+nam::Linear::Linear(const int in_channels, const int out_channels, const int receptive_field, const bool _bias,
+                    const std::vector<float>& 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<const Eigen::VectorXf>(&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::DSP> nam::linear::LinearConfig::create(std::vector<float> weights, double sampleRate)
+{
+  return std::make_unique<nam::Linear>(in_channels, out_channels, receptive_field, bias, weights, sampleRate);
+}
+
+std::unique_ptr<nam::ModelConfig> nam::linear::create_config(const nlohmann::json& config, double sampleRate)
+{
+  (void)sampleRate;
+  auto c = std::make_unique<LinearConfig>();
+  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 0000000..dbe9454
--- /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<float>& 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<DSP> create(std::vector<float> 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<ModelConfig> wrapping a LinearConfig
+std::unique_ptr<ModelConfig> create_config(const nlohmann::json& config, double sampleRate);
+} // namespace linear
+
+} // namespace nam
