analyzer_base.cpp (30857B)
1 2 #include "analyzer_base.hpp" 3 #include "dsp/signal.hpp" 4 #include <vector> 5 6 ChannelAnalyzer::~ChannelAnalyzer() { 7 { 8 std::lock_guard<std::mutex> lock(_workerMutex); 9 _workerStop = true; 10 } 11 _workerCV.notify_one(); 12 _worker.join(); 13 delete[] _workerBuf; 14 delete[] _stepBuf; 15 if (_averagedBins) { 16 delete _averagedBins; 17 } 18 } 19 20 void ChannelAnalyzer::step(float sample) { 21 _stepBuf[_stepBufI++] = sample; 22 if (_stepBufI >= _stepBufN) { 23 _stepBufI = 0; 24 25 { 26 std::lock_guard<std::mutex> lock(_workerMutex); 27 for (int i = 0; i < _stepBufN; ++i) { 28 _workerBuf[_workerBufWriteI] = _stepBuf[i]; 29 _workerBufWriteI = (_workerBufWriteI + 1) % _workerBufN; 30 if (_workerBufWriteI == _workerBufReadI) { 31 _workerBufWriteI = _workerBufReadI = 0; 32 break; 33 } 34 } 35 } 36 _workerCV.notify_one(); 37 } 38 } 39 40 void ChannelAnalyzer::work() { 41 bool process = false; 42 MAIN: while (true) { 43 if (_workerStop) { 44 return; 45 } 46 47 if (process) { 48 process = false; 49 50 _analyzer.process(); 51 _analyzer.postProcess(); 52 float* bins = _bins0; 53 if (_currentBins == _bins0) { 54 bins = _bins1; 55 } 56 if (_averagedBins) { 57 float* frame = _averagedBins->getInputFrame(); 58 _analyzer.getMagnitudes(frame, _binsN); 59 _averagedBins->commitInputFrame(); 60 const float* averages = _averagedBins->getAverages(); 61 std::copy(averages, averages + _binsN, bins); 62 } 63 else { 64 _analyzer.getMagnitudes(bins, _binsN); 65 } 66 _currentBins = bins; 67 _currentOutBuf = _currentBins; 68 } 69 70 while (_workerBufReadI != _workerBufWriteI) { 71 float sample = _workerBuf[_workerBufReadI]; 72 _workerBufReadI = (_workerBufReadI + 1) % _workerBufN; 73 if (_analyzer.step(sample)) { 74 process = true; 75 goto MAIN; 76 } 77 } 78 79 std::unique_lock<std::mutex> lock(_workerMutex); 80 while (!(_workerBufReadI != _workerBufWriteI || _workerStop)) { 81 _workerCV.wait(lock); 82 } 83 } 84 } 85 86 87 void AnalyzerCore::setParams(float sampleRate, int averageN, Quality quality, Window window) { 88 std::lock_guard<std::mutex> lock(_channelsMutex); 89 90 bool reset = false; 91 if (_sampleRate != sampleRate) { 92 _sampleRate = sampleRate; 93 reset = true; 94 } 95 if (_averageN != averageN) { 96 _averageN = averageN; 97 reset = true; 98 } 99 if (_quality != quality) { 100 _quality = quality; 101 reset = true; 102 } 103 if (_window != window) { 104 _window = window; 105 reset = true; 106 } 107 if (reset) { 108 resetChannelsLocked(); 109 } 110 } 111 112 void AnalyzerCore::resetChannels() { 113 std::lock_guard<std::mutex> lock(_channelsMutex); 114 resetChannelsLocked(); 115 } 116 117 void AnalyzerCore::resetChannelsLocked() { 118 _size = size(); 119 _binsN = _size / _binAverageN; 120 121 for (int i = 0; i < _nChannels; ++i) { 122 if (_channels[i]) { 123 delete _channels[i]; 124 _channels[i] = NULL; 125 } 126 } 127 } 128 129 SpectrumAnalyzer::Size AnalyzerCore::size() { 130 switch (_quality) { 131 case QUALITY_FIXED_16K: { 132 return SpectrumAnalyzer::SIZE_16384; 133 } 134 case QUALITY_FIXED_32K: { 135 return SpectrumAnalyzer::SIZE_32768; 136 } 137 default:; 138 } 139 140 if (_sampleRate < 96000.0f) { 141 switch (_quality) { 142 case QUALITY_ULTRA_ULTRA: { 143 return SpectrumAnalyzer::SIZE_16384; 144 } 145 case QUALITY_ULTRA: { 146 return SpectrumAnalyzer::SIZE_8192; 147 } 148 case QUALITY_HIGH: { 149 return SpectrumAnalyzer::SIZE_4096; 150 } 151 default: { 152 return SpectrumAnalyzer::SIZE_1024; 153 } 154 } 155 } 156 else { 157 switch (_quality) { 158 case QUALITY_ULTRA_ULTRA: { 159 return SpectrumAnalyzer::SIZE_32768; 160 } 161 case QUALITY_ULTRA: { 162 return SpectrumAnalyzer::SIZE_16384; 163 } 164 case QUALITY_HIGH: { 165 return SpectrumAnalyzer::SIZE_8192; 166 } 167 default: { 168 return SpectrumAnalyzer::SIZE_2048; 169 } 170 } 171 } 172 } 173 174 SpectrumAnalyzer::WindowType AnalyzerCore::window() { 175 switch (_window) { 176 case WINDOW_NONE: { 177 return SpectrumAnalyzer::WINDOW_NONE; 178 } 179 case WINDOW_HAMMING: { 180 return SpectrumAnalyzer::WINDOW_HAMMING; 181 } 182 default: { 183 return SpectrumAnalyzer::WINDOW_KAISER; 184 } 185 } 186 } 187 188 float AnalyzerCore::getPeak(int channel, float minHz, float maxHz) { 189 assert(channel >= 0 && channel < _nChannels); 190 const float* bins = getBins(channel); 191 const int bandsPerBin = _size / _binsN; 192 const float fWidth = (_sampleRate / 2.0f) / (float)(_size / bandsPerBin); 193 float max = 0.0f; 194 int maxBin = 0; 195 int i = std::max(0, (int)(minHz / fWidth)); 196 int n = std::min(_binsN, 1 + (int)(maxHz / fWidth)); 197 for (; i < n; ++i) { 198 if (bins[i] > max) { 199 max = bins[i]; 200 maxBin = i; 201 } 202 } 203 return (maxBin + 0.5f)*fWidth; 204 } 205 206 void AnalyzerCore::stepChannel(int channelIndex, Input& input) { 207 assert(channelIndex >= 0); 208 assert(channelIndex < _nChannels); 209 210 if (input.isConnected()) { 211 stepChannelSample(channelIndex, input.getVoltageSum()); 212 } 213 else if (_channels[channelIndex]) { 214 std::lock_guard<std::mutex> lock(_channelsMutex); 215 delete _channels[channelIndex]; 216 _channels[channelIndex] = NULL; 217 } 218 } 219 220 void AnalyzerCore::stepChannelSample(int channelIndex, float sample) { 221 assert(channelIndex >= 0); 222 assert(channelIndex < _nChannels); 223 224 if (!_channels[channelIndex]) { 225 std::lock_guard<std::mutex> lock(_channelsMutex); 226 _channels[channelIndex] = new ChannelAnalyzer( 227 _size, 228 _overlap, 229 window(), 230 _sampleRate, 231 _averageN, 232 _binAverageN, 233 _outBufs + 2 * channelIndex * _outBufferN, 234 _outBufs + (2 * channelIndex + 1) * _outBufferN, 235 _currentOutBufs[channelIndex] 236 ); 237 } 238 _channels[channelIndex]->step(sample); 239 } 240 241 242 #define FREQUENCY_PLOT_KEY "frequency_plot" 243 #define FREQUENCY_PLOT_LOG_KEY "log" 244 #define FREQUENCY_PLOT_LINEAR_KEY "linear" 245 246 void AnalyzerBase::frequencyPlotToJson(json_t* root) { 247 switch (_frequencyPlot) { 248 case LOG_FP: { 249 json_object_set_new(root, FREQUENCY_PLOT_KEY, json_string(FREQUENCY_PLOT_LOG_KEY)); 250 break; 251 } 252 case LINEAR_FP: { 253 json_object_set_new(root, FREQUENCY_PLOT_KEY, json_string(FREQUENCY_PLOT_LINEAR_KEY)); 254 break; 255 } 256 } 257 } 258 259 void AnalyzerBase::frequencyPlotFromJson(json_t* root) { 260 json_t* fp = json_object_get(root, FREQUENCY_PLOT_KEY); 261 if (fp) { 262 std::string fps = json_string_value(fp); 263 if (fps == FREQUENCY_PLOT_LOG_KEY) { 264 _frequencyPlot = LOG_FP; 265 } 266 else if (fps == FREQUENCY_PLOT_LINEAR_KEY) { 267 _frequencyPlot = LINEAR_FP; 268 } 269 } 270 } 271 272 #define RANGE_KEY "range" 273 274 void AnalyzerBase::frequencyRangeToJson(json_t* root) { 275 json_object_set_new(root, RANGE_KEY, json_real(_range)); 276 } 277 278 void AnalyzerBase::frequencyRangeFromJson(json_t* root) { 279 json_t* jr = json_object_get(root, RANGE_KEY); 280 if (jr) { 281 _range = clamp(json_real_value(jr), -0.9f, 0.8f); 282 } 283 } 284 285 #define RANGE_DB_KEY "range_db" 286 #define AMPLITUDE_PLOT_KEY "amplitude_plot" 287 #define AMPLITUDE_PLOT_DECIBLES_80_KEY "decibels_80" 288 #define AMPLITUDE_PLOT_DECIBLES_140_KEY "decibels_140" 289 #define AMPLITUDE_PLOT_PERCENTAGE_KEY "percentage" 290 291 void AnalyzerBase::amplitudePlotToJson(json_t* root) { 292 switch (_amplitudePlot) { 293 case DECIBELS_80_AP: { 294 json_object_set_new(root, AMPLITUDE_PLOT_KEY, json_string(AMPLITUDE_PLOT_DECIBLES_80_KEY)); 295 break; 296 } 297 case DECIBELS_140_AP: { 298 json_object_set_new(root, AMPLITUDE_PLOT_KEY, json_string(AMPLITUDE_PLOT_DECIBLES_140_KEY)); 299 break; 300 } 301 case PERCENTAGE_AP: { 302 json_object_set_new(root, AMPLITUDE_PLOT_KEY, json_string(AMPLITUDE_PLOT_PERCENTAGE_KEY)); 303 break; 304 } 305 } 306 } 307 308 void AnalyzerBase::amplitudePlotFromJson(json_t* root) { 309 json_t* ap = json_object_get(root, AMPLITUDE_PLOT_KEY); 310 if (ap) { 311 std::string aps = json_string_value(ap); 312 if (aps == AMPLITUDE_PLOT_DECIBLES_80_KEY) { 313 _amplitudePlot = DECIBELS_80_AP; 314 } 315 else if (aps == AMPLITUDE_PLOT_DECIBLES_140_KEY) { 316 _amplitudePlot = DECIBELS_140_AP; 317 } 318 else if (aps == AMPLITUDE_PLOT_PERCENTAGE_KEY) { 319 _amplitudePlot = PERCENTAGE_AP; 320 } 321 } 322 else { // backwards compatibility... 323 json_t* jrd = json_object_get(root, RANGE_DB_KEY); 324 if (jrd) { 325 if ((float)json_real_value(jrd) == 140.0f) { 326 _amplitudePlot = DECIBELS_140_AP; 327 } 328 } 329 } 330 } 331 332 333 void AnalyzerBaseWidget::addFrequencyPlotContextMenu(Menu* menu) { 334 auto m = dynamic_cast<AnalyzerBase*>(module); 335 assert(m); 336 337 OptionsMenuItem* mi = new OptionsMenuItem("Frequency plot"); 338 mi->addItem(OptionMenuItem("Logarithmic", [m]() { return m->_frequencyPlot == AnalyzerBase::LOG_FP; }, [m]() { m->_frequencyPlot = AnalyzerBase::LOG_FP; })); 339 mi->addItem(OptionMenuItem("Linear", [m]() { return m->_frequencyPlot == AnalyzerBase::LINEAR_FP; }, [m]() { m->_frequencyPlot = AnalyzerBase::LINEAR_FP; })); 340 OptionsMenuItem::addToMenu(mi, menu); 341 } 342 343 void AnalyzerBaseWidget::addFrequencyRangeContextMenu(Menu* menu) { 344 auto m = dynamic_cast<AnalyzerBase*>(module); 345 assert(m); 346 347 OptionsMenuItem* mi = new OptionsMenuItem("Frequency range"); 348 mi->addItem(OptionMenuItem("Lower 10%", [m]() { return m->_range == -0.9f; }, [m]() { m->_range = -0.9f; })); 349 mi->addItem(OptionMenuItem("Lower 25%", [m]() { return m->_range == -0.75f; }, [m]() { m->_range = -0.75f; })); 350 mi->addItem(OptionMenuItem("Lower 50%", [m]() { return m->_range == -0.5f; }, [m]() { m->_range = -0.5f; })); 351 mi->addItem(OptionMenuItem("Lower 75%", [m]() { return m->_range == -0.25f; }, [m]() { m->_range = -0.25f; })); 352 mi->addItem(OptionMenuItem("Full", [m]() { return m->_range == 0.0f; }, [m]() { m->_range = 0.0f; })); 353 mi->addItem(OptionMenuItem("Upper 75%", [m]() { return m->_range == 0.25f; }, [m]() { m->_range = 0.25f; })); 354 mi->addItem(OptionMenuItem("Upper 50%", [m]() { return m->_range == 0.5f; }, [m]() { m->_range = 0.5f; })); 355 mi->addItem(OptionMenuItem("Upper 25%", [m]() { return m->_range == 0.75f; }, [m]() { m->_range = 0.75f; })); 356 OptionsMenuItem::addToMenu(mi, menu); 357 } 358 359 void AnalyzerBaseWidget::addAmplitudePlotContextMenu(Menu* menu, bool linearOption) { 360 auto m = dynamic_cast<AnalyzerBase*>(module); 361 assert(m); 362 363 OptionsMenuItem* mi = new OptionsMenuItem("Amplitude plot"); 364 mi->addItem(OptionMenuItem("Decibels to -60dB", [m]() { return m->_amplitudePlot == AnalyzerBase::DECIBELS_80_AP; }, [m]() { m->_amplitudePlot = AnalyzerBase::DECIBELS_80_AP; })); 365 mi->addItem(OptionMenuItem("Decibels To -120dB", [m]() { return m->_amplitudePlot == AnalyzerBase::DECIBELS_140_AP; }, [m]() { m->_amplitudePlot = AnalyzerBase::DECIBELS_140_AP; })); 366 if (linearOption) { 367 mi->addItem(OptionMenuItem("Linear percentage", [m]() { return m->_amplitudePlot == AnalyzerBase::PERCENTAGE_AP; }, [m]() { m->_amplitudePlot = AnalyzerBase::PERCENTAGE_AP; })); 368 } 369 OptionsMenuItem::addToMenu(mi, menu); 370 } 371 372 373 void AnalyzerDisplay::onButton(const event::Button& e) { 374 if (!(e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0)) { 375 return; 376 } 377 e.consume(this); 378 _freezeMouse = e.pos; 379 _freezeLastBinI = -1; 380 381 if (_freezeBufs) { 382 delete[] _freezeBufs; 383 } 384 _freezeBufs = new float[_module->_core._nChannels * _module->_core._outBufferN]; 385 for (int i = 0; i < _module->_core._nChannels; ++i) { 386 if (_channelBinsReaderFactories[i]) { 387 std::unique_ptr<BinsReader> br = _channelBinsReaderFactories[i](_module->_core); 388 float* dest = _freezeBufs + i * _module->_core._outBufferN; 389 for (int j = 0; j < _module->_core._outBufferN; ++j) { 390 *(dest + j) = br->at(j); 391 } 392 } 393 else { 394 float* bins = _module->_core.getBins(i); 395 std::copy(bins, bins + _module->_core._outBufferN, _freezeBufs + i * _module->_core._outBufferN); 396 } 397 } 398 } 399 400 void AnalyzerDisplay::onDragMove(const event::DragMove& e) { 401 float zoom = APP->scene->rackScroll->zoomWidget->zoom; 402 _freezeMouse.x += e.mouseDelta.x / zoom; 403 _freezeMouse.y += e.mouseDelta.y / zoom; 404 _freezeDraw = _freezeMouse.x > _insetLeft && _freezeMouse.x < _size.x - _insetRight && _freezeMouse.y > _insetTop && _freezeMouse.y < _size.y - _insetBottom; 405 if (e.mouseDelta.x != 0.0f) { 406 _freezeNudgeBin = 0; 407 } 408 } 409 410 void AnalyzerDisplay::onDragEnd(const event::DragEnd& e) { 411 _freezeMouse = Vec(0, 0); 412 _freezeDraw = false; 413 if (_freezeBufs) { 414 delete[] _freezeBufs; 415 _freezeBufs = NULL; 416 } 417 } 418 419 void AnalyzerDisplay::onHoverKey(const event::HoverKey &e) { 420 if (e.key == GLFW_KEY_LEFT) { 421 e.consume(this); 422 if (_freezeLastBinI > 0 && (e.action == GLFW_PRESS || e.action == GLFW_REPEAT)) { 423 _freezeNudgeBin--; 424 } 425 } 426 else if (e.key == GLFW_KEY_RIGHT) { 427 e.consume(this); 428 int binsN = _module->_core._size / _module->_core._binAverageN; 429 if (_freezeLastBinI < binsN - 1 && (e.action == GLFW_PRESS || e.action == GLFW_REPEAT)) { 430 _freezeNudgeBin++; 431 } 432 } 433 } 434 435 void AnalyzerDisplay::setChannelBinsReaderFactory(int channel, BinsReaderFactory brf) { 436 assert(_channelBinsReaderFactories); 437 assert(_module); 438 assert(channel >= 0 && channel < _module->_core._nChannels); 439 _channelBinsReaderFactories[channel] = brf; 440 } 441 442 void AnalyzerDisplay::displayChannel(int channel, bool display) { 443 assert(channel >= 0 && channel < _module->_core._nChannels); 444 assert(_displayChannel); 445 assert(_module); 446 assert(channel < _module->_core._nChannels); 447 _displayChannel[channel] = display; 448 } 449 450 void AnalyzerDisplay::channelLabel(int channel, std::string label) { 451 assert(_module); 452 assert(channel >= 0 && channel < _module->_core._nChannels); 453 _channelLabels[channel] = label; 454 } 455 456 void AnalyzerDisplay::drawOnce(const DrawArgs& args, bool screenshot, bool lit) { 457 if (!screenshot) { 458 assert(_module); 459 _module->_core._channelsMutex.lock(); 460 } 461 462 FrequencyPlot frequencyPlot = LOG_FP; 463 AmplitudePlot amplitudePlot = DECIBELS_80_AP; 464 float rangeMinHz = 0.0f; 465 float rangeMaxHz = 0.0f; 466 if (!screenshot) { 467 frequencyPlot = _module->_frequencyPlot; 468 amplitudePlot = _module->_amplitudePlot; 469 rangeMinHz = _module->_rangeMinHz; 470 rangeMaxHz = _module->_rangeMaxHz; 471 assert(rangeMaxHz <= 0.5f * _module->_core._sampleRate); 472 assert(rangeMinHz <= rangeMaxHz); 473 } 474 else { 475 rangeMaxHz = 0.5f * APP->engine->getSampleRate(); 476 } 477 478 float strokeWidth = std::max(1.0f, 3.0f - getZoom()); 479 if (frequencyPlot == LINEAR_FP) { 480 _xAxisLogFactor = 1.0f; 481 } 482 else { 483 _xAxisLogFactor = (rangeMaxHz - rangeMinHz) / rangeMaxHz; 484 _xAxisLogFactor *= 1.0f - baseXAxisLogFactor; 485 _xAxisLogFactor = 1.0f - _xAxisLogFactor; 486 } 487 488 nvgSave(args.vg); 489 drawBackground(args); 490 nvgScissor(args.vg, _insetAround, _insetAround, _size.x - _insetAround, _size.y - _insetAround); 491 if (isScreenshot() || !lit) { 492 drawYAxis(args, strokeWidth, amplitudePlot); 493 drawXAxis(args, strokeWidth, frequencyPlot, rangeMinHz, rangeMaxHz); 494 } 495 else { 496 drawHeader(args, rangeMinHz, rangeMaxHz); 497 drawYAxis(args, strokeWidth, amplitudePlot); 498 drawXAxis(args, strokeWidth, frequencyPlot, rangeMinHz, rangeMaxHz); 499 int freezeBinI = 0; 500 float freezeLowHz = 0.0f; 501 float freezeHighHz = 0.0f; 502 if (_freezeDraw) { 503 freezeValues(rangeMinHz, rangeMaxHz, freezeBinI, freezeLowHz, freezeHighHz); 504 _freezeLastBinI = freezeBinI; 505 drawFreezeUnder(args, freezeLowHz, freezeHighHz, rangeMinHz, rangeMaxHz, strokeWidth); 506 } 507 508 for (int i = 0; i < _module->_core._nChannels; ++i) { 509 if (_displayChannel[i]) { 510 if (_module->_core._channels[i]) { 511 GenericBinsReader br(_freezeBufs ? _freezeBufs + i * _module->_core._outBufferN : _module->_core.getBins(i)); 512 drawGraph(args, br, _channelColors[i % channelColorsN], strokeWidth, frequencyPlot, rangeMinHz, rangeMaxHz, amplitudePlot); 513 } 514 else if (_channelBinsReaderFactories[i]) { 515 std::unique_ptr<BinsReader> br = _channelBinsReaderFactories[i](_module->_core); 516 drawGraph(args, *br, _channelColors[i % channelColorsN], strokeWidth, frequencyPlot, rangeMinHz, rangeMaxHz, amplitudePlot); 517 } 518 } 519 } 520 521 if (_freezeDraw) { 522 drawFreezeOver(args, freezeBinI, _module->_core._size / _module->_core._binAverageN, freezeLowHz, freezeHighHz, strokeWidth); 523 } 524 } 525 nvgRestore(args.vg); 526 527 if (!screenshot) { 528 _module->_core._channelsMutex.unlock(); 529 } 530 } 531 532 void AnalyzerDisplay::drawBackground(const DrawArgs& args) { 533 nvgSave(args.vg); 534 nvgBeginPath(args.vg); 535 nvgRect(args.vg, 0, 0, _size.x, _size.y); 536 nvgFillColor(args.vg, nvgRGBA(0x00, 0x00, 0x00, 0xff)); 537 nvgFill(args.vg); 538 if (_drawInset) { 539 nvgStrokeColor(args.vg, nvgRGBA(0x50, 0x50, 0x50, 0xff)); 540 nvgStroke(args.vg); 541 } 542 nvgRestore(args.vg); 543 } 544 545 void AnalyzerDisplay::drawHeader(const DrawArgs& args, float rangeMinHz, float rangeMaxHz) { 546 nvgSave(args.vg); 547 548 const int textY = -4; 549 const int charPx = 5; 550 int x = _insetAround + 2; 551 552 std::string s = format("Peaks (+/-%0.1f):", (_module->_core._sampleRate / 2.0f) / (float)(_module->_core._size / _module->_core._binAverageN)); 553 drawText(args, s.c_str(), x, _insetTop + textY); 554 x += s.size() * charPx - 0; 555 556 int spacing = 3; 557 if (_size.x > 300) { 558 x += 5; 559 spacing = 20; 560 } 561 for (int i = 0; i < _module->_core._nChannels; ++i) { 562 if (_module->_core._channels[i]) { 563 s = format("%c:%7.1f", 'A' + i, _module->_core.getPeak(i, rangeMinHz, rangeMaxHz)); 564 drawText(args, s.c_str(), x, _insetTop + textY, 0.0, &_channelColors[i % channelColorsN]); 565 } 566 x += 9 * charPx + spacing; 567 } 568 569 nvgRestore(args.vg); 570 } 571 572 void AnalyzerDisplay::drawYAxis(const DrawArgs& args, float strokeWidth, AmplitudePlot plot) { 573 nvgSave(args.vg); 574 nvgStrokeColor(args.vg, _axisColor); 575 nvgStrokeWidth(args.vg, strokeWidth); 576 const int lineX = _insetLeft - 2; 577 const int textX = 9; 578 const float textR = -M_PI/2.0; 579 580 nvgBeginPath(args.vg); 581 int lineY = _insetTop; 582 nvgMoveTo(args.vg, lineX, lineY); 583 nvgLineTo(args.vg, _size.x - _insetRight, lineY); 584 nvgStroke(args.vg); 585 586 switch (plot) { 587 case DECIBELS_80_AP: 588 case DECIBELS_140_AP: { 589 float rangeDb = plot == DECIBELS_140_AP ? 140.0 : 80.0; 590 auto line = [&](float db, float sw, const char* label, float labelOffset) { 591 nvgBeginPath(args.vg); 592 int y = _insetTop + (_graphSize.y - _graphSize.y*(rangeDb - _positiveDisplayDB + db)/rangeDb); 593 nvgMoveTo(args.vg, lineX, y); 594 nvgLineTo(args.vg, _size.x - _insetRight, y); 595 nvgStrokeWidth(args.vg, strokeWidth * sw); 596 nvgStroke(args.vg); 597 drawText(args, label, textX, y + labelOffset, textR); 598 }; 599 line(12.0, 1.0, "12", 5.0); 600 line(0.0, 2.0, "0", 2.3); 601 line(-12.0, 1.0, "-12", 10.0); 602 line(-24.0, 1.0, "-24", 10.0); 603 line(-48.0, 1.0, "-48", 10.0); 604 if (rangeDb > 100.0) { 605 line(-96.0, 1.0, "-96", 10.0); 606 } 607 drawText(args, "dB", textX, _size.y - _insetBottom, textR); 608 break; 609 } 610 611 case PERCENTAGE_AP: { 612 auto line = [&](float pct, float sw, const char* label, float labelOffset) { 613 nvgBeginPath(args.vg); 614 int y = _insetTop + (_graphSize.y - _graphSize.y*(pct / (100.0 * _totalLinearAmplitude))); 615 nvgMoveTo(args.vg, lineX, y); 616 nvgLineTo(args.vg, _size.x - _insetRight, y); 617 nvgStrokeWidth(args.vg, strokeWidth * sw); 618 nvgStroke(args.vg); 619 drawText(args, label, textX, y + labelOffset, textR); 620 }; 621 line(180.0, 1.0, "180", 8.0); 622 line(160.0, 1.0, "160", 8.0); 623 line(140.0, 1.0, "140", 8.0); 624 line(120.0, 1.0, "120", 8.0); 625 line(100.0, 2.0, "100", 8.0); 626 line(80.0, 1.0, "80", 5.0); 627 line(60.0, 1.0, "60", 5.0); 628 line(40.0, 1.0, "40", 5.0); 629 line(20.0, 1.0, "20", 5.0); 630 drawText(args, "%", textX, _size.y - _insetBottom, textR); 631 break; 632 } 633 } 634 635 nvgBeginPath(args.vg); 636 lineY = _insetTop + _graphSize.y + 1; 637 nvgMoveTo(args.vg, lineX, lineY); 638 nvgLineTo(args.vg, _size.x - _insetRight, lineY); 639 nvgStroke(args.vg); 640 641 nvgBeginPath(args.vg); 642 nvgMoveTo(args.vg, lineX, _insetTop); 643 nvgLineTo(args.vg, lineX, lineY); 644 nvgStroke(args.vg); 645 646 nvgRestore(args.vg); 647 } 648 649 void AnalyzerDisplay::drawXAxis(const DrawArgs& args, float strokeWidth, FrequencyPlot plot, float rangeMinHz, float rangeMaxHz) { 650 nvgSave(args.vg); 651 nvgStrokeColor(args.vg, _axisColor); 652 nvgStrokeWidth(args.vg, strokeWidth); 653 654 switch (plot) { 655 case LOG_FP: { 656 float hz = 100.0f; 657 while (hz < rangeMaxHz && hz < 1001.0) { 658 if (hz >= rangeMinHz) { 659 drawXAxisLine(args, hz, rangeMinHz, rangeMaxHz); 660 } 661 hz += 100.0; 662 } 663 hz = 2000.0; 664 while (hz < rangeMaxHz && hz < 10001.0) { 665 if (hz >= rangeMinHz) { 666 drawXAxisLine(args, hz, rangeMinHz, rangeMaxHz); 667 } 668 hz += 1000.0; 669 } 670 hz = 20000.0; 671 while (hz < rangeMaxHz && hz < 100001.0) { 672 if (hz >= rangeMinHz) { 673 drawXAxisLine(args, hz, rangeMinHz, rangeMaxHz); 674 } 675 hz += 10000.0; 676 } 677 hz = 200000.0; 678 while (hz < rangeMaxHz && hz < 1000001.0) { 679 if (hz >= rangeMinHz) { 680 drawXAxisLine(args, hz, rangeMinHz, rangeMaxHz); 681 } 682 hz += 100000.0; 683 } 684 685 drawText(args, " Hz", _insetLeft, _size.y - 2); 686 if (rangeMinHz <= 100.0f) { 687 float x = (100.0 - rangeMinHz) / (rangeMaxHz - rangeMinHz); 688 x = powf(x, _xAxisLogFactor); 689 if (x < 1.0) { 690 x *= _graphSize.x; 691 drawText(args, "100", _insetLeft + x - 8, _size.y - 2); 692 } 693 } 694 if (rangeMinHz <= 1000.0f) { 695 float x = (1000.0 - rangeMinHz) / (rangeMaxHz - rangeMinHz); 696 x = powf(x, _xAxisLogFactor); 697 if (x < 1.0) { 698 x *= _graphSize.x; 699 drawText(args, "1k", _insetLeft + x - 4, _size.y - 2); 700 } 701 } 702 if (rangeMinHz <= 10000.0f) { 703 float x = (10000.0 - rangeMinHz) / (rangeMaxHz - rangeMinHz); 704 x = powf(x, _xAxisLogFactor); 705 if (x < 1.0) { 706 x *= _graphSize.x; 707 drawText(args, "10k", _insetLeft + x - 7, _size.y - 2); 708 } 709 } 710 if (rangeMinHz <= 100000.0f) { 711 float x = (100000.0 - rangeMinHz) / (rangeMaxHz - rangeMinHz); 712 x = powf(x, _xAxisLogFactor); 713 if (x < 1.0) { 714 x *= _graphSize.x; 715 drawText(args, "100k", _insetLeft + x - 9, _size.y - 2); 716 } 717 } 718 719 auto extraLabels = [&](float increment) { 720 if (rangeMinHz > increment) { 721 float hz = 2.0f * increment; 722 float lastX = 0.0f; 723 while (hz < 10.0f * increment) { 724 float x = (hz - rangeMinHz) / (rangeMaxHz - rangeMinHz); 725 x = powf(x, _xAxisLogFactor); 726 if (x > lastX + 0.1f && x < 1.0f) { 727 lastX = x; 728 x *= _graphSize.x; 729 std::string s = format("%dk", (int)(hz / 1000.0f)); 730 drawText(args, s.c_str(), _insetLeft + x - 7, _size.y - 2); 731 } 732 hz += increment; 733 } 734 return true; 735 } 736 return false; 737 }; 738 extraLabels(100000.0f) || 739 extraLabels(10000.0f) || 740 extraLabels(1000.0f); 741 break; 742 } 743 744 case LINEAR_FP: { 745 float range = rangeMaxHz - rangeMinHz; 746 float spacing = 100.0f; 747 if (range > 100000.0f) { 748 spacing = 10000.0f; 749 } 750 else if (range > 25000.0f) { 751 spacing = 5000.0f; 752 } 753 else if (range > 10000.0f) { 754 spacing = 1000.0f; 755 } 756 else if (range > 2500.0f) { 757 spacing = 500.0f; 758 } 759 const char* suffix = ""; 760 float divisor = 1.0f; 761 if (spacing > 100.f) { 762 suffix = "k"; 763 divisor = 1000.0f; 764 } 765 766 drawText(args, "Hz", _insetLeft, _size.y - 2); 767 float hz = 0.0f; 768 float lastX = 0.0f; 769 float xMin = 0.1f; 770 if (_graphSize.x > 400) { 771 xMin = 0.05f; 772 } 773 while (hz < rangeMaxHz) { 774 if (hz > rangeMinHz) { 775 drawXAxisLine(args, hz, rangeMinHz, rangeMaxHz); 776 777 float x = (hz - rangeMinHz) / (rangeMaxHz - rangeMinHz); 778 if (x > lastX + xMin && x < 0.99f) { 779 lastX = x; 780 x *= _graphSize.x; 781 float dhz = hz / divisor; 782 std::string s; 783 if (dhz - (int)dhz < 0.0001f) { 784 s = format("%d%s", (int)dhz, suffix); 785 } 786 else { 787 s = format("%0.1f%s", dhz, suffix); 788 } 789 drawText(args, s.c_str(), _insetLeft + x - (s.size() <= 2 ? 5 : 8), _size.y - 2); 790 } 791 } 792 hz += spacing; 793 } 794 break; 795 } 796 } 797 798 nvgRestore(args.vg); 799 } 800 801 void AnalyzerDisplay::drawXAxisLine(const DrawArgs& args, float hz, float rangeMinHz, float rangeMaxHz) { 802 float x = (hz - rangeMinHz) / (rangeMaxHz - rangeMinHz); 803 x = powf(x, _xAxisLogFactor); 804 if (x < 1.0) { 805 x *= _graphSize.x; 806 nvgBeginPath(args.vg); 807 nvgMoveTo(args.vg, _insetLeft + x, _insetTop); 808 nvgLineTo(args.vg, _insetLeft + x, _insetTop + _graphSize.y); 809 nvgStroke(args.vg); 810 } 811 } 812 813 void AnalyzerDisplay::drawGraph( 814 const DrawArgs& args, 815 BinsReader& bins, 816 NVGcolor color, 817 float strokeWidth, 818 FrequencyPlot freqPlot, 819 float rangeMinHz, 820 float rangeMaxHz, 821 AmplitudePlot ampPlot 822 ) { 823 float nyquist = 0.5f * _module->_core._sampleRate; 824 int binsN = _module->_core._size / _module->_core._binAverageN; 825 float binHz = nyquist / (float)binsN; 826 float range = (rangeMaxHz - rangeMinHz) / nyquist; 827 int pointsN = roundf(binsN * range); 828 int pointsOffset = roundf(binsN * (rangeMinHz / nyquist)); 829 nvgSave(args.vg); 830 nvgScissor(args.vg, _insetLeft, _insetTop, _graphSize.x, _graphSize.y); 831 nvgStrokeColor(args.vg, color); 832 nvgStrokeWidth(args.vg, strokeWidth); 833 nvgBeginPath(args.vg); 834 for (int i = 0; i < pointsN; ++i) { 835 int oi = pointsOffset + i; 836 assert(oi < _module->_core._outBufferN); 837 int height = binValueToHeight(bins.at(oi), ampPlot); 838 if (i == 0) { 839 nvgMoveTo(args.vg, _insetLeft, _insetTop + (_graphSize.y - height)); 840 } 841 float hz = ((float)(pointsOffset + i) + 0.5f) * binHz; 842 float x = _graphSize.x * powf((hz - rangeMinHz) / (rangeMaxHz - rangeMinHz), _xAxisLogFactor); 843 nvgLineTo(args.vg, _insetLeft + x, _insetTop + (_graphSize.y - height)); 844 } 845 nvgStroke(args.vg); 846 nvgRestore(args.vg); 847 } 848 849 void AnalyzerDisplay::freezeValues(float rangeMinHz, float rangeMaxHz, int& binI, float& lowHz, float& highHz) { 850 int binsN = _module->_core._size / _module->_core._binAverageN; 851 float binHz = (0.5f * _module->_core._sampleRate) / (float)binsN; 852 float mouseHz = powf((_freezeMouse.x - _insetLeft) / (float)_graphSize.x, 1.0f / _xAxisLogFactor); 853 mouseHz *= rangeMaxHz - rangeMinHz; 854 mouseHz += rangeMinHz; 855 binI = mouseHz / binHz; 856 binI = std::min(binsN - 1, std::max(0, binI + _freezeNudgeBin)); 857 lowHz = binI * binHz; 858 highHz = (binI + 1) * binHz; 859 } 860 861 void AnalyzerDisplay::drawFreezeUnder(const DrawArgs& args, float lowHz, float highHz, float rangeMinHz, float rangeMaxHz, float strokeWidth) { 862 float x1 = _graphSize.x * powf((lowHz - rangeMinHz) / (rangeMaxHz - rangeMinHz), _xAxisLogFactor); 863 float x2 = _graphSize.x * powf((highHz - rangeMinHz) / (rangeMaxHz - rangeMinHz), _xAxisLogFactor); 864 if (x2 - x1 < strokeWidth) { 865 float x = strokeWidth - (x2 - x1); 866 x /= 2.0f; 867 x1 -= x; 868 x2 += x; 869 } 870 871 nvgSave(args.vg); 872 nvgScissor(args.vg, _insetLeft, _insetTop, _graphSize.x, _graphSize.y); 873 nvgBeginPath(args.vg); 874 nvgRect(args.vg, _insetLeft + x1, _insetTop, x2 - x1, _size.y - _insetBottom); 875 nvgFillColor(args.vg, nvgRGBA(0x00, 0xff, 0x00, 0xa0)); 876 nvgFill(args.vg); 877 nvgRestore(args.vg); 878 } 879 880 void AnalyzerDisplay::drawFreezeOver(const DrawArgs& args, int binI, int binsN, float lowHz, float highHz, float strokeWidth) { 881 nvgSave(args.vg); 882 auto formatHz = [](float hz) -> std::string { 883 if (hz < 1000.0f) { 884 return format("%0.2f Hz", hz); 885 } 886 return format("%0.3f KHz", hz / 1000.0f); 887 }; 888 889 std::vector<std::string> labels; 890 std::vector<std::string> values; 891 std::vector<const NVGcolor*> colors; 892 labels.push_back("Bin"); 893 values.push_back(format("%d / %d", binI + 1, binsN)); 894 colors.push_back(NULL); 895 labels.push_back("Low"); 896 values.push_back(formatHz(lowHz)); 897 colors.push_back(NULL); 898 labels.push_back("High"); 899 values.push_back(formatHz(highHz)); 900 colors.push_back(NULL); 901 for (int i = 0; i < _module->_core._nChannels; ++i) { 902 if (_displayChannel[i] && (_module->_core._channels[i] || _channelBinsReaderFactories[i])) { 903 if (_channelLabels[i].empty()) { 904 labels.push_back(format("Ch. %d", i + 1)); 905 } 906 else { 907 labels.push_back(_channelLabels[i]); 908 } 909 float bv = *(_freezeBufs + i * _module->_core._outBufferN + binI); 910 values.push_back(format("%0.2f dB", binValueToDb(bv))); 911 colors.push_back(&_channelColors[i % channelColorsN]); 912 } 913 } 914 assert(labels.size() == values.size()); 915 916 size_t maxLabel = 0; 917 for (auto& label : labels) { 918 if (label.size() > maxLabel) { 919 maxLabel = label.size(); 920 } 921 } 922 923 std::vector<std::string> lines; 924 size_t maxLine = 0; 925 for (size_t i = 0; i < labels.size(); ++i) { 926 char spaces[maxLabel + 1]; 927 int nSpaces = maxLabel - labels[i].size(); 928 memset(spaces, ' ', nSpaces); 929 spaces[nSpaces] = '\0'; 930 std::string line = format("%s%s: %s", spaces, labels[i].c_str(), values[i].c_str()); 931 lines.push_back(line); 932 if (line.size() > maxLine) { 933 maxLine = line.size(); 934 } 935 } 936 937 const float charWidth = 7.0f; 938 const float charHeight = 14.0f; 939 const float inset = 10.0f; 940 const float lineSep = 1.5f; 941 const float mousePad = 5.0f; 942 const float edgePad = 3.0f; 943 Vec boxDim( 944 maxLine * charWidth + 2 * inset, 945 lines.size() * charHeight + (lines.size() - 1) * lineSep + 2 * inset 946 ); 947 Vec boxPos( 948 _freezeMouse.x + mousePad, 949 _freezeMouse.y - boxDim.y / 2.0f 950 ); 951 if (boxPos.x + boxDim.x > _size.x - _insetRight) { 952 boxPos.x = _freezeMouse.x - mousePad - boxDim.x; 953 } 954 if (_freezeMouse.y - boxDim.y / 2.0f < _insetTop + edgePad) { 955 boxPos.y = _insetTop + edgePad; 956 } 957 if (_freezeMouse.y + boxDim.y / 2.0f > _size.y - _insetBottom - edgePad) { 958 boxPos.y = _size.y - _insetBottom - edgePad - boxDim.y; 959 } 960 961 nvgBeginPath(args.vg); 962 nvgRect(args.vg, boxPos.x, boxPos.y, boxDim.x, boxDim.y); 963 nvgFillColor(args.vg, nvgRGBA(0x00, 0x00, 0x00, 0xff)); 964 nvgFill(args.vg); 965 966 nvgStrokeColor(args.vg, _axisColor); // nvgRGBA(0x00, 0xff, 0x00, 0xd0)); 967 nvgStrokeWidth(args.vg, strokeWidth); 968 nvgBeginPath(args.vg); 969 nvgMoveTo(args.vg, boxPos.x, boxPos.y); 970 nvgLineTo(args.vg, boxPos.x + boxDim.x, boxPos.y); 971 nvgLineTo(args.vg, boxPos.x + boxDim.x, boxPos.y + boxDim.y); 972 nvgLineTo(args.vg, boxPos.x, boxPos.y + boxDim.y); 973 nvgLineTo(args.vg, boxPos.x, boxPos.y); 974 nvgStroke(args.vg); 975 976 float y = boxPos.y + inset; 977 for (size_t i = 0; i < labels.size(); ++i) { 978 drawText(args, lines[i].c_str(), boxPos.x + inset, y + 11.0f, 0.0f, colors[i], 14); 979 y += charHeight + lineSep; 980 } 981 982 nvgRestore(args.vg); 983 } 984 985 void AnalyzerDisplay::drawText(const DrawArgs& args, const char* s, float x, float y, float rotation, const NVGcolor* color, int fontSize) { 986 std::shared_ptr<Font> font = APP->window->loadFont(_fontPath); 987 nvgSave(args.vg); 988 nvgTranslate(args.vg, x, y); 989 nvgRotate(args.vg, rotation); 990 nvgFontSize(args.vg, fontSize); 991 nvgFontFaceId(args.vg, font->handle); 992 nvgFillColor(args.vg, color ? *color : _textColor); 993 nvgText(args.vg, 0, 0, s, NULL); 994 nvgRestore(args.vg); 995 } 996 997 int AnalyzerDisplay::binValueToHeight(float value, AmplitudePlot plot) { 998 switch (plot) { 999 case DECIBELS_80_AP: 1000 case DECIBELS_140_AP: { 1001 float rangeDb = plot == DECIBELS_140_AP ? 140.0 : 80.0; 1002 const float minDB = -(rangeDb - _positiveDisplayDB); 1003 value = binValueToDb(value); 1004 value = std::max(minDB, value); 1005 value = std::min(_positiveDisplayDB, value); 1006 value -= minDB; 1007 value /= rangeDb; 1008 return roundf(_graphSize.y * value); 1009 } 1010 default:; 1011 } 1012 1013 //case PERCENTAGE_AP: 1014 value = binValueToAmplitude(value); 1015 value = std::min(value, 2.0f); 1016 value = std::max(value, 0.0f); 1017 return roundf(_graphSize.y * value / _totalLinearAmplitude); 1018 } 1019 1020 float AnalyzerDisplay::binValueToAmplitude(float value) { 1021 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. 1022 return powf(value, 0.5f); // undoing magnitude scaling of levels back from the FFT? 1023 } 1024 1025 float AnalyzerDisplay::binValueToDb(float value) { 1026 return amplitudeToDecibels(binValueToAmplitude(value)); 1027 } 1028 1029 float AnalyzerDisplay::dbToBinValue(float db) { 1030 db = decibelsToAmplitude(db); 1031 db *= db; 1032 return db * 10.0f; 1033 }