BogaudioModules

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

commit 5e2d23fe43a76d088795cd0220191ff31572e811
parent 8b8f99140270f2d921a4c5c54ee779379819f85c
Author: Matt Demanett <matt@demanett.net>
Date:   Wed, 16 Sep 2020 23:09:27 -0400

RANALYZER: add back analysis trace, as (response - test), plotted in decibels; replace "peaks" header with lables for traces; add trigger-on-load option; fix buffer flush in loop mode. #116

Diffstat:
Msrc/Ranalyzer.cpp | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/Ranalyzer.hpp | 19+++++++++++++++++--
Msrc/analyzer_base.cpp | 12++++++++++++
Msrc/analyzer_base.hpp | 4+++-
4 files changed, 129 insertions(+), 35 deletions(-)

diff --git a/src/Ranalyzer.cpp b/src/Ranalyzer.cpp @@ -1,11 +1,12 @@ #include "Ranalyzer.hpp" -// #define ANALYSIS_TRACE 1 - #define RANGE_KEY "range" #define RANGE_DB_KEY "range_db" -#define DISPLAY_ALL "display_all" +#define TRIGGER_ON_LOAD "triggerOnLoad" +#define DISPLAY_TRACES "display_traces" +#define DISPLAY_TRACES_TEST_RETURN "test_return" +#define DISPLAY_TRACES_ANALYSIS "analysis" void Ranalyzer::reset() { _trigger.reset(); @@ -33,9 +34,19 @@ void Ranalyzer::sampleRateChange() { json_t* Ranalyzer::toJson(json_t* root) { json_object_set_new(root, RANGE_KEY, json_real(_range)); json_object_set_new(root, RANGE_DB_KEY, json_real(_rangeDb)); -#ifdef ANALYSIS_TRACE - json_object_set_new(root, DISPLAY_ALL, json_boolean(_displayAll)); -#endif + json_object_set_new(root, TRIGGER_ON_LOAD, json_boolean(_triggerOnLoad)); + + switch (_displayTraces) { + case ALL_TRACES: break; + case TEST_RETURN_TRACES: { + json_object_set_new(root, DISPLAY_TRACES, json_string(DISPLAY_TRACES_TEST_RETURN)); + break; + } + case ANALYSIS_TRACES: { + json_object_set_new(root, DISPLAY_TRACES, json_string(DISPLAY_TRACES_ANALYSIS)); + break; + } + } return root; } @@ -50,12 +61,21 @@ void Ranalyzer::fromJson(json_t* root) { _rangeDb = clamp(json_real_value(jrd), 80.0f, 140.0f); } -#ifdef ANALYSIS_TRACE - json_t* da = json_object_get(root, DISPLAY_ALL); - if (da) { - setDisplayAll(json_boolean_value(da)); + json_t* t = json_object_get(root, TRIGGER_ON_LOAD); + if (t) { + _triggerOnLoad = json_boolean_value(t); + } + + json_t* dt = json_object_get(root, DISPLAY_TRACES); + if (dt) { + std::string dts = json_string_value(dt); + if (dts == DISPLAY_TRACES_TEST_RETURN) { + setDisplayTraces(TEST_RETURN_TRACES); + } + else if (dts == DISPLAY_TRACES_ANALYSIS) { + setDisplayTraces(ANALYSIS_TRACES); + } } -#endif } void Ranalyzer::modulate() { @@ -84,9 +104,16 @@ void Ranalyzer::modulate() { } void Ranalyzer::processAll(const ProcessArgs& args) { + bool maybeTriggerOnLoad = false; + if (_initialDelay && !_initialDelay->next()) { + maybeTriggerOnLoad = true; + delete _initialDelay; + _initialDelay = NULL; + } + bool triggered = _trigger.process(params[TRIGGER_PARAM].getValue()*5.0f + inputs[TRIGGER_INPUT].getVoltage()); - if (!_run) { - if (triggered || _loop) { + if (!_run && !_flush) { + if (triggered || _loop || (maybeTriggerOnLoad && _triggerOnLoad)) { _run = true; _bufferCount = _currentReturnSampleDelay = _returnSampleDelay; _chirp.reset(); @@ -133,14 +160,22 @@ void Ranalyzer::processAll(const ProcessArgs& args) { outputs[EOC_OUTPUT].setVoltage(_eocPulseGen.process(_sampleTime) * 5.0f); } -void Ranalyzer::setDisplayAll(bool displayAll) { - _displayAll = displayAll; +void Ranalyzer::setDisplayTraces(Traces traces) { + _displayTraces = traces; if (_channelDisplayListener) { - if (_displayAll) { - _channelDisplayListener->displayChannels(true, true, true); - } - else { - _channelDisplayListener->displayChannels(false, false, true); + switch (_displayTraces) { + case ALL_TRACES: { + _channelDisplayListener->displayChannels(true, true, true); + break; + } + case TEST_RETURN_TRACES: { + _channelDisplayListener->displayChannels(true, true, false); + break; + } + case ANALYSIS_TRACES: { + _channelDisplayListener->displayChannels(false, false, true); + break; + } } } } @@ -156,12 +191,9 @@ struct AnalysisBinsReader : AnalyzerDisplay::BinsReader { float at(int i) override { assert(_base->_core._nChannels == 3); - float test = _base->_core.getBins(0)[i]; - float response = _base->_core.getBins(1)[i]; - if (test > 0.0001f) { - return response / test; - } - return response; + float test = AnalyzerDisplay::binValueToDb(_base->_core.getBins(0)[i]); + float response = AnalyzerDisplay::binValueToDb(_base->_core.getBins(1)[i]); + return AnalyzerDisplay::dbToBinValue(response - test); } }; @@ -176,6 +208,41 @@ struct RanalyzerDisplay : AnalyzerDisplay, ChannelDisplayListener { displayChannel(1, c1); displayChannel(2, c2); } + + void drawHeader(const DrawArgs& args) override { + nvgSave(args.vg); + + const int textY = -4; + const int charPx = 5; + const int sLen = 100; + char s[sLen]; + int x = _insetAround + 2; + + int n = snprintf(s, sLen, "Bin width %0.1f HZ", APP->engine->getSampleRate() / (float)(_module->_core._size / _module->_core._binAverageN)); + drawText(args, s, x, _insetTop + textY); + x += n * charPx + 20; + + const char* labels[3] = { "TEST", "RESPONSE", "ANALYSIS" }; + for (int i = 0; i < 3; ++i) { + auto color = _channelColors[i % channelColorsN]; + nvgStrokeColor(args.vg, color); + nvgStrokeWidth(args.vg, std::max(1.0f, 3.0f - getZoom())); + nvgBeginPath(args.vg); + float lineY = _insetTop - 7.0f; + nvgMoveTo(args.vg, x, lineY); + x += 10.0f; + nvgLineTo(args.vg, x, lineY); + x += 3.0f; + nvgStroke(args.vg); + + if (_displayChannel[i]) { + drawText(args, labels[i], x, _insetTop + textY, 0.0, &color); + } + x += strlen(labels[i]) * charPx + 20; + } + + nvgRestore(args.vg); + } }; @@ -193,12 +260,10 @@ struct RanalyzerWidget : BGModuleWidget { auto display = new RanalyzerDisplay(module, size, false); display->box.pos = inset; display->box.size = size; -#ifdef ANALYSIS_TRACE if (module) { display->setChannelBinsReader(2, new AnalysisBinsReader(module)); module->setChannelDisplayListener(display); } -#endif addChild(display); } @@ -270,14 +335,14 @@ struct RanalyzerWidget : BGModuleWidget { mi->addItem(OptionMenuItem("To -120dB", [a]() { return a->_rangeDb == 140.0f; }, [a]() { a->_rangeDb = 140.0f; })); OptionsMenuItem::addToMenu(mi, menu); } -#ifdef ANALYSIS_TRACE { OptionsMenuItem* mi = new OptionsMenuItem("Display traces"); - mi->addItem(OptionMenuItem("All", [a]() { return a->_displayAll; }, [a]() { a->setDisplayAll(true); })); - mi->addItem(OptionMenuItem("Analysis only", [a]() { return !a->_displayAll; }, [a]() { a->setDisplayAll(false); })); + mi->addItem(OptionMenuItem("All", [a]() { return a->_displayTraces == Ranalyzer::ALL_TRACES; }, [a]() { a->setDisplayTraces(Ranalyzer::ALL_TRACES); })); + mi->addItem(OptionMenuItem("Analysis only", [a]() { return a->_displayTraces == Ranalyzer::ANALYSIS_TRACES; }, [a]() { a->setDisplayTraces(Ranalyzer::ANALYSIS_TRACES); })); + mi->addItem(OptionMenuItem("Test/return only", [a]() { return a->_displayTraces == Ranalyzer::TEST_RETURN_TRACES; }, [a]() { a->setDisplayTraces(Ranalyzer::TEST_RETURN_TRACES); })); OptionsMenuItem::addToMenu(mi, menu); } -#endif + menu->addChild(new BoolOptionMenuItem("Trigger on load", [a]() { return &a->_triggerOnLoad; })); } }; diff --git a/src/Ranalyzer.hpp b/src/Ranalyzer.hpp @@ -3,6 +3,7 @@ #include "bogaudio.hpp" #include "analyzer_base.hpp" #include "dsp/oscillator.hpp" +#include "dsp/signal.hpp" extern Model* modelRanalyzer; @@ -39,6 +40,12 @@ struct Ranalyzer : AnalyzerBase { NUM_OUTPUTS }; + enum Traces { + ALL_TRACES, + TEST_RETURN_TRACES, + ANALYSIS_TRACES + }; + static constexpr float minFrequency = 1.0f; static constexpr float maxFrequencyNyquistRatio = 0.49f; static constexpr int maxResponseDelay = 20; @@ -86,8 +93,10 @@ struct Ranalyzer : AnalyzerBase { int _bufferCount = 0; HistoryBuffer<float> _inputBuffer; float _range = 0.0f; - bool _displayAll = true; + Traces _displayTraces = ALL_TRACES; ChannelDisplayListener* _channelDisplayListener = NULL; + bool _triggerOnLoad = true; + Timer* _initialDelay = NULL; Ranalyzer() : AnalyzerBase(3, NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) @@ -101,6 +110,12 @@ struct Ranalyzer : AnalyzerBase { configParam(DELAY_PARAM, 2.0f, (float)maxResponseDelay, 2.0f, "Return sample delay"); _skinnable = false; + _initialDelay = new Timer(APP->engine->getSampleRate(), 0.01f); + } + virtual ~Ranalyzer() { + if (_initialDelay) { + delete _initialDelay; + } } void reset() override; @@ -109,7 +124,7 @@ struct Ranalyzer : AnalyzerBase { void fromJson(json_t* root) override; void modulate() override; void processAll(const ProcessArgs& args) override; - void setDisplayAll(bool displayAll); + void setDisplayTraces(Traces traces); void setChannelDisplayListener(ChannelDisplayListener* listener); }; diff --git a/src/analyzer_base.cpp b/src/analyzer_base.cpp @@ -597,3 +597,15 @@ int AnalyzerDisplay::binValueToHeight(float value, float rangeDb) { value /= rangeDb; return roundf(_graphSize.y * value); } + +float AnalyzerDisplay::binValueToDb(float value) { + value /= 10.0f; // arbitrarily use 5.0f as reference "maximum" baseline signal (e.g. raw output of an oscillator)...but signals are +/-5, so 10 total. + value = powf(value, 0.5f); // undoing magnitude scaling of levels back from the FFT? + return amplitudeToDecibels(value); +} + +float AnalyzerDisplay::dbToBinValue(float db) { + db = decibelsToAmplitude(db); + db *= db; + return db * 10.0f; +} diff --git a/src/analyzer_base.hpp b/src/analyzer_base.hpp @@ -226,13 +226,15 @@ struct AnalyzerDisplay : TransparentWidget { void displayChannel(int channel, bool display); void draw(const DrawArgs& args) override; void drawBackground(const DrawArgs& args); - void drawHeader(const DrawArgs& args); + virtual void drawHeader(const DrawArgs& args); void drawYAxis(const DrawArgs& args, float strokeWidth, float rangeDb); void drawXAxis(const DrawArgs& args, float strokeWidth, float rangeMinHz, float rangeMaxHz); void drawXAxisLine(const DrawArgs& args, float hz, float rangeMinHz, float rangeMaxHz); void drawGraph(const DrawArgs& args, BinsReader& bins, NVGcolor color, float strokeWidth, float rangeMinHz, float rangeMaxHz, float rangeDb); void drawText(const DrawArgs& args, const char* s, float x, float y, float rotation = 0.0, const NVGcolor* color = NULL); int binValueToHeight(float value, float rangeDb); + static float binValueToDb(float value); + static float dbToBinValue(float db); }; } // namespace bogaudio