Ranalyzer.cpp (13839B)
1 2 #include "Ranalyzer.hpp" 3 4 #define TRIGGER_ON_LOAD "triggerOnLoad" 5 #define DISPLAY_TRACES "display_traces" 6 #define DISPLAY_TRACES_ALL "all" 7 #define DISPLAY_TRACES_TEST_RETURN "test_return" 8 #define DISPLAY_TRACES_ANALYSIS "analysis" 9 #define WINDOW_TYPE "window_type" 10 #define WINDOW_TYPE_NONE "none" 11 #define WINDOW_TYPE_TAPER "taper" 12 #define WINDOW_TYPE_HAMMING "hamming" 13 #define WINDOW_TYPE_KAISER "Kaiser" 14 15 void Ranalyzer::reset() { 16 _trigger.reset(); 17 _triggerPulseGen.process(10.0f); 18 _eocPulseGen.process(10.0f); 19 _core.resetChannels(); 20 _chirp.reset(); 21 _run = false; 22 } 23 24 void Ranalyzer::sampleRateChange() { 25 reset(); 26 _sampleRate = APP->engine->getSampleRate(); 27 _sampleTime = 1.0f / _sampleRate; 28 _maxFrequency = roundf(maxFrequencyNyquistRatio * _sampleRate); 29 _chirp.setSampleRate(_sampleRate); 30 _rangeMinHz = 0.0f; 31 _rangeMaxHz = 0.5f * _sampleRate; 32 if (_sampleRate >= 96000.0f) { 33 _core.setParams(_sampleRate, 1, AnalyzerCore::QUALITY_FIXED_32K, AnalyzerCore::WINDOW_NONE); 34 } 35 else { 36 _core.setParams(_sampleRate, 1, AnalyzerCore::QUALITY_FIXED_16K, AnalyzerCore::WINDOW_NONE); 37 } 38 setWindow(_windowType); 39 _run = false; 40 _flush = false; 41 if (!_initialDelay) { 42 _initialDelay = new Timer(_sampleRate, initialDelaySeconds); 43 } 44 } 45 46 json_t* Ranalyzer::saveToJson(json_t* root) { 47 frequencyPlotToJson(root); 48 frequencyRangeToJson(root); 49 amplitudePlotToJson(root); 50 json_object_set_new(root, TRIGGER_ON_LOAD, json_boolean(_triggerOnLoad)); 51 52 switch (_displayTraces) { 53 case ALL_TRACES: { 54 json_object_set_new(root, DISPLAY_TRACES, json_string(DISPLAY_TRACES_ALL)); 55 break; 56 } 57 case TEST_RETURN_TRACES: { 58 json_object_set_new(root, DISPLAY_TRACES, json_string(DISPLAY_TRACES_TEST_RETURN)); 59 break; 60 } 61 case ANALYSIS_TRACES: { 62 json_object_set_new(root, DISPLAY_TRACES, json_string(DISPLAY_TRACES_ANALYSIS)); 63 break; 64 } 65 } 66 67 switch (_windowType) { 68 case NONE_WINDOW_TYPE: { 69 json_object_set_new(root, WINDOW_TYPE, json_string(WINDOW_TYPE_NONE)); 70 break; 71 } 72 case TAPER_WINDOW_TYPE: { 73 json_object_set_new(root, WINDOW_TYPE, json_string(WINDOW_TYPE_TAPER)); 74 break; 75 } 76 case HAMMING_WINDOW_TYPE: { 77 json_object_set_new(root, WINDOW_TYPE, json_string(WINDOW_TYPE_HAMMING)); 78 break; 79 } 80 case KAISER_WINDOW_TYPE: { 81 json_object_set_new(root, WINDOW_TYPE, json_string(WINDOW_TYPE_KAISER)); 82 break; 83 } 84 } 85 86 return root; 87 } 88 89 void Ranalyzer::loadFromJson(json_t* root) { 90 frequencyPlotFromJson(root); 91 frequencyRangeFromJson(root); 92 amplitudePlotFromJson(root); 93 94 json_t* t = json_object_get(root, TRIGGER_ON_LOAD); 95 if (t) { 96 _triggerOnLoad = json_boolean_value(t); 97 } 98 99 json_t* dt = json_object_get(root, DISPLAY_TRACES); 100 if (dt) { 101 std::string dts = json_string_value(dt); 102 if (dts == DISPLAY_TRACES_ALL) { 103 setDisplayTraces(ALL_TRACES); 104 } 105 else if (dts == DISPLAY_TRACES_TEST_RETURN) { 106 setDisplayTraces(TEST_RETURN_TRACES); 107 } 108 else if (dts == DISPLAY_TRACES_ANALYSIS) { 109 setDisplayTraces(ANALYSIS_TRACES); 110 } 111 } 112 113 json_t* wt = json_object_get(root, WINDOW_TYPE); 114 if (wt) { 115 std::string wts = json_string_value(wt); 116 if (wts == WINDOW_TYPE_NONE) { 117 setWindow(NONE_WINDOW_TYPE); 118 } 119 else if (wts == WINDOW_TYPE_TAPER) { 120 setWindow(TAPER_WINDOW_TYPE); 121 } 122 else if (wts == WINDOW_TYPE_HAMMING) { 123 setWindow(HAMMING_WINDOW_TYPE); 124 } 125 else if (wts == WINDOW_TYPE_KAISER) { 126 setWindow(KAISER_WINDOW_TYPE); 127 } 128 } 129 } 130 131 void Ranalyzer::modulate() { 132 _rangeMinHz = 0.0f; 133 _rangeMaxHz = 0.5f * _sampleRate; 134 if (_range < 0.0f) { 135 _rangeMaxHz *= 1.0f + _range; 136 } 137 else if (_range > 0.0f) { 138 _rangeMinHz = _range * _rangeMaxHz; 139 } 140 141 _exponential = params[EXPONENTIAL_PARAM].getValue() > 0.5f; 142 _loop = params[LOOP_PARAM].getValue() > 0.5f; 143 _returnSampleDelay = clamp((int)roundf(params[DELAY_PARAM].getValue()), 2, maxResponseDelay); 144 145 _frequency1 = clamp(params[FREQUENCY1_PARAM].getValue(), 0.0f, 1.0f); 146 _frequency1 *= _frequency1; 147 _frequency1 *= _maxFrequency - minFrequency; 148 _frequency1 += minFrequency; 149 150 _frequency2 = clamp(params[FREQUENCY2_PARAM].getValue(), 0.0f, 1.0f); 151 _frequency2 *= _frequency2; 152 _frequency2 *= _maxFrequency - minFrequency; 153 _frequency2 += minFrequency; 154 } 155 156 void Ranalyzer::processAll(const ProcessArgs& args) { 157 bool maybeTriggerOnLoad = false; 158 if (_initialDelay && !_initialDelay->next()) { 159 maybeTriggerOnLoad = true; 160 delete _initialDelay; 161 _initialDelay = NULL; 162 } 163 164 bool triggered = _trigger.process(params[TRIGGER_PARAM].getValue()*5.0f + inputs[TRIGGER_INPUT].getVoltage()); 165 if (!_run) { 166 if (triggered || (!_initialDelay && _loop) || (maybeTriggerOnLoad && _triggerOnLoad)) { 167 _run = true; 168 _outBufferCount = _currentReturnSampleDelay = _returnSampleDelay; 169 _chirp.reset(); 170 _cycleN = _core.size(); 171 _cycleI = 0; 172 _chirp.setParams(_frequency1, _frequency2, _core.size() / (double)_sampleRate, !_exponential); 173 _triggerPulseGen.trigger(0.001f); 174 _useTestInput = inputs[TEST_INPUT].isConnected(); 175 } 176 } 177 178 float out = 0.0f; 179 if (_run) { 180 if (_useTestInput) { 181 out = inputs[TEST_INPUT].getVoltage(); 182 } 183 else { 184 out = _chirp.next() * 5.0f; 185 } 186 187 _inputBuffer.push(out); 188 if (_outBufferCount > 0) { 189 --_outBufferCount; 190 } 191 else { 192 float w = _window ? _window->at(_cycleI - _currentReturnSampleDelay) : 1.0f; 193 _core.stepChannelSample(0, w * _inputBuffer.value(_currentReturnSampleDelay - 1)); 194 _core.stepChannelSample(1, w * inputs[RETURN_INPUT].getVoltage()); 195 } 196 197 ++_cycleI; 198 if (_cycleI >= _cycleN) { 199 _run = false; 200 _flush = true; 201 _analysisBufferCount = _currentReturnSampleDelay; 202 } 203 } 204 if (_flush) { 205 float w = _window ? _window->at(_cycleN - _analysisBufferCount) : 1.0f; 206 _core.stepChannelSample(0, w * _inputBuffer.value((_run ? _currentReturnSampleDelay : _analysisBufferCount) - 1)); 207 _core.stepChannelSample(1, w * inputs[RETURN_INPUT].getVoltage()); 208 --_analysisBufferCount; 209 if (_analysisBufferCount < 1) { 210 _flush = false; 211 _eocPulseGen.trigger(0.001f); 212 } 213 } 214 215 outputs[SEND_OUTPUT].setVoltage(out); 216 outputs[TRIGGER_OUTPUT].setVoltage(_triggerPulseGen.process(_sampleTime) * 5.0f); 217 outputs[EOC_OUTPUT].setVoltage(_eocPulseGen.process(_sampleTime) * 5.0f); 218 } 219 220 void Ranalyzer::setDisplayTraces(Traces traces) { 221 _displayTraces = traces; 222 if (_channelDisplayListener) { 223 switch (_displayTraces) { 224 case ALL_TRACES: { 225 _channelDisplayListener->displayChannels(true, true, true); 226 break; 227 } 228 case TEST_RETURN_TRACES: { 229 _channelDisplayListener->displayChannels(true, true, false); 230 break; 231 } 232 case ANALYSIS_TRACES: { 233 _channelDisplayListener->displayChannels(false, false, true); 234 break; 235 } 236 } 237 } 238 } 239 240 void Ranalyzer::setChannelDisplayListener(ChannelDisplayListener* listener) { 241 _channelDisplayListener = listener; 242 } 243 244 void Ranalyzer::setWindow(WindowType wt) { 245 if (!_window || _windowType != wt || _window->size() != _core.size()) { 246 if (_window) { 247 delete _window; 248 _window = NULL; 249 } 250 251 _windowType = wt; 252 switch (_windowType) { 253 case NONE_WINDOW_TYPE: break; 254 case TAPER_WINDOW_TYPE: { 255 _window = new PlanckTaperWindow(_core.size(), (int)(_core.size() * 0.03f)); 256 break; 257 } 258 case HAMMING_WINDOW_TYPE: { 259 _window = new HammingWindow(_core.size()); 260 break; 261 } 262 case KAISER_WINDOW_TYPE: { 263 _window = new KaiserWindow(_core.size()); 264 break; 265 } 266 } 267 } 268 } 269 270 271 struct AnalysisBinsReader : AnalyzerDisplay::BinsReader { 272 float* _testBins; 273 float* _responseBins; 274 275 AnalysisBinsReader(float* testBins, float* responseBins) : _testBins(testBins), _responseBins(responseBins) {} 276 277 float at(int i) override { 278 float test = AnalyzerDisplay::binValueToDb(_testBins[i]); 279 float response = AnalyzerDisplay::binValueToDb(_responseBins[i]); 280 return AnalyzerDisplay::dbToBinValue(response - test); 281 } 282 283 static std::unique_ptr<BinsReader> factory(AnalyzerCore& core) { 284 assert(core._nChannels == 3); 285 return std::unique_ptr<BinsReader>(new AnalysisBinsReader(core.getBins(0), core.getBins(1))); 286 } 287 }; 288 289 290 struct RanalyzerDisplay : AnalyzerDisplay, ChannelDisplayListener { 291 RanalyzerDisplay(Ranalyzer* module, Vec size, bool drawInset) 292 : AnalyzerDisplay(module, size, drawInset) 293 {} 294 295 void displayChannels(bool c0, bool c1, bool c2) override { 296 displayChannel(0, c0); 297 displayChannel(1, c1); 298 displayChannel(2, c2); 299 } 300 301 void drawHeader(const DrawArgs& args, float rangeMinHz, float rangeMaxHz) override { 302 nvgSave(args.vg); 303 304 const int textY = -4; 305 const int charPx = 5; 306 int x = _insetAround + 2; 307 308 std::string s = format("Bin width %0.1f HZ", APP->engine->getSampleRate() / (float)(_module->_core._size / _module->_core._binAverageN)); 309 drawText(args, s.c_str(), x, _insetTop + textY); 310 x += s.size() * charPx + 20; 311 312 const char* labels[3] = { "TEST", "RESPONSE", "ANALYSIS" }; 313 for (int i = 0; i < 3; ++i) { 314 if (_displayChannel[i]) { 315 auto color = _channelColors[i % channelColorsN]; 316 nvgStrokeColor(args.vg, color); 317 nvgStrokeWidth(args.vg, std::max(1.0f, 3.0f - getZoom())); 318 nvgBeginPath(args.vg); 319 float lineY = _insetTop - 7.0f; 320 nvgMoveTo(args.vg, x, lineY); 321 x += 10.0f; 322 nvgLineTo(args.vg, x, lineY); 323 x += 3.0f; 324 nvgStroke(args.vg); 325 326 drawText(args, labels[i], x, _insetTop + textY, 0.0, &color); 327 x += strlen(labels[i]) * charPx + 20; 328 } 329 } 330 331 nvgRestore(args.vg); 332 } 333 }; 334 335 336 struct RanalyzerWidget : AnalyzerBaseWidget { 337 static constexpr int hp = 45; 338 339 RanalyzerWidget(Ranalyzer* module) { 340 setModule(module); 341 box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); 342 setPanel(box.size, "Ranalyzer", false); 343 344 { 345 auto inset = Vec(75, 1); 346 auto size = Vec(box.size.x - inset.x - 1, 378); 347 auto display = new RanalyzerDisplay(module, size, false); 348 display->box.pos = inset; 349 display->box.size = size; 350 if (module) { 351 display->setChannelBinsReaderFactory(2, AnalysisBinsReader::factory); 352 module->setChannelDisplayListener(display); 353 display->channelLabel(0, "Test"); 354 display->channelLabel(1, "Response"); 355 display->channelLabel(2, "Analysis"); 356 } 357 addChild(display); 358 } 359 360 // generated by svg_widgets.rb 361 auto frequency1ParamPosition = Vec(24.5, 42.0); 362 auto frequency2ParamPosition = Vec(24.5, 103.5); 363 auto triggerParamPosition = Vec(18.0, 154.0); 364 auto exponentialParamPosition = Vec(23.0, 213.0); 365 auto loopParamPosition = Vec(62.0, 213.0); 366 auto delayParamPosition = Vec(29.5, 249.5); 367 368 auto triggerInputPosition = Vec(40.5, 151.0); 369 auto testInputPosition = Vec(30.5, 181.0); 370 auto returnInputPosition = Vec(40.5, 323.0); 371 372 auto triggerOutputPosition = Vec(10.5, 286.0); 373 auto eocOutputPosition = Vec(40.5, 286.0); 374 auto sendOutputPosition = Vec(10.5, 323.0); 375 // end generated by svg_widgets.rb 376 377 { 378 auto w = createParam<Knob26>(frequency1ParamPosition, module, Ranalyzer::FREQUENCY1_PARAM); 379 auto k = dynamic_cast<BGKnob*>(w); 380 k->skinChanged("dark"); 381 addParam(w); 382 } 383 { 384 auto w = createParam<Knob26>(frequency2ParamPosition, module, Ranalyzer::FREQUENCY2_PARAM); 385 auto k = dynamic_cast<BGKnob*>(w); 386 k->skinChanged("dark"); 387 addParam(w); 388 } 389 addParam(createParam<Button18>(triggerParamPosition, module, Ranalyzer::TRIGGER_PARAM)); 390 addParam(createParam<IndicatorButtonGreen9>(exponentialParamPosition, module, Ranalyzer::EXPONENTIAL_PARAM)); 391 addParam(createParam<IndicatorButtonGreen9>(loopParamPosition, module, Ranalyzer::LOOP_PARAM)); 392 addParam(createParam<Knob16>(delayParamPosition, module, Ranalyzer::DELAY_PARAM)); 393 394 addInput(createInput<Port24>(triggerInputPosition, module, Ranalyzer::TRIGGER_INPUT)); 395 addInput(createInput<Port24>(testInputPosition, module, Ranalyzer::TEST_INPUT)); 396 addInput(createInput<Port24>(returnInputPosition, module, Ranalyzer::RETURN_INPUT)); 397 398 addOutput(createOutput<Port24>(triggerOutputPosition, module, Ranalyzer::TRIGGER_OUTPUT)); 399 addOutput(createOutput<Port24>(eocOutputPosition, module, Ranalyzer::EOC_OUTPUT)); 400 addOutput(createOutput<Port24>(sendOutputPosition, module, Ranalyzer::SEND_OUTPUT)); 401 } 402 403 void contextMenu(Menu* menu) override { 404 auto a = dynamic_cast<Ranalyzer*>(module); 405 assert(a); 406 407 menu->addChild(new MenuLabel()); 408 { 409 OptionsMenuItem* mi = new OptionsMenuItem("Display traces"); 410 mi->addItem(OptionMenuItem("All", [a]() { return a->_displayTraces == Ranalyzer::ALL_TRACES; }, [a]() { a->setDisplayTraces(Ranalyzer::ALL_TRACES); })); 411 mi->addItem(OptionMenuItem("Analysis only", [a]() { return a->_displayTraces == Ranalyzer::ANALYSIS_TRACES; }, [a]() { a->setDisplayTraces(Ranalyzer::ANALYSIS_TRACES); })); 412 mi->addItem(OptionMenuItem("Test/return only", [a]() { return a->_displayTraces == Ranalyzer::TEST_RETURN_TRACES; }, [a]() { a->setDisplayTraces(Ranalyzer::TEST_RETURN_TRACES); })); 413 OptionsMenuItem::addToMenu(mi, menu); 414 } 415 { 416 OptionsMenuItem* mi = new OptionsMenuItem("Window"); 417 mi->addItem(OptionMenuItem("None", [a]() { return a->_windowType == Ranalyzer::NONE_WINDOW_TYPE; }, [a]() { a->setWindow(Ranalyzer::NONE_WINDOW_TYPE); })); 418 mi->addItem(OptionMenuItem("Taper", [a]() { return a->_windowType == Ranalyzer::TAPER_WINDOW_TYPE; }, [a]() { a->setWindow(Ranalyzer::TAPER_WINDOW_TYPE); })); 419 mi->addItem(OptionMenuItem("Hamming", [a]() { return a->_windowType == Ranalyzer::HAMMING_WINDOW_TYPE; }, [a]() { a->setWindow(Ranalyzer::HAMMING_WINDOW_TYPE); })); 420 mi->addItem(OptionMenuItem("Kaiser", [a]() { return a->_windowType == Ranalyzer::KAISER_WINDOW_TYPE; }, [a]() { a->setWindow(Ranalyzer::KAISER_WINDOW_TYPE); })); 421 OptionsMenuItem::addToMenu(mi, menu); 422 } 423 addFrequencyPlotContextMenu(menu); 424 addFrequencyRangeContextMenu(menu); 425 addAmplitudePlotContextMenu(menu, false); 426 menu->addChild(new BoolOptionMenuItem("Trigger on load", [a]() { return &a->_triggerOnLoad; })); 427 } 428 }; 429 430 Model* modelRanalyzer = createModel<Ranalyzer, RanalyzerWidget>("Bogaudio-Ranalyzer", "RANALYZER", "Swept-sine frequency response analyzer", "Visual");