BogaudioModules

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

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 }