BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

commit 1fbc6328ec12d8017d0873f15173f920b004d513
parent 27d811e8fcbedec3d70747c2dd0dd1b57b158aeb
Author: Matt Demanett <matt@demanett.net>
Date:   Tue,  5 Dec 2017 01:32:34 -0500

Analyzer: pull fft averaging out into separate class; add benchmark on it; optimization.

Diffstat:
Mbenchmarks/buffer.cpp | 28++++++++++++++++++++++++++++
Msrc/Analyzer.cpp | 66++++++++++++++++++++++++++++++++++++------------------------------
Msrc/dsp/buffer.hpp | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 131 insertions(+), 30 deletions(-)

diff --git a/benchmarks/buffer.cpp b/benchmarks/buffer.cpp @@ -18,3 +18,31 @@ static void BM_OverlappingBuffer(benchmark::State& state) { } } BENCHMARK(BM_OverlappingBuffer); + + +static void _averagingBuffer(benchmark::State& state, int n, int m) { + AveragingBuffer<float> b(n, m); + for (int i = 0; i < m; ++i) { + float* frame = b.getInputFrame(); + std::fill_n(frame, n, M_PI); + b.commitInputFrame(); + } + float pi = 0.0; + for (auto _ : state) { + b.getInputFrame(); + b.commitInputFrame(); + pi = b.getAverages()[0]; + } + const float e = 0.00001; + assert(pi > M_PI - e && pi < M_PI + e); +} + +static void BM_AveragingBufferSmallN(benchmark::State& state) { + _averagingBuffer(state, 1024, 3); +} +BENCHMARK(BM_AveragingBufferSmallN); + +static void BM_AveragingBufferLargeN(benchmark::State& state) { + _averagingBuffer(state, 1024, 100); +} +BENCHMARK(BM_AveragingBufferLargeN); diff --git a/src/Analyzer.cpp b/src/Analyzer.cpp @@ -7,11 +7,9 @@ using namespace bogaudio::dsp; struct ChannelAnalyzer : SpectrumAnalyzer { - const int _averageN; - const int _binsN; + int _binsN; float* _bins; - float* _frames; - int _currentFrame; + AveragingBuffer<float>* _averagedBins; ChannelAnalyzer( SpectrumAnalyzer::Size size, @@ -22,16 +20,27 @@ struct ChannelAnalyzer : SpectrumAnalyzer { int binSize ) : SpectrumAnalyzer(size, overlap, windowType, sampleRate) - , _averageN(averageN) , _binsN(size / binSize) - , _bins(new float[size] {}) - , _frames(new float[_averageN * _binsN] {}) - , _currentFrame(0) + , _bins(averageN == 1 ? new float[_binsN] {} : NULL) + , _averagedBins(averageN == 1 ? NULL : new AveragingBuffer<float>(_binsN, averageN)) { + assert(averageN >= 1); + assert(binSize >= 1); } virtual ~ChannelAnalyzer() { - delete[] _bins; - delete[] _frames; + if (_bins) { + delete[] _bins; + } + if (_averagedBins) { + delete _averagedBins; + } + } + + const float* getBins() { + if (_bins) { + return _bins; + } + return _averagedBins->getAverages(); } virtual bool step(float sample) override; @@ -40,18 +49,14 @@ struct ChannelAnalyzer : SpectrumAnalyzer { bool ChannelAnalyzer::step(float sample) { if (SpectrumAnalyzer::step(sample)) { - float* frame = _frames + _currentFrame*_binsN; - getMagnitudes(frame, _binsN); - - for (int bin = 0; bin < _binsN; ++bin) { - _bins[bin] = 0.0; - for (int i = 0; i < _averageN; ++i) { - _bins[bin] += _frames[i*_binsN + bin]; - } - _bins[bin] /= (float)_averageN; - // FIXME: still unclear if should or should not take the root here: _bins[bin] = sqrtf(_bins[bin]); + if (_bins) { + getMagnitudes(_bins, _binsN); + } + else { + float* frame = _averagedBins->getInputFrame(); + getMagnitudes(frame, _binsN); + _averagedBins->commitInputFrame(); } - _currentFrame = (_currentFrame + 1) % _averageN; return true; } return false; @@ -61,12 +66,13 @@ float ChannelAnalyzer::getPeak() { float max = 0.0; float sum = 0.0; int maxBin = 0; + const float* bins = getBins(); for (int bin = 0; bin < _binsN; ++bin) { - if( _bins[bin] > max) { - max = _bins[bin]; + if (bins[bin] > max) { + max = bins[bin]; maxBin = bin; } - sum += _bins[bin]; + sum += bins[bin]; } const float fWidth = _sampleRate / (float)(_size / (_size / _binsN)); return (maxBin + 1)*fWidth - fWidth/2.0; @@ -262,7 +268,7 @@ struct AnalyzerDisplay : TransparentWidget { void drawYAxis(NVGcontext* vg, float strokeWidth); void drawXAxis(NVGcontext* vg, float strokeWidth); void drawXAxisLine(NVGcontext* vg, float hz, float maxHz); - void drawGraph(NVGcontext* vg, float* bins, int binsN, NVGcolor color, float strokeWidth); + void drawGraph(NVGcontext* vg, const float* bins, int binsN, NVGcolor color, float strokeWidth); void drawText(NVGcontext* vg, const char* s, float x, float y, float rotation = 0.0, const NVGcolor* color = NULL); int binValueToHeight(float value); }; @@ -279,16 +285,16 @@ void AnalyzerDisplay::draw(NVGcontext* vg) { drawXAxis(vg, strokeWidth); if (_module->_channelA) { - drawGraph(vg, _module->_channelA->_bins, _module->_channelA->_binsN, _channelAColor, strokeWidth); + drawGraph(vg, _module->_channelA->getBins(), _module->_channelA->_binsN, _channelAColor, strokeWidth); } if (_module->_channelB) { - drawGraph(vg, _module->_channelB->_bins, _module->_channelB->_binsN, _channelBColor, strokeWidth); + drawGraph(vg, _module->_channelB->getBins(), _module->_channelB->_binsN, _channelBColor, strokeWidth); } if (_module->_channelC) { - drawGraph(vg, _module->_channelC->_bins, _module->_channelC->_binsN, _channelCColor, strokeWidth); + drawGraph(vg, _module->_channelC->getBins(), _module->_channelC->_binsN, _channelCColor, strokeWidth); } if (_module->_channelD) { - drawGraph(vg, _module->_channelD->_bins, _module->_channelD->_binsN, _channelDColor, strokeWidth); + drawGraph(vg, _module->_channelD->getBins(), _module->_channelD->_binsN, _channelDColor, strokeWidth); } nvgRestore(vg); } @@ -453,7 +459,7 @@ void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz, float maxHz) { } } -void AnalyzerDisplay::drawGraph(NVGcontext* vg, float* bins, int binsN, NVGcolor color, float strokeWidth) { +void AnalyzerDisplay::drawGraph(NVGcontext* vg, const float* bins, int binsN, NVGcolor color, float strokeWidth) { const int pointsN = roundf(_module->_range*(_module->size()/2)); nvgSave(vg); nvgScissor(vg, _insetLeft, _insetTop, _graphSize.x, _graphSize.y); diff --git a/src/dsp/buffer.hpp b/src/dsp/buffer.hpp @@ -51,5 +51,72 @@ struct OverlappingBuffer { } }; + +template<typename T> +struct AveragingBuffer { + const int _size; + const int _framesN; + const float _inverseFramesN; + T* _sums; + T* _averages; + T* _frames; + int _currentFrame; + const int _resetsPerCommit; + int _currentReset; + + AveragingBuffer( + int size, + int framesToAverage + ) + : _size(size) + , _framesN(framesToAverage) + , _inverseFramesN(1.0 / (float)_framesN) + , _sums(new T[_size] {}) + , _averages(new T[_size] {}) + , _frames(new T[_size * _framesN] {}) + , _currentFrame(0) + , _resetsPerCommit(_size / 1000) + , _currentReset(0) + { + assert(framesToAverage > 0); + } + ~AveragingBuffer() { + delete[] _sums; + delete[] _averages; + delete[] _frames; + } + + T* getInputFrame() { + float* frame = _frames + _currentFrame*_size; + for (int i = 0; i < _size; ++i) { + _sums[i] -= frame[i]; + } + return frame; + } + + void commitInputFrame() { + float* frame = _frames + _currentFrame*_size; + for (int i = 0; i < _size; ++i) { + _sums[i] += frame[i]; + _averages[i] = _sums[i] * _inverseFramesN; + } + + // Reset the average for some bins, such that reset overhead is even between calls -- avoids buildup of floating point error. + for (int i = 0; i < _resetsPerCommit; ++i) { + _sums[_currentReset] = 0.0; + for (int j = 0; j < _framesN; ++j) { + _sums[_currentReset] += _frames[j*_size + _currentReset]; + } + _currentReset = (_currentReset + 1) % _size; + } + + _currentFrame = (_currentFrame + 1) % _framesN; + } + + const T* getAverages() { + return _averages; + } +}; + } // namespace dsp } // namespace bogaudio