diff --git a/CMakeLists.txt b/CMakeLists.txt index d21f36a2..eed7055b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ endif() set(NAM_DEPS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Dependencies") include_directories(SYSTEM "${NAM_DEPS_PATH}/eigen") -# Build the specialized A2 fast-path WaveNet (A2 standard + A2 nano). When ON, +# Build the specialized A2 fast-path WaveNet (A2-Full + A2-Lite). When ON, # models whose config matches the A2 shape signature are routed to a hand-optimized # WaveNet implementation instead of the generic one. When OFF, all models go # through the generic path. diff --git a/NAM/conv1d.cpp b/NAM/conv1d.cpp index 8ae8ff43..3c43e188 100644 --- a/NAM/conv1d.cpp +++ b/NAM/conv1d.cpp @@ -1,5 +1,6 @@ #include "conv1d.h" #include "compiler.h" +#include #include #include @@ -9,6 +10,7 @@ namespace nam void Conv1D::set_weights_(std::vector::iterator& weights) { + _has_cached_prewarm_state = false; if (this->_is_depthwise) { // Depthwise convolution: one weight per channel per kernel tap @@ -109,6 +111,10 @@ void Conv1D::set_size_(const int in_channels, const int out_channels, const int } else this->_bias.resize(0); + + _cached_prewarm_state.resize(in_channels); + _cached_prewarm_state.setZero(); + _has_cached_prewarm_state = false; } void Conv1D::set_size_and_weights_(const int in_channels, const int out_channels, const int kernel_size, @@ -142,6 +148,17 @@ void Conv1D::SetMaxBufferSize(const int maxBufferSize) _output.setZero(); } +void Conv1D::PrewarmFromCache() +{ + assert(HasCachedPrewarmState()); + _input_buffer.FillWithSample(_cached_prewarm_state); +} + +void Conv1D::CacheStateAsPrewarmed() +{ + _input_buffer.CacheLastWrittenSample(_cached_prewarm_state); + _has_cached_prewarm_state = true; +} void Conv1D::Process(const Eigen::MatrixXf& input, const int num_frames) { diff --git a/NAM/conv1d.h b/NAM/conv1d.h index 8f006864..7c0a8802 100644 --- a/NAM/conv1d.h +++ b/NAM/conv1d.h @@ -117,6 +117,15 @@ class Conv1D /// \return true if bias is present, false otherwise bool has_bias() const { return this->_bias.size() > 0; }; + /// \brief Check whether a steady-state prewarm sample has been cached + bool HasCachedPrewarmState() const { return _has_cached_prewarm_state; } + + /// \brief Restore the input history from the cached steady-state sample + void PrewarmFromCache(); + + /// \brief Cache the most recently written input sample as the steady prewarm state + void CacheStateAsPrewarmed(); + protected: // conv[kernel](cout, cin) - used for non-depthwise convolutions std::vector _weight; @@ -131,6 +140,8 @@ class Conv1D private: RingBuffer _input_buffer; // Ring buffer for input (channels x buffer_size) + Eigen::VectorXf _cached_prewarm_state; + bool _has_cached_prewarm_state = false; Eigen::MatrixXf _output; // Pre-allocated output buffer (out_channels x maxBufferSize) int _max_buffer_size = 0; // Stored maxBufferSize }; diff --git a/NAM/ring_buffer.cpp b/NAM/ring_buffer.cpp index 8f0919a8..95f361ee 100644 --- a/NAM/ring_buffer.cpp +++ b/NAM/ring_buffer.cpp @@ -61,6 +61,20 @@ void RingBuffer::Advance(const int num_frames) _write_pos += num_frames; } +void RingBuffer::CacheLastWrittenSample(Eigen::VectorXf& destination) const +{ + assert(_write_pos > 0); + assert(destination.size() == _storage.rows()); + destination = _storage.col(_write_pos - 1); +} + +void RingBuffer::FillWithSample(const Eigen::VectorXf& sample) +{ + assert(sample.size() == _storage.rows()); + _storage.colwise() = sample; + _write_pos = _max_lookback; +} + bool RingBuffer::NeedsRewind(const int num_frames) const { return _write_pos + num_frames > (long)_storage.cols(); diff --git a/NAM/ring_buffer.h b/NAM/ring_buffer.h index 5d0e9b3a..ea41ea08 100644 --- a/NAM/ring_buffer.h +++ b/NAM/ring_buffer.h @@ -52,6 +52,12 @@ class RingBuffer /// \param max_lookback Maximum lookback distance void SetMaxLookback(const long max_lookback) { _max_lookback = max_lookback; } + /// \brief Copy the most recently written sample into pre-allocated storage + void CacheLastWrittenSample(Eigen::VectorXf& destination) const; + + /// \brief Fill the buffer with a constant sample and restore its initial write position + void FillWithSample(const Eigen::VectorXf& sample); + private: // Wrap buffer when approaching end (called automatically if needed) void Rewind(); diff --git a/NAM/wavenet/a2_fast.cpp b/NAM/wavenet/a2_fast.cpp index ee72ab60..08d52e72 100644 --- a/NAM/wavenet/a2_fast.cpp +++ b/NAM/wavenet/a2_fast.cpp @@ -66,6 +66,7 @@ class A2FastModel : public DSP ~A2FastModel() override = default; void process(NAM_SAMPLE** input, NAM_SAMPLE** output, int num_frames) override; + void prewarm() override; int GetPrewarmSamples() override { return _prewarm_samples; } protected: @@ -92,6 +93,7 @@ class A2FastModel : public DSP // Conv1D input history ring buffer, column-major (Channels rows). std::vector history; + std::array cached_prewarm_state{}; #if NAM_A2_RING_MODE == 1 // pow2 ring + tail mirror. Storage = (pow2_size + max_buffer_size) cols. // write_pos is kept in [0, pow2_size), reads use (pos & pow2_mask) and are @@ -124,6 +126,7 @@ class A2FastModel : public DSP // Head ring buffer (Channels rows, col-major). Same ring layout as per-layer. std::vector _head_history; + std::array _cached_head_prewarm_state{}; #if NAM_A2_RING_MODE == 1 int _head_pow2_size = 0; int _head_pow2_mask = 0; @@ -141,8 +144,12 @@ class A2FastModel : public DSP std::vector _head_out; // float32 head output before writing to NAM_SAMPLE int _prewarm_samples = 0; + bool _has_cached_prewarm_state = false; void _load_weights(std::vector& weights); + bool HasCachedPrewarmState() const { return _has_cached_prewarm_state; } + void PrewarmFromCache(); + void CacheStateAsPrewarmed(); void _ring_write(Layer& L, int num_frames); void _head_ring_write(int num_frames); void _layer_forward(int layer_idx, const float* cond, int num_frames); @@ -329,6 +336,73 @@ void A2FastModel::SetMaxBufferSize(int maxBufferSize) #endif } +// ----------------------------------------------------------------------------- +// Prewarm-state cache +// +// Processing silence for a full receptive field leaves every convolution +// history constant in time. Keep one Channels-wide column from each layer and +// the head so later prewarms can rebuild the complete histories directly. +// ----------------------------------------------------------------------------- +template +void A2FastModel::prewarm() +{ + if (HasCachedPrewarmState()) + { + PrewarmFromCache(); + return; + } + + DSP::prewarm(); + CacheStateAsPrewarmed(); +} + +template +void A2FastModel::PrewarmFromCache() +{ + for (auto& L : _layers) + { + const size_t columns = L.history.size() / Channels; + for (size_t column = 0; column < columns; column++) + { + std::copy(L.cached_prewarm_state.begin(), L.cached_prewarm_state.end(), + L.history.begin() + static_cast(column * Channels)); + } + L.write_pos = L.max_lookback; + } + + const size_t head_columns = _head_history.size() / Channels; + for (size_t column = 0; column < head_columns; column++) + { + std::copy(_cached_head_prewarm_state.begin(), _cached_head_prewarm_state.end(), + _head_history.begin() + static_cast(column * Channels)); + } + _head_write_pos = kHeadKernelSize - 1; +} + +template +void A2FastModel::CacheStateAsPrewarmed() +{ + for (auto& L : _layers) + { + #if NAM_A2_RING_MODE == 1 + const int last_column = (L.write_pos - 1) & L.pow2_mask; + #else + const int last_column = L.write_pos - 1; + #endif + std::copy_n(L.history.begin() + static_cast(last_column * Channels), Channels, + L.cached_prewarm_state.begin()); + } + + #if NAM_A2_RING_MODE == 1 + const int last_head_column = (_head_write_pos - 1) & _head_pow2_mask; + #else + const int last_head_column = _head_write_pos - 1; + #endif + std::copy_n(_head_history.begin() + static_cast(last_head_column * Channels), Channels, + _cached_head_prewarm_state.begin()); + _has_cached_prewarm_state = true; +} + // ----------------------------------------------------------------------------- // Ring-write helpers. // Mode 1: pow2 + tail mirror. Constant-time per block (one short memcpy @@ -430,14 +504,14 @@ void A2FastModel::_layer_forward_k(Layer& L, const float* cond, int nu // Two conv strategies, dispatched at compile time on Channels: // - // - Channels <= 4 (A2 nano): full-block tap-major. The z accumulator lives + // - Channels <= 4 (A2-Lite): full-block tap-major. The z accumulator lives // in the heap buffer across all taps, and for each tap the inner f-loop // iterates over all num_frames. This gives clang frame-level // parallelism — it vectorizes across 4 frames at a time, which matters // more than weight-reload cost when the b-loop (3 wide) can't saturate // NEON lanes on its own. // - // - Channels >= 8 (A2 standard): frame-tiled tap-major with T=4. ztile + // - Channels >= 8 (A2-Full): frame-tiled tap-major with T=4. ztile // stays in NEON registers across all K taps, amortizing weight loads // over 4 frames — equivalent to what a GEMM kernel does. Weight reuse // matters here because the b-loop (8 wide) already saturates SIMD, so diff --git a/NAM/wavenet/a2_fast.h b/NAM/wavenet/a2_fast.h index 7bc1b0b7..7fac5347 100644 --- a/NAM/wavenet/a2_fast.h +++ b/NAM/wavenet/a2_fast.h @@ -1,7 +1,7 @@ #pragma once -// Specialized WaveNet fast path for the A2 standard (Channels=8) and -// A2 nano (Channels=3) models. Shares the same architecture shape; only +// Specialized WaveNet fast path for the A2-Full (Channels=8) and +// A2-Lite (Channels=3) models. Shares the same architecture shape; only // the channel count differs. // // When NAM_ENABLE_A2_FAST is defined at build time, wavenet::create_config @@ -34,17 +34,17 @@ constexpr int kHeadKernelSize = 16; /// \brief LeakyReLU negative-slope used by every layer. constexpr float kLeakySlope = 0.01f; -/// \brief Per-layer kernel sizes (fixed pattern shared by A2 standard + nano). +/// \brief Per-layer kernel sizes (fixed pattern shared by A2-Full + A2-Lite). inline constexpr std::array kKernelSizes = { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 15, 15, 6, 6, 6, 6, 6, 6, 6}; -/// \brief Per-layer dilations (fixed pattern shared by A2 standard + nano). +/// \brief Per-layer dilations (fixed pattern shared by A2-Full + A2-Lite). inline constexpr std::array kDilations = { 1, 3, 7, 17, 41, 101, 239, 1, 3, 7, 17, 41, 101, 239, 1, 13, 1, 3, 7, 17, 41, 101, 239}; /// \brief Strict detector: returns true iff config matches the A2 shape. /// \param config The "config" sub-object from a .nam WaveNet entry. -/// \param channels Out-param set to 3 (A2 nano) or 8 (A2 standard) on match. +/// \param channels Out-param set to 3 (A2-Lite) or 8 (A2-Full) on match. /// \return true if every architectural knob matches the A2 signature exactly. bool is_a2_shape(const nlohmann::json& config, int* channels); diff --git a/NAM/wavenet/detail.h b/NAM/wavenet/detail.h index 1e5d10e2..c7b06e1a 100644 --- a/NAM/wavenet/detail.h +++ b/NAM/wavenet/detail.h @@ -224,6 +224,10 @@ class Layer /// \return Const reference to the internal Conv1D object const Conv1D& get_conv() const { return _conv; } + bool HasCachedPrewarmState() const { return _conv.HasCachedPrewarmState(); } + void PrewarmFromCache() { _conv.PrewarmFromCache(); } + void CacheStateAsPrewarmed() { _conv.CacheStateAsPrewarmed(); } + private: // The dilated convolution at the front of the block Conv1D _conv; @@ -335,6 +339,10 @@ class LayerArray /// \return Receptive field size long get_receptive_field() const; + bool HasCachedPrewarmState() const; + void PrewarmFromCache(); + void CacheStateAsPrewarmed(); + private: // The rechannel before the layers Conv1x1 _rechannel; @@ -376,6 +384,10 @@ class Head const Eigen::MatrixXf& get_last_output() const { return _convs.back().GetOutput(); } + bool HasCachedPrewarmState() const; + void PrewarmFromCache(); + void CacheStateAsPrewarmed(); + private: std::vector _convs; std::vector _activations; diff --git a/NAM/wavenet/model.cpp b/NAM/wavenet/model.cpp index 7689625d..7d5f6c1d 100644 --- a/NAM/wavenet/model.cpp +++ b/NAM/wavenet/model.cpp @@ -55,6 +55,23 @@ void nam::wavenet::detail::Head::SetMaxBufferSize(const int maxBufferSize) _convs[i].SetMaxBufferSize(maxBufferSize); } +bool nam::wavenet::detail::Head::HasCachedPrewarmState() const +{ + return std::all_of(_convs.begin(), _convs.end(), [](const Conv1D& conv) { return conv.HasCachedPrewarmState(); }); +} + +void nam::wavenet::detail::Head::PrewarmFromCache() +{ + for (auto& conv : _convs) + conv.PrewarmFromCache(); +} + +void nam::wavenet::detail::Head::CacheStateAsPrewarmed() +{ + for (auto& conv : _convs) + conv.CacheStateAsPrewarmed(); +} + long nam::wavenet::detail::Head::receptive_field() const { long rf = 1; @@ -413,7 +430,6 @@ void nam::wavenet::detail::LayerArray::SetMaxBufferSize(const int maxBufferSize) this->_head_inputs.resize(this->_head_output_size, maxBufferSize); } - long nam::wavenet::detail::LayerArray::get_receptive_field() const { long result = 0; @@ -423,6 +439,26 @@ long nam::wavenet::detail::LayerArray::get_receptive_field() const return result; } +bool nam::wavenet::detail::LayerArray::HasCachedPrewarmState() const +{ + return _head_rechannel.HasCachedPrewarmState() && std::all_of(_layers.begin(), _layers.end(), [](const Layer& layer) { + return layer.HasCachedPrewarmState(); + }); +} + +void nam::wavenet::detail::LayerArray::PrewarmFromCache() +{ + for (auto& layer : _layers) + layer.PrewarmFromCache(); + _head_rechannel.PrewarmFromCache(); +} + +void nam::wavenet::detail::LayerArray::CacheStateAsPrewarmed() +{ + for (auto& layer : _layers) + layer.CacheStateAsPrewarmed(); + _head_rechannel.CacheStateAsPrewarmed(); +} void nam::wavenet::detail::LayerArray::Process(const Eigen::MatrixXf& layer_inputs, const Eigen::MatrixXf& condition, const int num_frames) @@ -698,6 +734,46 @@ void nam::wavenet::WaveNet::SetPrewarmOnReset(const bool prewarmOnReset) this->_condition_dsp->SetPrewarmOnReset(prewarmOnReset); } +void nam::wavenet::WaveNet::prewarm() +{ + if (HasCachedPrewarmState()) + { + PrewarmFromCache(); + return; + } + + DSP::prewarm(); + CacheStateAsPrewarmed(); +} + +bool nam::wavenet::WaveNet::HasCachedPrewarmState() const +{ + if (_condition_dsp != nullptr) + return false; + if (!std::all_of(_layer_arrays.begin(), _layer_arrays.end(), + [](const detail::LayerArray& layer_array) { return layer_array.HasCachedPrewarmState(); })) + return false; + return _post_stack_head == nullptr || _post_stack_head->HasCachedPrewarmState(); +} + +void nam::wavenet::WaveNet::PrewarmFromCache() +{ + for (auto& layer_array : _layer_arrays) + layer_array.PrewarmFromCache(); + if (_post_stack_head != nullptr) + _post_stack_head->PrewarmFromCache(); +} + +void nam::wavenet::WaveNet::CacheStateAsPrewarmed() +{ + if (_condition_dsp != nullptr) + return; + for (auto& layer_array : _layer_arrays) + layer_array.CacheStateAsPrewarmed(); + if (_post_stack_head != nullptr) + _post_stack_head->CacheStateAsPrewarmed(); +} + void nam::wavenet::WaveNet::_process_condition(const int num_frames) { if (this->_condition_dsp == nullptr) diff --git a/NAM/wavenet/model.h b/NAM/wavenet/model.h index 5941892e..a878dd39 100644 --- a/NAM/wavenet/model.h +++ b/NAM/wavenet/model.h @@ -58,6 +58,8 @@ class WaveNet : public DSP /// \param num_frames Number of frames to process void process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) override; + void prewarm() override; + void SetPrewarmOnReset(const bool prewarmOnReset) override; /// \brief Set model weights from a vector @@ -115,6 +117,10 @@ class WaveNet : public DSP Eigen::MatrixXf _scaled_head_scratch; int mPrewarmSamples = 0; // Pre-compute during initialization + + bool HasCachedPrewarmState() const; + void PrewarmFromCache(); + void CacheStateAsPrewarmed(); }; /// \brief Configuration for a WaveNet model diff --git a/tools/bench_a2_fast.cpp b/tools/bench_a2_fast.cpp index 40575031..8a0a58d8 100644 --- a/tools/bench_a2_fast.cpp +++ b/tools/bench_a2_fast.cpp @@ -274,7 +274,7 @@ void bench_model(const LoadedModel& m, const Options& o) const Stats gen_s = compute_stats(gen_block_times); const double block_audio_us = 1e6 * o.buffer_size / m.sample_rate; - const std::string arch = (channels == 3) ? "A2 nano" : (channels == 8 ? "A2 standard" : "A2 unknown"); + const std::string arch = (channels == 3) ? "A2-Lite" : (channels == 8 ? "A2-Full" : "A2 unknown"); auto fmt_us = [](double ms) { return ms * 1000.0; }; std::cout << "\n== " << m.path << " (" << arch << ", Channels=" << channels << ") ==\n"; diff --git a/tools/run_tests.cpp b/tools/run_tests.cpp index 3c800dcf..f3e1f0af 100644 --- a/tools/run_tests.cpp +++ b/tools/run_tests.cpp @@ -364,8 +364,8 @@ int main() #if defined(NAM_ENABLE_A2_FAST) // A2 fast-path WaveNet: detector coverage + numerical match against generic. - test_a2_fast::test_detector_matches_nano(); - test_a2_fast::test_detector_matches_standard(); + test_a2_fast::test_detector_matches_lite(); + test_a2_fast::test_detector_matches_full(); test_a2_fast::test_detector_accepts_nonstandard_head_scale(); test_a2_fast::test_detector_rejects_wrong_channels(); test_a2_fast::test_detector_rejects_wrong_kernel_sizes(); @@ -373,12 +373,14 @@ int main() test_a2_fast::test_detector_rejects_gating(); test_a2_fast::test_detector_rejects_condition_dsp(); test_a2_fast::test_detector_rejects_legacy_gated(); - test_a2_fast::test_matches_generic_nano(); - test_a2_fast::test_matches_generic_standard(); - test_a2_fast::test_prewarm_matches_generic_nano(); - test_a2_fast::test_prewarm_matches_generic_standard(); - test_a2_fast::test_process_realtime_safe_nano(); - test_a2_fast::test_process_realtime_safe_standard(); + test_a2_fast::test_matches_generic_lite(); + test_a2_fast::test_matches_generic_full(); + test_a2_fast::test_prewarm_matches_generic_lite(); + test_a2_fast::test_prewarm_matches_generic_full(); + test_a2_fast::test_cached_prewarm_lite(); + test_a2_fast::test_cached_prewarm_full(); + test_a2_fast::test_process_realtime_safe_lite(); + test_a2_fast::test_process_realtime_safe_full(); #endif std::cout << "Success!" << std::endl; diff --git a/tools/test/test_a2_fast.cpp b/tools/test/test_a2_fast.cpp index 742d04d7..914c57d9 100644 --- a/tools/test/test_a2_fast.cpp +++ b/tools/test/test_a2_fast.cpp @@ -31,7 +31,7 @@ namespace { // Build a JSON config with the A2 shape, parameterized by channel count -// (3 = A2 nano, 8 = A2 standard). Follows the real .nam schema so both the +// (3 = A2-Lite, 8 = A2-Full). Follows the real .nam schema so both the // strict detector and the generic parser accept it. nlohmann::json build_a2_config(int channels) { @@ -148,6 +148,24 @@ std::vector run_dsp(nam::DSP& dsp, const std::vector& in return out; } +std::vector process_dsp(nam::DSP& dsp, const std::vector& input, int block_size) +{ + std::vector out(input.size(), static_cast(0)); + int pos = 0; + const int total = static_cast(input.size()); + while (pos < total) + { + const int n = std::min(block_size, total - pos); + const NAM_SAMPLE* in_ptr = input.data() + pos; + NAM_SAMPLE* out_ptr = out.data() + pos; + const NAM_SAMPLE* in_arr[] = {in_ptr}; + NAM_SAMPLE* out_arr[] = {out_ptr}; + dsp.process(const_cast(in_arr), out_arr, n); + pos += n; + } + return out; +} + void compare(const std::vector& a, const std::vector& b, int channels, int block_size, double tol) { @@ -174,7 +192,7 @@ void compare(const std::vector& a, const std::vector& b, } // namespace -void test_detector_matches_nano() +void test_detector_matches_lite() { auto cfg = build_a2_config(3); int ch = 0; @@ -182,7 +200,7 @@ void test_detector_matches_nano() assert(ch == 3); } -void test_detector_matches_standard() +void test_detector_matches_full() { auto cfg = build_a2_config(8); int ch = 0; @@ -281,12 +299,12 @@ void test_matches_generic(int channels) } } -void test_matches_generic_nano() +void test_matches_generic_lite() { test_matches_generic(3); } -void test_matches_generic_standard() +void test_matches_generic_full() { test_matches_generic(8); } @@ -313,16 +331,63 @@ void test_prewarm_matches_generic(int channels) assert(fast_dsp->GetPrewarmSamples() == generic_dsp->GetPrewarmSamples()); } -void test_prewarm_matches_generic_nano() +void test_prewarm_matches_generic_lite() { test_prewarm_matches_generic(3); } -void test_prewarm_matches_generic_standard() +void test_prewarm_matches_generic_full() { test_prewarm_matches_generic(8); } +// With no cache, Reset() uses the legacy silence-processing prewarm and caches +// its steady state. Process more than a receptive field of audio to disturb every +// convolution history, restore the cached prewarm, then require the same audio to +// produce exactly the same output as it did after the legacy prewarm. +void test_cached_prewarm_dsp(nam::DSP& dsp, int channels, const std::string& implementation) +{ + const int block_size = 64; + dsp.Reset(48000.0, block_size); + const auto input = make_test_input(dsp.GetPrewarmSamples() + block_size, 48000.0); + const auto expected = process_dsp(dsp, input, block_size); + + // Cached restoration must not fall back to DSP::prewarm(), which allocates + // silence buffers and processes the full receptive field. + const std::string test_name = implementation + "<" + std::to_string(channels) + ">::cached prewarm"; + allocation_tracking::run_allocation_test_no_allocations( + nullptr, [&]() { dsp.prewarm(); }, nullptr, test_name.c_str()); + + const auto actual = process_dsp(dsp, input, block_size); + compare(expected, actual, channels, block_size, /*tol=*/1.0e-12); +} + +void test_cached_prewarm(int channels) +{ + const auto cfg = build_a2_config(channels); + const auto weights = make_deterministic_weights(a2_weight_count(channels), /*seed=*/0xA2CA000u + channels); + + auto fast_cfg = nam::wavenet::a2_fast::create_a2_fast_config(cfg, 48000.0); + std::vector w_fast = weights; + auto fast_dsp = fast_cfg->create(std::move(w_fast), 48000.0); + test_cached_prewarm_dsp(*fast_dsp, channels, "A2FastModel"); + + auto generic_cfg = nam::wavenet::parse_config_json(cfg, 48000.0); + std::vector w_generic = weights; + auto generic_dsp = generic_cfg.create(std::move(w_generic), 48000.0); + test_cached_prewarm_dsp(*generic_dsp, channels, "WaveNet"); +} + +void test_cached_prewarm_lite() +{ + test_cached_prewarm(3); +} + +void test_cached_prewarm_full() +{ + test_cached_prewarm(8); +} + // Real-time safety: once the DSP has been Reset (buffers sized, prewarmed), // subsequent process() calls must not allocate or free heap memory. Uses the // same allocation-tracking infrastructure as the generic WaveNet RT-safety @@ -378,12 +443,12 @@ void test_process_realtime_safe(int channels) } } -void test_process_realtime_safe_nano() +void test_process_realtime_safe_lite() { test_process_realtime_safe(3); } -void test_process_realtime_safe_standard() +void test_process_realtime_safe_full() { test_process_realtime_safe(8); }