#include "string.h" #include "AnalyzerXL.hpp" #define RANGE_KEY "range" #define RANGE_DB_KEY "range_db" #define SMOOTH_KEY "smooth" #define QUALITY_KEY "quality" #define QUALITY_GOOD_KEY "good" #define QUALITY_HIGH_KEY "high" #define QUALITY_ULTRA_KEY "ultra" #define WINDOW_KEY "window" #define WINDOW_NONE_KEY "none" #define WINDOW_HAMMING_KEY "hamming" #define WINDOW_KAISER_KEY "kaiser" void AnalyzerXL::onReset() { _modulationStep = modulationSteps; _range = 0.0f; _smooth = 0.25f; _quality = AnalyzerCore::QUALITY_GOOD; _window = AnalyzerCore::WINDOW_KAISER; setCoreParams(); _core.resetChannels(); } void AnalyzerXL::onSampleRateChange() { _modulationStep = modulationSteps; setCoreParams(); _core.resetChannels(); } void AnalyzerXL::setCoreParams() { _rangeMinHz = 0.0f; _rangeMaxHz = 0.5f * engineGetSampleRate(); if (_range < 0.0f) { _rangeMaxHz *= 1.0f + _range; } else if (_range > 0.0f) { _rangeMinHz = _range * _rangeMaxHz; } float smooth = _smooth / (_core.size() / (_core._overlap * engineGetSampleRate())); int averageN = std::max(1, (int)roundf(smooth)); _core.setParams(averageN, _quality, _window); } json_t* AnalyzerXL::toJson() { json_t* root = json_object(); json_object_set_new(root, RANGE_KEY, json_real(_range)); json_object_set_new(root, RANGE_DB_KEY, json_real(_rangeDb)); json_object_set_new(root, SMOOTH_KEY, json_real(_smooth)); switch (_quality) { case AnalyzerCore::QUALITY_GOOD: { json_object_set_new(root, QUALITY_KEY, json_string(QUALITY_GOOD_KEY)); break; } case AnalyzerCore::QUALITY_HIGH: { json_object_set_new(root, QUALITY_KEY, json_string(QUALITY_HIGH_KEY)); break; } case AnalyzerCore::QUALITY_ULTRA: { json_object_set_new(root, QUALITY_KEY, json_string(QUALITY_ULTRA_KEY)); break; } } switch (_window) { case AnalyzerCore::WINDOW_NONE: { json_object_set_new(root, WINDOW_KEY, json_string(WINDOW_NONE_KEY)); break; } case AnalyzerCore::WINDOW_HAMMING: { json_object_set_new(root, WINDOW_KEY, json_string(WINDOW_HAMMING_KEY)); break; } case AnalyzerCore::WINDOW_KAISER: { json_object_set_new(root, WINDOW_KEY, json_string(WINDOW_KAISER_KEY)); break; } } return root; } void AnalyzerXL::fromJson(json_t* root) { json_t* jr = json_object_get(root, RANGE_KEY); if (jr) { _range = clamp(json_real_value(jr), -0.9f, 0.8f); } json_t* jrd = json_object_get(root, RANGE_DB_KEY); if (jrd) { _rangeDb = clamp(json_real_value(jrd), 80.0f, 140.0f); } json_t* js = json_object_get(root, SMOOTH_KEY); if (js) { _smooth = clamp(json_real_value(js), 0.0f, 0.5f); } json_t* jq = json_object_get(root, QUALITY_KEY); if (jq) { const char *s = json_string_value(jq); if (strcmp(s, QUALITY_GOOD_KEY) == 0) { _quality = AnalyzerCore::QUALITY_GOOD; } else if (strcmp(s, QUALITY_HIGH_KEY) == 0) { _quality = AnalyzerCore::QUALITY_HIGH; } else if (strcmp(s, QUALITY_ULTRA_KEY) == 0) { _quality = AnalyzerCore::QUALITY_ULTRA; } } json_t* jw = json_object_get(root, WINDOW_KEY); if (jw) { const char *s = json_string_value(jw); if (strcmp(s, WINDOW_NONE_KEY) == 0) { _window = AnalyzerCore::WINDOW_NONE; } else if (strcmp(s, WINDOW_HAMMING_KEY) == 0) { _window = AnalyzerCore::WINDOW_HAMMING; } else if (strcmp(s, WINDOW_KAISER_KEY) == 0) { _window = AnalyzerCore::WINDOW_KAISER; } } } void AnalyzerXL::step() { ++_modulationStep; if (_modulationStep >= modulationSteps) { _modulationStep = 0; setCoreParams(); } for (int i = 0; i < 8; ++i) { _core.stepChannel(i, inputs[SIGNALA_INPUT + i]); } } struct RangeMenuItem : MenuItem { AnalyzerXL* _module; const float _range; RangeMenuItem(AnalyzerXL* module, const char* label, float range) : _module(module) , _range(range) { this->text = label; } void onAction(EventAction &e) override { _module->_range = _range; } void step() override { rightText = _module->_range == _range ? "✔" : ""; } }; struct RangeDbMenuItem : MenuItem { AnalyzerXL* _module; const float _rangeDb; RangeDbMenuItem(AnalyzerXL* module, const char* label, float rangeDb) : _module(module) , _rangeDb(rangeDb) { this->text = label; } void onAction(EventAction &e) override { _module->_rangeDb = _rangeDb; } void step() override { rightText = _module->_rangeDb == _rangeDb ? "✔" : ""; } }; struct SmoothMenuItem : MenuItem { AnalyzerXL* _module; const float _smooth; SmoothMenuItem(AnalyzerXL* module, const char* label, float smooth) : _module(module) , _smooth(smooth) { this->text = label; } void onAction(EventAction &e) override { _module->_smooth = _smooth; } void step() override { rightText = _module->_smooth == _smooth ? "✔" : ""; } }; struct QualityMenuItem : MenuItem { AnalyzerXL* _module; const AnalyzerCore::Quality _quality; QualityMenuItem(AnalyzerXL* module, const char* label, AnalyzerCore::Quality quality) : _module(module) , _quality(quality) { this->text = label; } void onAction(EventAction &e) override { _module->_quality = _quality; } void step() override { rightText = _module->_quality == _quality ? "✔" : ""; } }; struct WindowMenuItem : MenuItem { AnalyzerXL* _module; const AnalyzerCore::Window _window; WindowMenuItem(AnalyzerXL* module, const char* label, AnalyzerCore::Window window) : _module(module) , _window(window) { this->text = label; } void onAction(EventAction &e) override { _module->_window = _window; } void step() override { rightText = _module->_window == _window ? "✔" : ""; } }; struct AnalyzerXLWidget : ModuleWidget { static constexpr int hp = 42; AnalyzerXLWidget(AnalyzerXL* module) : ModuleWidget(module) { box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/AnalyzerXL.svg"))); addChild(panel); } { auto inset = Vec(30, 1); auto size = Vec(box.size.x - inset.x - 1, 378); auto display = new AnalyzerDisplay(module, size, false); display->box.pos = inset; display->box.size = size; addChild(display); } // generated by svg_widgets.rb auto signalaInputPosition = Vec(3.0, 13.0); auto signalbInputPosition = Vec(3.0, 47.0); auto signalcInputPosition = Vec(3.0, 81.0); auto signaldInputPosition = Vec(3.0, 115.0); auto signaleInputPosition = Vec(3.0, 149.0); auto signalfInputPosition = Vec(3.0, 183.0); auto signalgInputPosition = Vec(3.0, 217.0); auto signalhInputPosition = Vec(3.0, 251.0); // end generated by svg_widgets.rb addInput(Port::create(signalaInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALA_INPUT)); addInput(Port::create(signalbInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALB_INPUT)); addInput(Port::create(signalcInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALC_INPUT)); addInput(Port::create(signaldInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALD_INPUT)); addInput(Port::create(signaleInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALE_INPUT)); addInput(Port::create(signalfInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALF_INPUT)); addInput(Port::create(signalgInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALG_INPUT)); addInput(Port::create(signalhInputPosition, Port::INPUT, module, AnalyzerXL::SIGNALH_INPUT)); } void appendContextMenu(Menu* menu) override { AnalyzerXL* a = dynamic_cast(module); assert(a); menu->addChild(new MenuLabel()); menu->addChild(new RangeMenuItem(a, "Frequency range: lower 25%", -0.75f)); menu->addChild(new RangeMenuItem(a, "Frequency range: lower 50%", -0.5f)); menu->addChild(new RangeMenuItem(a, "Frequency range: full", 0.0f)); menu->addChild(new RangeMenuItem(a, "Frequency range: upper 50%", 0.5f)); menu->addChild(new RangeMenuItem(a, "Frequency range: upper 25%", 0.75f)); menu->addChild(new MenuLabel()); menu->addChild(new RangeDbMenuItem(a, "Amplitude range: to -60dB", 80.0f)); menu->addChild(new RangeDbMenuItem(a, "Amplitude range: to -120dB", 140.0f)); menu->addChild(new MenuLabel()); menu->addChild(new SmoothMenuItem(a, "Smooth: none", 0.0f)); menu->addChild(new SmoothMenuItem(a, "Smooth: 10ms", 0.01f)); menu->addChild(new SmoothMenuItem(a, "Smooth: 50ms", 0.05f)); menu->addChild(new SmoothMenuItem(a, "Smooth: 100ms", 0.1f)); menu->addChild(new SmoothMenuItem(a, "Smooth: 250ms", 0.25f)); menu->addChild(new SmoothMenuItem(a, "Smooth: 500ms", 0.5f)); menu->addChild(new MenuLabel()); menu->addChild(new QualityMenuItem(a, "Quality: good", AnalyzerCore::QUALITY_GOOD)); menu->addChild(new QualityMenuItem(a, "Quality: high", AnalyzerCore::QUALITY_HIGH)); menu->addChild(new QualityMenuItem(a, "Quality: ultra", AnalyzerCore::QUALITY_ULTRA)); menu->addChild(new MenuLabel()); menu->addChild(new WindowMenuItem(a, "Window: Kaiser", AnalyzerCore::WINDOW_KAISER)); menu->addChild(new WindowMenuItem(a, "Window: Hamming", AnalyzerCore::WINDOW_HAMMING)); menu->addChild(new WindowMenuItem(a, "Window: none", AnalyzerCore::WINDOW_NONE)); } }; RACK_PLUGIN_MODEL_INIT(Bogaudio, AnalyzerXL) { Model *modelAnalyzerXL = createModel("Bogaudio-AnalyzerXL", "Analyzer-XL", "spectrum analyzer", VISUAL_TAG); return modelAnalyzerXL; }