diff --git a/NAM/convnet.cpp b/NAM/convnet.cpp index ef63121..76d3baa 100644 --- a/NAM/convnet.cpp +++ b/NAM/convnet.cpp @@ -109,9 +109,9 @@ nam::convnet::ConvNet::ConvNet(const int channels, const std::vector& dilat if (it != weights.end()) throw std::runtime_error("Didn't touch all the weights when initializing ConvNet"); - _prewarm_samples = 1; + mPrewarmSamples = 1; for (size_t i = 0; i < dilations.size(); i++) - _prewarm_samples += dilations[i]; + mPrewarmSamples += dilations[i]; } diff --git a/NAM/convnet.h b/NAM/convnet.h index 310a1e5..b1fa142 100644 --- a/NAM/convnet.h +++ b/NAM/convnet.h @@ -71,6 +71,8 @@ class ConvNet : public Buffer std::vector& weights, const double expected_sample_rate = -1.0); ~ConvNet() = default; + void process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) override; + protected: std::vector _blocks; std::vector _block_vals; @@ -81,7 +83,8 @@ class ConvNet : public Buffer void _update_buffers_(NAM_SAMPLE* input, const int num_frames) override; void _rewind_buffers_() override; - void process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) override; + int mPrewarmSamples = 0; // Pre-compute during initialization + int PrewarmSamples() override { return mPrewarmSamples; }; }; }; // namespace convnet }; // namespace nam diff --git a/NAM/dsp.cpp b/NAM/dsp.cpp index c94cb73..b945789 100644 --- a/NAM/dsp.cpp +++ b/NAM/dsp.cpp @@ -21,14 +21,15 @@ nam::DSP::DSP(const double expected_sample_rate) void nam::DSP::prewarm() { - if (_prewarm_samples == 0) + const int prewarmSamples = PrewarmSamples(); + if (prewarmSamples == 0) return; NAM_SAMPLE sample = 0; NAM_SAMPLE* sample_ptr = &sample; // pre-warm the model for a model-specific number of samples - for (long i = 0; i < _prewarm_samples; i++) + for (long i = 0; i < prewarmSamples; i++) { this->process(sample_ptr, sample_ptr, 1); sample = 0; @@ -51,6 +52,17 @@ double nam::DSP::GetLoudness() const return mLoudness; } +void nam::DSP::Reset(const double sampleRate, const int maxBufferSize) +{ + // Some subclasses might want to throw an exception if the sample rate is "wrong". + // This could be under a debugging flag potentially. + mExternalSampleRate = sampleRate; + mHaveExternalSampleRate = true; + mMaxBufferSize = maxBufferSize; + + // Subclasses might also want to pre-warm, but let them call that themselves in case + // they want to e.g. do some allocations first. +} void nam::DSP::SetLoudness(const double loudness) { mLoudness = loudness; diff --git a/NAM/dsp.h b/NAM/dsp.h index fd176da..35bd3bc 100644 --- a/NAM/dsp.h +++ b/NAM/dsp.h @@ -44,6 +44,9 @@ class DSP virtual ~DSP() = default; // prewarm() does any required intial work required to "settle" model initial conditions // it can be somewhat expensive, so should not be called during realtime audio processing + // Important: don't expect the model to be outputting zeroes after this. Neural networks + // Don't know that there's anything special about "zero", and forcing this gets rid of + // some possibilities that I dont' want to rule out (e.g. models that "are noisy"). virtual void prewarm(); // process() does all of the processing requried to take `input` array and // fill in the required values on `output`. @@ -60,6 +63,16 @@ class DSP double GetLoudness() const; // Get whether the model knows how loud it is. bool HasLoudness() const { return mHasLoudness; }; + // General function for resetting the DSP unit. + // This doesn't call prewarm(). If you want to do that, then you might want to use ResetAndPrewarm(). + // See https://github.com/sdatkinson/NeuralAmpModelerCore/issues/96 for the reasoning. + virtual void Reset(const double sampleRate, const int maxBufferSize); + // Reset(), then prewarm() + void ResetAndPrewarm(const double sampleRate, const int maxBufferSize) + { + Reset(sampleRate, maxBufferSize); + prewarm(); + } // Set the loudness, in dB. // This is usually defined to be the loudness to a standardized input. The trainer has its own, but you can always // use this to define it a different way if you like yours better. @@ -71,8 +84,14 @@ class DSP double mLoudness = 0.0; // What sample rate does the model expect? double mExpectedSampleRate; - // How many samples should be processed during "pre-warming" - int _prewarm_samples = 0; + // Have we been told what the external sample rate is? If so, what is it? + bool mHaveExternalSampleRate = false; + double mExternalSampleRate = -1.0; + // The largest buffer I expect to be told to process: + int mMaxBufferSize; + + // How many samples should be processed for me to be considered "warmed up"? + virtual int PrewarmSamples() { return 0; }; }; // Class where an input buffer is kept so that long-time effects can be @@ -116,6 +135,7 @@ class Linear : public Buffer // NN modules ================================================================= +// TODO conv could take care of its own ring buffer. class Conv1D { public: diff --git a/NAM/lstm.cpp b/NAM/lstm.cpp index 38ff494..437ce81 100644 --- a/NAM/lstm.cpp +++ b/NAM/lstm.cpp @@ -84,6 +84,14 @@ void nam::lstm::LSTM::process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int n output[i] = this->_process_sample(input[i]); } +int nam::lstm::LSTM::PrewarmSamples() +{ + int result = (int)(0.5 * mExpectedSampleRate); + // If the expected sample rate wasn't provided, it'll be -1. + // Make sure something still happens. + return result <= 0 ? 1 : result; +} + float nam::lstm::LSTM::_process_sample(const float x) { if (this->_layers.size() == 0) diff --git a/NAM/lstm.h b/NAM/lstm.h index 6b02b18..7ee38e0 100644 --- a/NAM/lstm.h +++ b/NAM/lstm.h @@ -55,6 +55,9 @@ class LSTM : public DSP ~LSTM() = default; protected: + // Hacky, but a half-second seems to work for most models. + int PrewarmSamples() override; + Eigen::VectorXf _head_weight; float _head_bias; void process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) override; diff --git a/NAM/wavenet.cpp b/NAM/wavenet.cpp index 6941f81..2104c4c 100644 --- a/NAM/wavenet.cpp +++ b/NAM/wavenet.cpp @@ -267,9 +267,9 @@ nam::wavenet::WaveNet::WaveNet(const std::vector this->_head_output.resize(1, 0); // Mono output! this->set_weights_(weights); - _prewarm_samples = 1; + mPrewarmSamples = 1; for (size_t i = 0; i < this->_layer_arrays.size(); i++) - _prewarm_samples += this->_layer_arrays[i].get_receptive_field(); + mPrewarmSamples += this->_layer_arrays[i].get_receptive_field(); } void nam::wavenet::WaveNet::set_weights_(std::vector& weights) diff --git a/NAM/wavenet.h b/NAM/wavenet.h index 745b3e7..a2ad2fc 100644 --- a/NAM/wavenet.h +++ b/NAM/wavenet.h @@ -196,6 +196,9 @@ class WaveNet : public DSP virtual void _set_condition_array(NAM_SAMPLE* input, const int num_frames); // Ensure that all buffer arrays are the right size for this num_frames void _set_num_frames_(const long num_frames); + + int mPrewarmSamples = 0; // Pre-compute during initialization + int PrewarmSamples() override { return mPrewarmSamples; }; }; }; // namespace wavenet }; // namespace nam diff --git a/tools/benchmodel.cpp b/tools/benchmodel.cpp index 4b1c3e3..99f3923 100644 --- a/tools/benchmodel.cpp +++ b/tools/benchmodel.cpp @@ -11,7 +11,8 @@ using std::chrono::milliseconds; #define AUDIO_BUFFER_SIZE 64 -double buffer[AUDIO_BUFFER_SIZE]; +double inputBuffer[AUDIO_BUFFER_SIZE]; +double outputBuffer[AUDIO_BUFFER_SIZE]; int main(int argc, char* argv[]) { @@ -38,14 +39,22 @@ int main(int argc, char* argv[]) auto t1 = high_resolution_clock::now(); - size_t bufferSize = 64; - size_t numBuffers = (48000 / 64) * 2; + size_t bufferSize = AUDIO_BUFFER_SIZE; + model->Reset(model->GetExpectedSampleRate(), bufferSize); + size_t numBuffers = (48000 / bufferSize) * 2; + + // Fill input buffer with zeroes. + // Output buffer doesn't matter. + for (int i = 0; i < AUDIO_BUFFER_SIZE; i++) + { + inputBuffer[i] = 0.0; + } std::cout << "Running benchmark\n"; for (size_t i = 0; i < numBuffers; i++) { - model->process(buffer, buffer, AUDIO_BUFFER_SIZE); + model->process(inputBuffer, outputBuffer, AUDIO_BUFFER_SIZE); } std::cout << "Finished\n";