commit 95ff1b7aa05fce8c167c2af9a6fcd1b2fc6f8acf
parent 34787d177c652395d4f0db188e98f9de52f6e445
Author: Matt Demanett <matt@demanett.net>
Date: Fri, 1 Dec 2017 02:39:39 -0500
Progress on Analyzer.
Diffstat:
4 files changed, 306 insertions(+), 126 deletions(-)
diff --git a/res/Analyzer-src.svg b/res/Analyzer-src.svg
Binary files differ.
diff --git a/res/Analyzer.svg b/res/Analyzer.svg
Binary files differ.
diff --git a/src/Analyzer.cpp b/src/Analyzer.cpp
@@ -4,18 +4,74 @@
#include "dsp/dsp.hpp"
#include "BogaudioModules.hpp"
+struct ChannelAnalyzer : bogaudio::dsp::SpectrumAnalyzer {
+ const int _averageN;
+ const int _binsN;
+ float* _bins;
+ float* _frames;
+ int _currentFrame;
+
+ ChannelAnalyzer(
+ bogaudio::dsp::SpectrumAnalyzer::Size size,
+ bogaudio::dsp::SpectrumAnalyzer::Overlap overlap,
+ bogaudio::dsp::SpectrumAnalyzer::WindowType windowType,
+ float sampleRate,
+ int averageN
+ )
+ : bogaudio::dsp::SpectrumAnalyzer(size, overlap, windowType, sampleRate)
+ , _averageN(averageN)
+ , _binsN(size / 2)
+ , _currentFrame(0)
+ {
+ _bins = new float[size] { 0.0 };
+ _frames = new float[_averageN * _binsN] { 0.0 };
+ }
+ virtual ~ChannelAnalyzer() {
+ delete[] _bins;
+ delete[] _frames;
+ }
+
+ virtual bool step(float sample) override;
+};
+
+bool ChannelAnalyzer::step(float sample) {
+ if (bogaudio::dsp::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]);
+ }
+ _currentFrame = (_currentFrame + 1) % _averageN;
+ return true;
+ }
+ return false;
+}
+
+
struct Analyzer : Module {
enum ParamsIds {
+ RANGE_PARAM,
+ // SMOOTH_PARAM,
+ // TYPE_PARAM,
+ POWER_PARAM,
NUM_PARAMS
};
enum InputsIds {
- SIGNAL_INPUT,
+ SIGNALA_INPUT,
+ SIGNALB_INPUT,
NUM_INPUTS
};
enum OutputsIds {
- SIGNAL_OUTPUT,
+ SIGNALA_OUTPUT,
+ SIGNALB_OUTPUT,
NUM_OUTPUTS
};
@@ -23,86 +79,94 @@ struct Analyzer : Module {
NUM_LIGHTS
};
- bogaudio::dsp::SpectrumAnalyzer* _analyzer = NULL;
- const int _displayBins = 256;
- const int _avgN = 10;
- float* _bins = NULL;
- float* _frames = NULL;
- int _currentFrame = 0;
+ enum DisplayType {
+ DISPLAYTYPE_LINE,
+ DISPLAYTYPE_BAR
+ };
+
+ const bogaudio::dsp::SpectrumAnalyzer::Size _size = bogaudio::dsp::SpectrumAnalyzer::SIZE_1024;
+ DisplayType _displayType;
+ int _averageN;
+ ChannelAnalyzer* _channelA = NULL;
+ ChannelAnalyzer* _channelB = NULL;
+ float _range = 0.0;
Analyzer() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
reset();
}
virtual ~Analyzer() {
- if (_analyzer) {
- delete _analyzer;
- delete[] _bins;
- delete[] _frames;
- }
+ reset();
}
virtual void reset() override;
virtual void step() override;
+ void stepChannel(ChannelAnalyzer*& channelPointer, bool running, Input& input, Output& output);
};
void Analyzer::reset() {
+ _displayType = DISPLAYTYPE_LINE;
+ _averageN = 10;
+ if (_channelA) {
+ delete _channelA;
+ _channelA = NULL;
+ }
+ if (_channelB) {
+ delete _channelB;
+ _channelB = NULL;
+ }
}
void Analyzer::step() {
- if (!inputs[SIGNAL_INPUT].active) {
- if (_analyzer) {
- delete _analyzer;
- _analyzer = NULL;
- delete[] _bins;
- _bins = NULL;
- delete[] _frames;
- _frames = NULL;
+ _range = clampf(params[RANGE_PARAM].value, 0.1, 1.0);
+ bool running = params[POWER_PARAM].value == 1.0;
+ stepChannel(_channelA, running, inputs[SIGNALA_INPUT], outputs[SIGNALA_OUTPUT]);
+ stepChannel(_channelB, running, inputs[SIGNALB_INPUT], outputs[SIGNALB_OUTPUT]);
+}
+
+void Analyzer::stepChannel(ChannelAnalyzer*& channelPointer, bool running, Input& input, Output& output) {
+ if (running && input.active) {
+ if (!channelPointer) {
+ channelPointer = new ChannelAnalyzer(
+ _size,
+ bogaudio::dsp::SpectrumAnalyzer::OVERLAP_2,
+ bogaudio::dsp::SpectrumAnalyzer::WINDOW_HAMMING,
+ engineGetSampleRate(),
+ _averageN
+ );
}
- outputs[SIGNAL_OUTPUT].value = 0.0;
- return;
- }
- if (!_analyzer) {
- _analyzer = new bogaudio::dsp::SpectrumAnalyzer(
- bogaudio::dsp::SpectrumAnalyzer::SIZE_1024,
- bogaudio::dsp::SpectrumAnalyzer::OVERLAP_2,
- bogaudio::dsp::SpectrumAnalyzer::WINDOW_HAMMING,
- // bogaudio::dsp::SpectrumAnalyzer::WINDOW_NONE,
- engineGetSampleRate()
- );
- _bins = new float[_displayBins] { 0.0 };
- _frames = new float[_avgN * _displayBins] { 0.0 };
- _currentFrame = 0;
+ channelPointer->step(input.value);
+ output.value = input.value;
}
-
- if (_analyzer->step(inputs[SIGNAL_INPUT].value)) {
- float* frame = _frames + _currentFrame*_displayBins;
- _analyzer->getMagnitudes(frame, _displayBins);
-
- for (int bin = 0; bin < _displayBins; ++bin) {
- _bins[bin] = 0.0;
- for (int i = 0; i < _avgN; ++i) {
- _bins[bin] += _frames[i * _displayBins + bin];
- }
- _bins[bin] /= (float)_avgN;
- // FIXME: still unclear if should or should not take the root here: _bins[bin] = sqrtf(_bins[bin]);
+ else {
+ if (channelPointer) {
+ delete channelPointer;
+ channelPointer = NULL;
}
- _currentFrame = (_currentFrame + 1) % _avgN;
- }
- outputs[SIGNAL_OUTPUT].value = inputs[SIGNAL_INPUT].value;
+ output.value = 0.0;
+ }
}
struct AnalyzerDisplay : TransparentWidget {
+ const int _insetLeft = 27;
+ const int _insetRight = 4;
+ const int _insetTop = 2;
+ const int _insetBottom = 18;
+
+ const float _refDB0 = 20.0; // arbitrary; makes a 10.0-amplitude sine wave reach to about 0db on the output.
+ const float _displayDB = 120.0;
+
+ const float xAxisLogFactor = 1 / 3.321; // magic number.
+
+ const NVGcolor _axisColor = nvgRGBA(0xff, 0xff, 0xff, 0x70);
+ const NVGcolor _channelAColor = nvgRGBA(0x00, 0xff, 0x00, 0xd0);
+ const NVGcolor _channelBColor = nvgRGBA(0xff, 0x00, 0x00, 0xd0);
+
Analyzer* _module;
const Vec _size;
-
- const int _levelWidth = 1;
- const int _levelSpace = 0;
- const Vec _levelsInset = Vec(30, 5);
- const int _levelHeight;
- const int _levelCount;
+ const Vec _graphSize;
AnalyzerDisplay(
Analyzer* module,
@@ -110,80 +174,182 @@ struct AnalyzerDisplay : TransparentWidget {
)
: _module(module)
, _size(size)
- , _levelHeight(_size.y - 2*_levelsInset.y)
- , _levelCount((size.x - _levelsInset.x + 1) / (_levelWidth + _levelSpace))
+ , _graphSize(_size.x - _insetLeft - _insetRight, _size.y - _insetTop - _insetBottom)
{
}
- void draw(NVGcontext *vg) override {
- const float refDB0 = 20.0; // arbitrary; makes a 10.0-amplitude sine wave reach to about 0db on the output.
- const float displayDB = 120.0;
-
- nvgBeginPath(vg);
- nvgRect(vg, 0, 0, _size.x, _size.y);
- nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
- nvgFill(vg);
- nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0xff));
- nvgStroke(vg);
+ void draw(NVGcontext* vg) override;
+ void drawBlank(NVGcontext* vg);
+ void drawYAxis(NVGcontext* vg);
+ void drawXAxis(NVGcontext* vg);
+ void drawXAxisLine(NVGcontext* vg, float hz, float maxHz);
+ void drawLine(NVGcontext* vg, float* bins, int binsN, NVGcolor color);
+ void drawBars(NVGcontext* vg, float* bins, int binsN, NVGcolor color);
- nvgBeginPath(vg);
- int lineY = _levelsInset.y + _levelHeight - _levelHeight*(displayDB - 20.0)/displayDB;
- nvgMoveTo(vg, _levelsInset.x, lineY);
- nvgLineTo(vg, _size.x, lineY);
- nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0xff));
- nvgStroke(vg);
- nvgBeginPath(vg);
- lineY = _levelsInset.y + _levelHeight - _levelHeight*(displayDB - 70.0)/displayDB;
- nvgMoveTo(vg, _levelsInset.x, lineY);
- nvgLineTo(vg, _size.x, lineY);
- nvgStrokeColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0xff));
- nvgStroke(vg);
+ int binValueToHeight(float value);
+};
- const bool lineGraph = true; // else bar graph. // FIXME: control from module.
- if (_module->_bins) {
- int n = std::min(_module->_displayBins, _levelCount) - 2;
- nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
- nvgStrokeColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
+void AnalyzerDisplay::draw(NVGcontext* vg) {
+ drawBlank(vg);
+ drawYAxis(vg);
+ drawXAxis(vg);
- if (lineGraph) {
- nvgBeginPath(vg);
+ switch(_module->_displayType) {
+ case Analyzer::DISPLAYTYPE_LINE: {
+ if (_module->_channelA) {
+ drawLine(vg, _module->_channelA->_bins, _module->_channelA->_binsN, _channelAColor);
}
- for (int i = 0; i < n; ++i) {
- float value = _module->_bins[i];
- value /= refDB0;
- value = log10(value);
- value = std::max(-5.0f, value);
- value = std::min(1.0f, value);
- value *= 20.0;
- value = (value + displayDB - 20.0) / displayDB;
-
- int height = round(_levelHeight * value);
- if (lineGraph) {
- if (i == 0) {
- nvgMoveTo(vg, _levelsInset.x, _levelsInset.y + (_levelHeight - height));
- }
- else {
- nvgLineTo(vg, _levelsInset.x + i, _levelsInset.y + (_levelHeight - height));
- }
- }
- else {
- nvgBeginPath(vg);
- nvgRect(
- vg,
- _levelsInset.x + i*(_levelWidth + _levelSpace),
- _levelsInset.y + (_levelHeight - height),
- _levelWidth,
- height
- );
- nvgFill(vg);
- }
+ if (_module->_channelB) {
+ drawLine(vg, _module->_channelB->_bins, _module->_channelB->_binsN, _channelBColor);
}
- if (lineGraph) {
- nvgStroke(vg);
+ break;
+ }
+ case Analyzer::DISPLAYTYPE_BAR: {
+ if (_module->_channelA) {
+ drawBars(vg, _module->_channelA->_bins, _module->_channelA->_binsN, _channelAColor);
}
+ if (_module->_channelB) {
+ drawBars(vg, _module->_channelB->_bins, _module->_channelB->_binsN, _channelBColor);
+ }
+ break;
}
}
-};
+}
+
+void AnalyzerDisplay::drawBlank(NVGcontext* vg) {
+ nvgSave(vg);
+ nvgBeginPath(vg);
+ nvgRect(vg, 0, 0, _size.x, _size.y);
+ nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
+ nvgFill(vg);
+ nvgStrokeColor(vg, nvgRGBA(0xc0, 0xc0, 0xc0, 0xff));
+ nvgStroke(vg);
+ nvgRestore(vg);
+}
+
+void AnalyzerDisplay::drawYAxis(NVGcontext* vg) {
+ nvgSave(vg);
+ nvgStrokeColor(vg, _axisColor);
+ const int lineX = _insetLeft - 2;
+
+ nvgBeginPath(vg);
+ int lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - 20.0)/_displayDB);
+ nvgMoveTo(vg, lineX, lineY);
+ nvgLineTo(vg, _size.x - _insetRight, lineY);
+ nvgStroke(vg);
+
+ nvgBeginPath(vg);
+ lineY = _insetTop + (_graphSize.y - _graphSize.y*(_displayDB - 70.0)/_displayDB);
+ nvgMoveTo(vg, lineX, lineY);
+ nvgLineTo(vg, _size.x - _insetRight, lineY);
+ nvgStroke(vg);
+
+ nvgBeginPath(vg);
+ lineY = _insetTop + _graphSize.y + 1;
+ nvgMoveTo(vg, lineX, lineY);
+ nvgLineTo(vg, _size.x - _insetRight, lineY);
+ nvgStroke(vg);
+
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, lineX, _insetTop);
+ nvgLineTo(vg, lineX, lineY);
+ nvgStroke(vg);
+ nvgRestore(vg);
+}
+
+void AnalyzerDisplay::drawXAxis(NVGcontext* vg) {
+ nvgSave(vg);
+ nvgStrokeColor(vg, _axisColor);
+ nvgStrokeWidth(vg, 0.8);
+
+ const float maxHz = _module->_range * (engineGetSampleRate() / 2.0);
+ float hz = 100.0;
+ while (hz < maxHz && hz < 1001.0) {
+ drawXAxisLine(vg, hz, maxHz);
+ hz += 100.0;
+ }
+ hz = 2000.0;
+ while (hz < maxHz && hz < 10001.0) {
+ drawXAxisLine(vg, hz, maxHz);
+ hz += 1000.0;
+ }
+ hz = 20000.0;
+ while (hz < maxHz && hz < 100001.0) {
+ drawXAxisLine(vg, hz, maxHz);
+ hz += 10000.0;
+ }
+
+ nvgRestore(vg);
+}
+
+void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz, float maxHz) {
+ float x = hz / maxHz;
+ x = powf(x, xAxisLogFactor);
+ if (x < 1.0) {
+ x *= _graphSize.x;
+ nvgBeginPath(vg);
+ nvgMoveTo(vg, _insetLeft + x, _insetTop);
+ nvgLineTo(vg, _insetLeft + x, _insetTop + _graphSize.y);
+ nvgStroke(vg);
+ }
+}
+
+void AnalyzerDisplay::drawLine(NVGcontext* vg, float* bins, int binsN, NVGcolor color) {
+ const int pointsN = roundf(_module->_range*(_module->_size/2));
+ nvgSave(vg);
+ nvgStrokeColor(vg, color);
+ nvgBeginPath(vg);
+ for (int i = 0; i < pointsN; ++i) {
+ int height = binValueToHeight(bins[i]);
+ if (i == 0) {
+ nvgMoveTo(vg, _insetLeft, _insetTop + (_graphSize.y - height));
+ }
+ else {
+ float x = _graphSize.x * powf(i / (float)pointsN, xAxisLogFactor);
+ nvgLineTo(vg, _insetLeft + x, _insetTop + (_graphSize.y - height));
+ }
+ }
+ nvgStroke(vg);
+ nvgRestore(vg);
+}
+
+void AnalyzerDisplay::drawBars(NVGcontext* vg, float* bins, int binsN, NVGcolor color) {
+ // nvgSave(vg);
+ // int n = std::min(_module->_displayBins, 100) - 2; // FIXME
+ // nvgFillColor(vg, color);
+ //
+ // for (int i = 0; i < n; ++i) {
+ // float value = bins[i];
+ // value /= refDB0;
+ // value = log10(value);
+ // value = std::max(-5.0f, value);
+ // value = std::min(1.0f, value);
+ // value *= 20.0;
+ // value = (value + displayDB - 20.0) / displayDB;
+ //
+ // int height = round(_levelHeight * value);
+ // nvgBeginPath(vg);
+ // nvgRect(
+ // vg,
+ // _levelsInset.x + i*(_levelWidth + _levelSpace),
+ // _levelsInset.y + (_levelHeight - height),
+ // _levelWidth,
+ // height
+ // );
+ // nvgFill(vg);
+ // nvgRestore(vg);
+ // }
+}
+
+int AnalyzerDisplay::binValueToHeight(float value) {
+ value /= _refDB0;
+ value = log10(value);
+ value = std::max(-5.0f, value);
+ value = std::min(1.0f, value);
+ value *= 20.0;
+ value = (value + _displayDB - 20.0) / _displayDB;
+ return round(_graphSize.y * value);
+}
AnalyzerWidget::AnalyzerWidget() {
@@ -199,8 +365,8 @@ AnalyzerWidget::AnalyzerWidget() {
}
{
- auto inset = Vec(10, 40);
- auto size = Vec(box.size.x - 2*inset.x, 100 + inset.y);
+ auto inset = Vec(10, 30);
+ auto size = Vec(box.size.x - 2*inset.x, 230);
auto display = new AnalyzerDisplay(module, size);
display->box.pos = inset;
display->box.size = size;
@@ -213,12 +379,26 @@ AnalyzerWidget::AnalyzerWidget() {
addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
// generated by svg_widgets.rb
- auto signalInputPosition = Vec(15.0, 324.0);
+ auto rangeParamPosition = Vec(100.08, 303.58);
+ // auto smoothParamPosition = Vec(164.08, 303.58);
+ // auto typeParamPosition = Vec(231.9, 305.4);
+ auto powerParamPosition = Vec(272.9, 305.4);
- auto signalOutputPosition = Vec(261.0, 324.0);
+ auto signalaInputPosition = Vec(27.5, 273.0);
+ auto signalbInputPosition = Vec(27.5, 323.0);
+
+ auto signalaOutputPosition = Vec(56.5, 273.0);
+ auto signalbOutputPosition = Vec(56.5, 323.0);
// end generated by svg_widgets.rb
- addInput(createInput<PJ301MPort>(signalInputPosition, module, Analyzer::SIGNAL_INPUT));
+ addParam(createParam<Knob38>(rangeParamPosition, module, Analyzer::RANGE_PARAM, 0.1, 1.0, 0.5));
+ // addParam(createParam<Knob38>(smoothParamPosition, module, Analyzer::SMOOTH_PARAM, 0.1, 1.0, 0.3));
+ // addParam(createParam<CKSS>(typeParamPosition, module, Analyzer::TYPE_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(powerParamPosition, module, Analyzer::POWER_PARAM, 0.0, 1.0, 1.0));
+
+ addInput(createInput<PJ301MPort>(signalaInputPosition, module, Analyzer::SIGNALA_INPUT));
+ addInput(createInput<PJ301MPort>(signalbInputPosition, module, Analyzer::SIGNALB_INPUT));
- addOutput(createOutput<PJ301MPort>(signalOutputPosition, module, Analyzer::SIGNAL_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(signalaOutputPosition, module, Analyzer::SIGNALA_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(signalbOutputPosition, module, Analyzer::SIGNALB_OUTPUT));
}
diff --git a/src/dsp/buffer.hpp b/src/dsp/buffer.hpp
@@ -27,7 +27,7 @@ struct OverlappingBuffer {
virtual void process(T* samples) = 0;
- bool step(T sample) {
+ virtual bool step(T sample) {
_samples[_sample++] = sample;
assert(_sample <= _samplesN);