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:
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);
}