BogaudioModules

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

commit d543b3a3fc5b4927fd9e62f66e644a168754dc83
parent 7ee4059cffce67db0131662d09525b37b736a279
Author: Matt Demanett <matt@demanett.net>
Date:   Thu, 22 Nov 2018 00:20:31 -0500

ANALYZER enhancemnts: add ULTRA quality mode; let range scroll up as well as down; drop silly power button; add window selector, with Kaiser window option, which is new default; layout tweaks; performance tweaks. #15

Diffstat:
MMakefile | 2+-
Mres-src/Analyzer-src.svg | 165+++++++++++++++++++++++++++++++++++++------------------------------------------
Mres/Analyzer.svg | 0
Msrc/Analyzer.cpp | 247++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/Analyzer.hpp | 24+++++++++++++++++++++---
Msrc/dsp/analyzer.cpp | 15+++++++++++++++
Msrc/dsp/analyzer.hpp | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
7 files changed, 348 insertions(+), 171 deletions(-)

diff --git a/Makefile b/Makefile @@ -50,7 +50,7 @@ plot: $(PLOT_OBJECTS) plotrun: plot ./plot plotrungp: plot - ./plot > plot.tmp && gnuplot -e "set yrange [-80:80]; plot 'plot.tmp' using 1:2 with lines" + ./plot > plot.tmp && gnuplot -e "set yrange [0:1.1]; plot 'plot.tmp' using 1:2 with lines" plot_clean: rm -f plot $(PLOT_OBJECTS) diff --git a/res-src/Analyzer-src.svg b/res-src/Analyzer-src.svg @@ -37,48 +37,21 @@ <symbol id="knobguide-range" viewBox="0 0 100px 100px"> <g transform="translate(50.5 50.5)"> - <g transform="rotate(-210) translate(30 0)"> - <g transform="translate(2 0) rotate(210)"> - <text font-size="7pt" transform="translate(-10 4)">0.1</text> - </g> - </g> - <g transform="rotate(-180) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(-150) translate(30 0)"> - <polyline points="0,0 5,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(-120) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(-90) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(-60) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(-30) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(0) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(30) translate(30 0)"> - <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> - </g> - <g transform="rotate(60) translate(30 0)"> - <g transform="translate(2 0) rotate(-60)"> - <text font-size="7pt" transform="translate(-5 4.5)">1.0</text> - </g> + <path d="M 0 -32 A 32 32 0 0 1 32 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(15)" /> + <path d="M 0 -32 A 32 32 0 0 1 32 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(50)" /> + <path d="M 0 -32 A 32 32 0 0 0 -32 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(-15)" /> + <path d="M 0 -32 A 32 32 0 0 0 -32 0" stroke="#333" stroke-width="0.5" stroke-linecap="round" fill="none" transform="rotate(-50)" /> + <g transform="rotate(-90) translate(29 0)"> + <polyline points="0,0 6,0" stroke-width="1.5" stroke="#333" /> </g> </g> </symbol> <symbol id="knobguide-smooth" viewBox="0 0 100px 100px"> <g transform="translate(50.5 50.5)"> - <g transform="rotate(-240) translate(30 0)"> + <g transform="rotate(-240) translate(31 0)"> <g transform="translate(2 0) rotate(240)"> - <text font-size="7pt" transform="translate(-3.2 4)">0</text> + <text font-size="7pt" transform="translate(-4 4)">0</text> </g> </g> <g transform="rotate(-210) translate(30 0)"> @@ -108,9 +81,9 @@ <g transform="rotate(30) translate(30 0)"> <polyline points="0,0 4,0" stroke-width="0.5" stroke="#333" /> </g> - <g transform="rotate(60) translate(30 0)"> + <g transform="rotate(60) translate(31 0)"> <g transform="translate(2 0) rotate(-60)"> - <text font-size="7pt" transform="translate(-7 4.3)">0.5s</text> + <text font-size="7pt" transform="translate(-5 4.3)">0.5s</text> </g> </g> </g> @@ -156,90 +129,108 @@ <use id="DISPLAY_WIDGET" xlink:href="#display" /> </g> + <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(10 0)" /> --> + <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(82.5 0)" /> --> + <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(155 0)" /> --> + <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(227 0)" /> --> + <!-- <polyline points="0,0 0,380" stroke="#0f0" stroke-width="1" fill="none" transform="translate(290 0)" /> --> + <!-- <polyline points="0,0 0,380" stroke="#f00" stroke-width="1" fill="none" transform="translate(150 0)" /> --> <g transform="translate(0 320)"> - <g transform="translate(11 0)"> - <rect width="58" height="40" rx="5" fill="#fafafa" /> - <rect width="21" height="40" rx="5" fill="#bbb" transform="translate(38)" /> - <rect width="20" height="40" fill="#bbb" transform="translate(29)" /> - <use id="SIGNALA_INPUT" xlink:href="#input" transform="translate(2.5 3)" /> - <use id="SIGNALA_OUTPUT" xlink:href="#output" transform="translate(31.5 3)" /> - <text font-size="6pt" letter-spacing="2px" transform="translate(9.5 36)">IN</text> - <text font-size="6pt" letter-spacing="2px" transform="translate(30 36)">THRU</text> + <g transform="translate(10 0)"> + <rect width="62" height="40" rx="5" fill="#fafafa" /> + <rect width="23" height="40" rx="5" fill="#bbb" transform="translate(40)" /> + <rect width="20" height="40" fill="#bbb" transform="translate(31)" /> + <use id="SIGNALA_INPUT" xlink:href="#input" transform="translate(3.5 3)" /> + <use id="SIGNALA_OUTPUT" xlink:href="#output" transform="translate(34.5 3)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(10.5 36)">IN</text> + <text font-size="6pt" letter-spacing="2px" transform="translate(33.5 36)">THRU</text> </g> - <g transform="translate(84 0)"> - <rect width="58" height="40" rx="5" fill="#fafafa" /> - <rect width="21" height="40" rx="5" fill="#bbb" transform="translate(38)" /> - <rect width="20" height="40" fill="#bbb" transform="translate(29)" /> - <use id="SIGNALB_INPUT" xlink:href="#input" transform="translate(2.5 3)" /> - <use id="SIGNALB_OUTPUT" xlink:href="#output" transform="translate(31.5 3)" /> - <text font-size="6pt" letter-spacing="2px" transform="translate(9.5 36)">IN</text> - <text font-size="6pt" letter-spacing="2px" transform="translate(30 36)">THRU</text> + <g transform="translate(82.5 0)"> + <rect width="62" height="40" rx="5" fill="#fafafa" /> + <rect width="23" height="40" rx="5" fill="#bbb" transform="translate(40)" /> + <rect width="20" height="40" fill="#bbb" transform="translate(31)" /> + <use id="SIGNALB_INPUT" xlink:href="#input" transform="translate(3.5 3)" /> + <use id="SIGNALB_OUTPUT" xlink:href="#output" transform="translate(34.5 3)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(10.5 36)">IN</text> + <text font-size="6pt" letter-spacing="2px" transform="translate(33.5 36)">THRU</text> </g> - <g transform="translate(158 0)"> - <rect width="58" height="40" rx="5" fill="#fafafa" /> - <rect width="21" height="40" rx="5" fill="#bbb" transform="translate(38)" /> - <rect width="20" height="40" fill="#bbb" transform="translate(29)" /> - <use id="SIGNALC_INPUT" xlink:href="#input" transform="translate(2.5 3)" /> - <use id="SIGNALC_OUTPUT" xlink:href="#output" transform="translate(31.5 3)" /> - <text font-size="6pt" letter-spacing="2px" transform="translate(9.5 36)">IN</text> - <text font-size="6pt" letter-spacing="2px" transform="translate(30 36)">THRU</text> + <g transform="translate(155 0)"> + <rect width="62" height="40" rx="5" fill="#fafafa" /> + <rect width="23" height="40" rx="5" fill="#bbb" transform="translate(40)" /> + <rect width="20" height="40" fill="#bbb" transform="translate(31)" /> + <use id="SIGNALC_INPUT" xlink:href="#input" transform="translate(3.5 3)" /> + <use id="SIGNALC_OUTPUT" xlink:href="#output" transform="translate(34.5 3)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(10.5 36)">IN</text> + <text font-size="6pt" letter-spacing="2px" transform="translate(33.5 36)">THRU</text> </g> - <g transform="translate(231 0)"> - <rect width="58" height="40" rx="5" fill="#fafafa" /> - <rect width="21" height="40" rx="5" fill="#bbb" transform="translate(38)" /> - <rect width="20" height="40" fill="#bbb" transform="translate(29)" /> - <use id="SIGNALD_INPUT" xlink:href="#input" transform="translate(2.5 3)" /> - <use id="SIGNALD_OUTPUT" xlink:href="#output" transform="translate(31.5 3)" /> - <text font-size="6pt" letter-spacing="2px" transform="translate(9.5 36)">IN</text> - <text font-size="6pt" letter-spacing="2px" transform="translate(30 36)">THRU</text> + <g transform="translate(227 0)"> + <rect width="62" height="40" rx="5" fill="#fafafa" /> + <rect width="23" height="40" rx="5" fill="#bbb" transform="translate(40)" /> + <rect width="20" height="40" fill="#bbb" transform="translate(31)" /> + <use id="SIGNALD_INPUT" xlink:href="#input" transform="translate(3.5 3)" /> + <use id="SIGNALD_OUTPUT" xlink:href="#output" transform="translate(34.5 3)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(10.5 36)">IN</text> + <text font-size="6pt" letter-spacing="2px" transform="translate(33.5 36)">THRU</text> </g> </g> <g transform="translate(0 271)"> - <g transform="translate(35 0)"> - <use id="RANGE_PARAM" xlink:href="#knob" transform="translate(0 0) scale(0.38)" /> + <g transform="translate(30 0)"> + <use id="RANGE2_PARAM" xlink:href="#knob" transform="translate(0 0) scale(0.38)" /> <text font-size="7pt" letter-spacing="2px" transform="rotate(-90) translate(-38 -11)">RANGE</text> </g> - <g transform="translate(109 0)"> + <g transform="translate(103 0)"> <use id="SMOOTH_PARAM" xlink:href="#knob" transform="translate(0 0) scale(0.38)" /> <text font-size="7pt" letter-spacing="2px" transform="rotate(-90) translate(-44 -11)">SMOOTH</text> </g> - <g transform="translate(182 0)"> - <g transform="translate(-3 -1)"> - <g transform="translate(0 3)"> + <g transform="translate(175 0)"> + <g transform="translate(-5 -5)"> + <g transform="translate(0 1)"> + <use id="QUALITY_ULTRA_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(9 6.1)">ULTRA</text> + </g> + <g transform="translate(0 15)"> <use id="QUALITY_HIGH_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> <text font-size="6pt" letter-spacing="2px" transform="translate(9 6.1)">HIGH</text> </g> - <g transform="translate(0 18)"> + <g transform="translate(0 29)"> <use id="QUALITY_GOOD_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> <text font-size="6pt" letter-spacing="2px" transform="translate(9 6.1)">GOOD</text> </g> - <g transform="translate(9 30)"> + <g transform="translate(9 40)"> <use id="QUALITY_PARAM" xlink:href="#button" transform="scale(0.1)" /> </g> </g> <text font-size="7pt" letter-spacing="2px" transform="rotate(-90) translate(-45 -11)">QUALITY</text> </g> - <g transform="translate(255 0)"> - <g transform="translate(-3 -1)"> - <g transform="translate(0 18)"> - <use id="POWER_ON_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> - <text font-size="6pt" letter-spacing="2px" transform="translate(9 6.1)">ON</text> + <g transform="translate(247 0)"> + <g transform="translate(-6 -5)"> + <g transform="translate(0 1)"> + <use id="WINDOW_NONE_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> + <text font-size="6pt" letter-spacing="2px" transform="translate(9 6.1)">NONE</text> + </g> + <g transform="translate(0 15)"> + <use id="WINDOW_HAMMING_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> + <text font-size="6pt" letter-spacing="0.5px" transform="translate(9 6.1)">HAMMING</text> + </g> + <g transform="translate(0 29)"> + <use id="WINDOW_KAISER_LIGHT" xlink:href="#light-small" transform="translate(0 0)" /> + <text font-size="6pt" letter-spacing="1px" transform="translate(9 6.1)">KAISER</text> </g> - <g transform="translate(9 30)"> - <use id="POWER_PARAM" xlink:href="#button" transform="scale(0.1)" /> + <g transform="translate(9 40)"> + <use id="WINDOW_PARAM" xlink:href="#button" transform="scale(0.1)" /> </g> </g> - <text font-size="7pt" letter-spacing="2px" transform="rotate(-90) translate(-40 -11)">POWER</text> + <text font-size="7pt" letter-spacing="2px" transform="rotate(-90) translate(-43.5 -11)">WINDOW</text> </g> </g> - <use xlink:href="#knobguide-range" transform=" translate(16.3 252) scale(0.75)" /> - <use xlink:href="#knobguide-smooth" transform=" translate(90.3 252) scale(0.75)" /> + <use xlink:href="#knobguide-range" transform=" translate(11.3 252) scale(0.75)" /> + <use xlink:href="#knobguide-smooth" transform=" translate(84.3 252) scale(0.75)" /> </svg> diff --git a/res/Analyzer.svg b/res/Analyzer.svg Binary files differ. diff --git a/src/Analyzer.cpp b/src/Analyzer.cpp @@ -77,10 +77,12 @@ float ChannelAnalyzer::getPeak() { void Analyzer::onReset() { + _modulationStep = modulationSteps; resetChannels(); } void Analyzer::onSampleRateChange() { + _modulationStep = modulationSteps; resetChannels(); } @@ -104,39 +106,103 @@ void Analyzer::resetChannels() { } SpectrumAnalyzer::Size Analyzer::size() { - if (_quality == QUALITY_HIGH) { - return SpectrumAnalyzer::SIZE_4096; + switch (_quality) { + case QUALITY_ULTRA: { + return SpectrumAnalyzer::SIZE_16384; + } + case QUALITY_HIGH: { + return SpectrumAnalyzer::SIZE_4096; + } + default: { + return SpectrumAnalyzer::SIZE_1024; + } } - return SpectrumAnalyzer::SIZE_1024; } -void Analyzer::step() { - _range = params[RANGE_PARAM].value; - - const float maxTime = 0.5; - float smooth = params[SMOOTH_PARAM].value * maxTime; - smooth /= size() / (_overlap * engineGetSampleRate()); - int smoothN = std::max(1, (int)roundf(smooth)); - if (_averageN != smoothN) { - _averageN = smoothN; - resetChannels(); +SpectrumAnalyzer::WindowType Analyzer::window() { + switch (_window) { + case WINDOW_NONE: { + return SpectrumAnalyzer::WINDOW_NONE; + } + case WINDOW_HAMMING: { + return SpectrumAnalyzer::WINDOW_HAMMING; + } + default: { + return SpectrumAnalyzer::WINDOW_KAISER; + } } +} + +void Analyzer::step() { + ++_modulationStep; + if (_modulationStep >= modulationSteps) { + _modulationStep = 0; + bool needResetChannels = false; + + float range = params[RANGE2_PARAM].value; + _rangeMinHz = 0.0f; + _rangeMaxHz = 0.5f * engineGetSampleRate(); + if (range < 0.0f) { + range *= 0.9f; + _rangeMaxHz *= 1.0f + range; + } + else if (range > 0.0f) { + range *= range; + range *= 0.8f; + _rangeMinHz = range * _rangeMaxHz; + } + + const float maxTime = 0.5; + float smooth = params[SMOOTH_PARAM].value * maxTime; + smooth /= size() / (_overlap * engineGetSampleRate()); + int smoothN = std::max(1, (int)roundf(smooth)); + if (_averageN != smoothN) { + _averageN = smoothN; + needResetChannels = true; + } + + Quality quality = QUALITY_GOOD; + if (params[QUALITY_PARAM].value > 2.5) { + quality = QUALITY_ULTRA; + } + else if (params[QUALITY_PARAM].value > 1.5) { + quality = QUALITY_HIGH; + } + if (_quality != quality) { + _quality = quality; + needResetChannels = true; + } + + Window window = WINDOW_KAISER; + if (params[WINDOW_PARAM].value > 2.5) { + window = WINDOW_NONE; + } + else if (params[WINDOW_PARAM].value > 1.5) { + window = WINDOW_HAMMING; + } + if (_window != window) { + _window = window; + needResetChannels = true; + } - Quality quality = params[QUALITY_PARAM].value > 1.5 ? QUALITY_HIGH : QUALITY_GOOD; - if (_quality != quality) { - _quality = quality; - resetChannels(); + if (needResetChannels) { + resetChannels(); + } + + _running = true; // params[POWER_PARAM].value == 1.0; } - _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]); stepChannel(_channelC, _running, inputs[SIGNALC_INPUT], outputs[SIGNALC_OUTPUT]); stepChannel(_channelD, _running, inputs[SIGNALD_INPUT], outputs[SIGNALD_OUTPUT]); - lights[QUALITY_HIGH_LIGHT].value = _running && quality == QUALITY_HIGH; - lights[QUALITY_GOOD_LIGHT].value = _running && quality == QUALITY_GOOD; - lights[POWER_ON_LIGHT].value = _running; + lights[QUALITY_ULTRA_LIGHT].value = _running && _quality == QUALITY_ULTRA; + lights[QUALITY_HIGH_LIGHT].value = _running && _quality == QUALITY_HIGH; + lights[QUALITY_GOOD_LIGHT].value = _running && _quality == QUALITY_GOOD; + lights[WINDOW_NONE_LIGHT].value = _running && _window == WINDOW_NONE; + lights[WINDOW_HAMMING_LIGHT].value = _running && _window == WINDOW_HAMMING; + lights[WINDOW_KAISER_LIGHT].value = _running && _window == WINDOW_KAISER; } void Analyzer::stepChannel(ChannelAnalyzer*& channelPointer, bool running, Input& input, Output& output) { @@ -145,7 +211,7 @@ void Analyzer::stepChannel(ChannelAnalyzer*& channelPointer, bool running, Input channelPointer = new ChannelAnalyzer( size(), _overlap, - SpectrumAnalyzer::WINDOW_HAMMING, + window(), engineGetSampleRate(), _averageN, _binAverageN @@ -176,7 +242,7 @@ struct AnalyzerDisplay : TransparentWidget { const float _displayDB = 80.0; const float _positiveDisplayDB = 20.0; - const float xAxisLogFactor = 1 / 3.321; // magic number. + const float baseXAxisLogFactor = 1 / 3.321; // magic number. const NVGcolor _axisColor = nvgRGBA(0xff, 0xff, 0xff, 0x70); const NVGcolor _textColor = nvgRGBA(0xff, 0xff, 0xff, 0xc0); @@ -189,6 +255,7 @@ struct AnalyzerDisplay : TransparentWidget { const Vec _size; const Vec _graphSize; std::shared_ptr<Font> _font; + float _xAxisLogFactor = baseXAxisLogFactor; AnalyzerDisplay( Analyzer* module, @@ -206,7 +273,7 @@ struct AnalyzerDisplay : TransparentWidget { void drawHeader(NVGcontext* vg); void drawYAxis(NVGcontext* vg, float strokeWidth); void drawXAxis(NVGcontext* vg, float strokeWidth); - void drawXAxisLine(NVGcontext* vg, float hz, float maxHz); + void drawXAxisLine(NVGcontext* vg, float hz); 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); @@ -216,6 +283,10 @@ void AnalyzerDisplay::draw(NVGcontext* vg) { drawBackground(vg); if (_module->_running) { float strokeWidth = std::max(1.0f, 3 - gRackScene->zoomWidget->zoom); + // _xAxisLogFactor = (_module->_rangeMaxHz - _module->_rangeMinHz) / (0.5f * engineGetSampleRate()); + // _xAxisLogFactor *= 1.0f - baseXAxisLogFactor; + // _xAxisLogFactor = 1.0f - _xAxisLogFactor; + nvgSave(vg); nvgScissor(vg, _insetAround, _insetAround, _size.x - _insetAround, _size.y - _insetAround); drawHeader(vg); @@ -360,55 +431,79 @@ void AnalyzerDisplay::drawXAxis(NVGcontext* vg, float strokeWidth) { nvgStrokeColor(vg, _axisColor); nvgStrokeWidth(vg, strokeWidth); - const float maxHz = _module->_range * (engineGetSampleRate() / 2.0); - float hz = 100.0; - while (hz < maxHz && hz < 1001.0) { - drawXAxisLine(vg, hz, maxHz); + float hz = 100.0f; + while (hz < _module->_rangeMaxHz && hz < 1001.0) { + if (hz >= _module->_rangeMinHz) { + drawXAxisLine(vg, hz); + } hz += 100.0; } hz = 2000.0; - while (hz < maxHz && hz < 10001.0) { - drawXAxisLine(vg, hz, maxHz); + while (hz < _module->_rangeMaxHz && hz < 10001.0) { + if (hz >= _module->_rangeMinHz) { + drawXAxisLine(vg, hz); + } hz += 1000.0; } hz = 20000.0; - while (hz < maxHz && hz < 100001.0) { - drawXAxisLine(vg, hz, maxHz); + while (hz < _module->_rangeMaxHz && hz < 100001.0) { + if (hz >= _module->_rangeMinHz) { + drawXAxisLine(vg, hz); + } hz += 10000.0; } drawText(vg, "Hz", _insetLeft, _size.y - 2); - { - float x = 100.0 / maxHz; - x = powf(x, xAxisLogFactor); + if (_module->_rangeMinHz <= 100.0f) { + float x = (100.0 - _module->_rangeMinHz) / (_module->_rangeMaxHz - _module->_rangeMinHz); + x = powf(x, _xAxisLogFactor); if (x < 1.0) { x *= _graphSize.x; drawText(vg, "100", _insetLeft + x - 8, _size.y - 2); } } - { - float x = 1000.0 / maxHz; - x = powf(x, xAxisLogFactor); + if (_module->_rangeMinHz <= 1000.0f) { + float x = (1000.0 - _module->_rangeMinHz) / (_module->_rangeMaxHz - _module->_rangeMinHz); + x = powf(x, _xAxisLogFactor); if (x < 1.0) { x *= _graphSize.x; drawText(vg, "1k", _insetLeft + x - 4, _size.y - 2); } } - { - float x = 10000.0 / maxHz; - x = powf(x, xAxisLogFactor); + if (_module->_rangeMinHz <= 10000.0f) { + float x = (10000.0 - _module->_rangeMinHz) / (_module->_rangeMaxHz - _module->_rangeMinHz); + x = powf(x, _xAxisLogFactor); if (x < 1.0) { x *= _graphSize.x; - drawText(vg, "10k", _insetLeft + x - 7, _size.y - 2); + drawText(vg, "10k", _insetLeft + x - 4, _size.y - 2); + } + } + if (_module->_rangeMinHz > 1000.0f) { + hz = 20000.0f; + float lastX = 0.0f; + while (hz < _module->_rangeMaxHz) { + if (_module->_rangeMinHz <= hz) { + float x = (hz - _module->_rangeMinHz) / (_module->_rangeMaxHz - _module->_rangeMinHz); + x = powf(x, _xAxisLogFactor); + if (x > lastX + 0.075f && x < 1.0f) { + lastX = x; + x *= _graphSize.x; + const int sLen = 32; + char s[sLen]; + snprintf(s, sLen, "%dk", (int)(hz / 1000.0f)); + drawText(vg, s, _insetLeft + x - 7, _size.y - 2); + } + } + hz += 10000.0f; } } nvgRestore(vg); } -void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz, float maxHz) { - float x = hz / maxHz; - x = powf(x, xAxisLogFactor); +void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz) { + float x = (hz - _module->_rangeMinHz) / (_module->_rangeMaxHz - _module->_rangeMinHz); + x = powf(x, _xAxisLogFactor); if (x < 1.0) { x *= _graphSize.x; nvgBeginPath(vg); @@ -419,19 +514,22 @@ void AnalyzerDisplay::drawXAxisLine(NVGcontext* vg, float hz, float maxHz) { } void AnalyzerDisplay::drawGraph(NVGcontext* vg, const float* bins, int binsN, NVGcolor color, float strokeWidth) { - const int pointsN = roundf(_module->_range*(_module->size()/2)); + float range = (_module->_rangeMaxHz - _module->_rangeMinHz) / (0.5f * engineGetSampleRate()); + int pointsN = roundf(range * (_module->size() / 2)); + range = _module->_rangeMinHz / (0.5f * engineGetSampleRate()); + int pointsOffset = roundf(range * (_module->size() / 2)); nvgSave(vg); nvgScissor(vg, _insetLeft, _insetTop, _graphSize.x, _graphSize.y); nvgStrokeColor(vg, color); nvgStrokeWidth(vg, strokeWidth); nvgBeginPath(vg); for (int i = 0; i < pointsN; ++i) { - int height = binValueToHeight(bins[i]); + int height = binValueToHeight(bins[pointsOffset + i]); if (i == 0) { nvgMoveTo(vg, _insetLeft, _insetTop + (_graphSize.y - height)); } else { - float x = _graphSize.x * powf(i / (float)pointsN, xAxisLogFactor); + float x = _graphSize.x * powf(i / (float)pointsN, _xAxisLogFactor); nvgLineTo(vg, _insetLeft + x, _insetTop + (_graphSize.y - height)); } } @@ -466,13 +564,6 @@ int AnalyzerDisplay::binValueToHeight(float value) { } -struct OneTenKnob : Knob38 { - OneTenKnob() : Knob38() { - minAngle = -0.664*M_PI; - } -}; - - struct AnalyzerWidget : ModuleWidget { static constexpr int hp = 20; @@ -501,34 +592,33 @@ struct AnalyzerWidget : ModuleWidget { addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365))); // generated by svg_widgets.rb - auto rangeParamPosition = Vec(35.08, 271.08); - auto smoothParamPosition = Vec(109.08, 271.08); - auto qualityParamPosition = Vec(188.02, 300.02); - auto powerParamPosition = Vec(261.02, 300.02); + auto range2ParamPosition = Vec(30.08, 271.08); + auto smoothParamPosition = Vec(103.08, 271.08); + auto qualityParamPosition = Vec(179.02, 306.02); + auto windowParamPosition = Vec(250.02, 306.02); auto signalaInputPosition = Vec(13.5, 323.0); - auto signalbInputPosition = Vec(86.5, 323.0); - auto signalcInputPosition = Vec(160.5, 323.0); - auto signaldInputPosition = Vec(233.5, 323.0); + auto signalbInputPosition = Vec(86.0, 323.0); + auto signalcInputPosition = Vec(158.5, 323.0); + auto signaldInputPosition = Vec(230.5, 323.0); - auto signalaOutputPosition = Vec(42.5, 323.0); - auto signalbOutputPosition = Vec(115.5, 323.0); + auto signalaOutputPosition = Vec(44.5, 323.0); + auto signalbOutputPosition = Vec(117.0, 323.0); auto signalcOutputPosition = Vec(189.5, 323.0); - auto signaldOutputPosition = Vec(262.5, 323.0); - - auto qualityHighLightPosition = Vec(179.0, 273.0); - auto qualityGoodLightPosition = Vec(179.0, 288.0); - auto powerOnLightPosition = Vec(252.0, 288.0); + auto signaldOutputPosition = Vec(261.5, 323.0); + + auto qualityUltraLightPosition = Vec(170.0, 267.0); + auto qualityHighLightPosition = Vec(170.0, 281.0); + auto qualityGoodLightPosition = Vec(170.0, 295.0); + auto windowNoneLightPosition = Vec(241.0, 267.0); + auto windowHammingLightPosition = Vec(241.0, 281.0); + auto windowKaiserLightPosition = Vec(241.0, 295.0); // end generated by svg_widgets.rb - addParam(ParamWidget::create<OneTenKnob>(rangeParamPosition, module, Analyzer::RANGE_PARAM, 0.1, 1.0, 0.5)); + addParam(ParamWidget::create<Knob38>(range2ParamPosition, module, Analyzer::RANGE2_PARAM, -1.0, 1.0, 0.0)); addParam(ParamWidget::create<Knob38>(smoothParamPosition, module, Analyzer::SMOOTH_PARAM, 0.0, 1.0, 0.5)); - addParam(ParamWidget::create<StatefulButton9>(qualityParamPosition, module, Analyzer::QUALITY_PARAM, 1.0, 2.0, 1.0)); - { - auto w = ParamWidget::create<StatefulButton9>(powerParamPosition, module, Analyzer::POWER_PARAM, 0.0, 1.0, 1.0); - w->randomizable = false; - addParam(w); - } + addParam(ParamWidget::create<StatefulButton9>(qualityParamPosition, module, Analyzer::QUALITY_PARAM, 1.0, 3.0, 1.0)); + addParam(ParamWidget::create<StatefulButton9>(windowParamPosition, module, Analyzer::WINDOW_PARAM, 1.0, 3.0, 1.0)); addInput(Port::create<Port24>(signalaInputPosition, Port::INPUT, module, Analyzer::SIGNALA_INPUT)); addInput(Port::create<Port24>(signalbInputPosition, Port::INPUT, module, Analyzer::SIGNALB_INPUT)); @@ -540,9 +630,12 @@ struct AnalyzerWidget : ModuleWidget { addOutput(Port::create<Port24>(signalcOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALC_OUTPUT)); addOutput(Port::create<Port24>(signaldOutputPosition, Port::OUTPUT, module, Analyzer::SIGNALD_OUTPUT)); + addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityUltraLightPosition, module, Analyzer::QUALITY_ULTRA_LIGHT)); addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityHighLightPosition, module, Analyzer::QUALITY_HIGH_LIGHT)); addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(qualityGoodLightPosition, module, Analyzer::QUALITY_GOOD_LIGHT)); - addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(powerOnLightPosition, module, Analyzer::POWER_ON_LIGHT)); + addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(windowNoneLightPosition, module, Analyzer::WINDOW_NONE_LIGHT)); + addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(windowHammingLightPosition, module, Analyzer::WINDOW_HAMMING_LIGHT)); + addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(windowKaiserLightPosition, module, Analyzer::WINDOW_KAISER_LIGHT)); } }; diff --git a/src/Analyzer.hpp b/src/Analyzer.hpp @@ -13,10 +13,12 @@ struct ChannelAnalyzer; struct Analyzer : Module { enum ParamsIds { - RANGE_PARAM, + RANGE_PARAM, // no longer used SMOOTH_PARAM, QUALITY_PARAM, - POWER_PARAM, + POWER_PARAM, // no longer used + WINDOW_PARAM, + RANGE2_PARAM, NUM_PARAMS }; @@ -40,23 +42,38 @@ struct Analyzer : Module { QUALITY_HIGH_LIGHT, QUALITY_GOOD_LIGHT, POWER_ON_LIGHT, + QUALITY_ULTRA_LIGHT, + WINDOW_NONE_LIGHT, + WINDOW_HAMMING_LIGHT, + WINDOW_KAISER_LIGHT, NUM_LIGHTS }; enum Quality { + QUALITY_ULTRA, QUALITY_HIGH, QUALITY_GOOD }; + enum Window { + WINDOW_NONE, + WINDOW_HAMMING, + WINDOW_KAISER + }; + + const int modulationSteps = 100; + int _modulationStep = 0; bool _running = false; int _averageN; ChannelAnalyzer* _channelA = NULL; ChannelAnalyzer* _channelB = NULL; ChannelAnalyzer* _channelC = NULL; ChannelAnalyzer* _channelD = NULL; - float _range = 0.0; + float _rangeMinHz = 0.0; + float _rangeMaxHz = 0.0; float _smooth = 0.0; Quality _quality = QUALITY_GOOD; + Window _window = WINDOW_KAISER; const SpectrumAnalyzer::Overlap _overlap = SpectrumAnalyzer::OVERLAP_2; const int _binAverageN = 2; @@ -71,6 +88,7 @@ struct Analyzer : Module { void onSampleRateChange() override; void resetChannels(); SpectrumAnalyzer::Size size(); + SpectrumAnalyzer::WindowType window(); void step() override; void stepChannel(ChannelAnalyzer*& channelPointer, bool running, Input& input, Output& output); }; diff --git a/src/dsp/analyzer.cpp b/src/dsp/analyzer.cpp @@ -35,3 +35,18 @@ FFT4096::~FFT4096() { void FFT4096::do_fft(float* out, float* in) { ((FIXED_FFT4096*)_fft)->do_fft(out, in); } + + +typedef ffft::FFTRealFixLen<14> FIXED_FFT16384; + +FFT16384::FFT16384() { + _fft = new FIXED_FFT16384(); +} + +FFT16384::~FFT16384() { + delete (FIXED_FFT16384*)_fft; +} + +void FFT16384::do_fft(float* out, float* in) { + ((FIXED_FFT16384*)_fft)->do_fft(out, in); +} diff --git a/src/dsp/analyzer.hpp b/src/dsp/analyzer.hpp @@ -49,6 +49,41 @@ struct HammingWindow : HanningWindow { HammingWindow(int size) : HanningWindow(size, 0.54) {} }; +struct KaiserWindow : Window { + KaiserWindow(int size, float alpha = 7.865f) : Window(size) { + float ii0a = 1.0f / i0(alpha); + float ism1 = 1.0f / (float)(size - 1); + for (int i = 0; i < _size; ++i) { + float x = i * 2.0f; + x *= ism1; + x -= 1.0f; + x *= x; + x = 1.0f - x; + x = sqrtf(x); + x *= alpha; + _sum += _window[i] = i0(x) * ii0a; + } + } + + // Rabiner, Gold: "The Theory and Application of Digital Signal Processing", 1975, page 103. + float i0(float x) { + assert(x >= 0.0f); + assert(x < 20.0f); + float y = 0.5f * x; + float t = .1e-8f; + float e = 1.0f; + float de = 1.0f; + for (int i = 1; i <= 25; ++i) { + de = de * y / (float)i; + float sde = de * de; + e += sde; + if (e * t - sde > 0.0f) { + break; + } + } + return e; + } +}; struct FFT1024 { void* _fft = NULL; @@ -58,7 +93,6 @@ struct FFT1024 { void do_fft(float* out, float* in); }; - struct FFT4096 { void* _fft = NULL; FFT4096(); @@ -67,6 +101,13 @@ struct FFT4096 { void do_fft(float* out, float* in); }; +struct FFT16384 { + void* _fft = NULL; + FFT16384(); + ~FFT16384(); + + void do_fft(float* out, float* in); +}; struct SpectrumAnalyzer : OverlappingBuffer<float> { enum Size { @@ -75,7 +116,8 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { SIZE_512 = 512, SIZE_1024 = 1024, SIZE_2048 = 2048, - SIZE_4096 = 4096 + SIZE_4096 = 4096, + SIZE_16384 = 16384 }; enum Overlap { @@ -88,13 +130,15 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { enum WindowType { WINDOW_NONE, WINDOW_HANNING, - WINDOW_HAMMING + WINDOW_HAMMING, + WINDOW_KAISER }; const float _sampleRate; ffft::FFTReal<float>* _fft; FFT1024* _fft1024; FFT4096* _fft4096; + FFT16384* _fft16384; Window* _window; float* _windowOut; float* _fftOut; @@ -110,6 +154,7 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { , _fft(NULL) , _fft1024(NULL) , _fft4096(NULL) + , _fft16384(NULL) , _window(NULL) , _windowOut(NULL) , _fftOut(new float[_size]) @@ -125,6 +170,10 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { _fft4096 = new FFT4096(); break; } + case SIZE_16384: { + _fft16384 = new FFT16384(); + break; + } default: { _fft = new ffft::FFTReal<float>(size); } @@ -144,6 +193,11 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { _windowOut = new float[size]; break; } + case WINDOW_KAISER: { + _window = new KaiserWindow(size); + _windowOut = new float[size]; + break; + } } } @@ -157,6 +211,9 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { if (_fft4096) { delete _fft4096; } + if (_fft16384) { + delete _fft16384; + } if (_window) { delete _window; @@ -178,6 +235,9 @@ struct SpectrumAnalyzer : OverlappingBuffer<float> { else if (_fft4096) { _fft4096->do_fft(_fftOut, input); } + else if (_fft16384) { + _fft16384->do_fft(_fftOut, input); + } else { _fft->do_fft(_fftOut, input); }