1 /*
2 * Spectral Analyzer audio effect based on DISTRHO Plugin Framework (DPF)
3 *
4 * SPDX-License-Identifier: MIT
5 *
6 * Copyright (C) 2019 Jean Pierre Cimalando <jpcima@protonmail.com>
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to
10 * deal in the Software without restriction, including without limitation the
11 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 * sell copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 * IN THE SOFTWARE.
25 */
26
27 #include "UISpectralAnalyzer.hpp"
28 #include "Parameters.h"
29 #include "ColorPalette.h"
30 #include "Config.h"
31 #include "ui/components/SpectrumView.h"
32 #include "ui/components/FloatingWindow.h"
33 #include "ui/components/SpinBoxChooser.h"
34 #include "ui/components/Slider.h"
35 #include "ui/components/TextLabel.h"
36 #include "ui/components/SelectionRectangle.h"
37 #include "ui/components/ResizeHandle.h"
38 #include "ui/FontEngine.h"
39 #include "dsp/AnalyzerDefs.h"
40 #include "util/format_string.h"
41 #include "Window.hpp"
42 #include "Color.hpp"
43 #include <sys/stat.h>
44 #include <cmath>
45
46 enum {
47 kToolBarIdSetup = 1,
48 kToolBarIdScale,
49 kToolBarIdFreeze,
50 kToolBarIdSelect,
51 kToolBarIdHide,
52 kToolBarIdColor,
53 };
54
55 // -----------------------------------------------------------------------
56 // Init / Deinit
57
UISpectralAnalyzer()58 UISpectralAnalyzer::UISpectralAnalyzer()
59 : UI(1000, 350),
60 fPalette(new ColorPalette(ColorPalette::create_default()))
61 {
62 ColorPalette &palette = *fPalette;
63
64 {
65 std::unique_ptr<CSimpleIniA> ini = create_theme();
66 ini->SetFileComment(
67 ";; This document describes the default theme." "\n"
68 ";; Important: do not edit this file directly! changes will be lost." "\n"
69 ";; If you want to design a theme, duplicate this file with a different name.");
70 ColorPalette::save_defaults(*ini, "color", true);
71 save_theme("default", *ini);
72 }
73
74 fUiConfig = load_configuration("ui");
75 if (!fUiConfig)
76 fUiConfig = create_configuration();
77
78 CSimpleIniA &uiConfig = *fUiConfig;
79 bool updateUiConfig = false;
80 if (!uiConfig.GetFileComment()) {
81 uiConfig.SetFileComment(";; This is the configuration of the user interface.");
82 updateUiConfig = true;
83 }
84 if (!uiConfig.GetValue("ui", "theme")) {
85 uiConfig.SetValue("ui", "theme", "default", "; Identifier of the theme which is active on program startup");
86 updateUiConfig = true;
87 }
88 if (updateUiConfig)
89 save_configuration("ui", uiConfig);
90
91 loadTheme(uiConfig.GetValue("ui", "theme", "default"));
92
93 FontEngine fe(*this, palette);
94
95 fSpectrumView = makeSubwidget<SpectrumView>(this, palette);
96
97 fMainToolBar = makeSubwidget<MainToolBar>(this, palette);
98 fMainToolBar->addButton(kToolBarIdSetup, "Setup", "\uf085");
99 fMainToolBar->addButton(kToolBarIdScale, "Scale", "\uf0b2");
100 fMainToolBar->addButton(kToolBarIdFreeze, "Freeze", "\uf256");
101 fMainToolBar->addButton(kToolBarIdSelect, "Select", "\uf05b");
102 fMainToolBar->addButton(kToolBarIdHide, "Hide", "\uf0d0");
103 fMainToolBar->addButton(kToolBarIdColor, "Color", "\uf53f");
104 fMainToolBar->setListener(this);
105
106 Font fontAwesome;
107 fontAwesome.name = "awesome";
108 fontAwesome.size = 16.0;
109 fontAwesome.colorRef = Colors::text_normal;
110 Font fontLabel;
111 fontLabel.name = "regular";
112 fontLabel.size = 12.0;
113 fontLabel.colorRef = Colors::text_normal;
114 Font fontChNLabel[kNumChannels];
115 Font fontChNAwesome[kNumChannels];
116 for (uint32_t c = 0; c < kNumChannels; ++c) {
117 fontChNLabel[c] = fontLabel;
118 fontChNAwesome[c] = fontAwesome;
119 fontChNLabel[c].colorRef = Colors::text_channel1 + c;
120 fontChNAwesome[c].colorRef = Colors::text_channel1 + c;
121 }
122
123 fSetupWindow = makeSubwidget<FloatingWindow>(this, palette);
124 fSetupWindow->setVisible(false);
125 fSetupWindow->setSize(260, 160);
126 {
127 int y = 10;
128
129 TextLabel *label;
130
131
132 label = makeSubwidget<TextLabel>(fSetupWindow, palette);
133 label->setText("Algorithm");
134 label->setFont(fontLabel);
135 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
136 label->setAbsolutePos(10, y);
137 label->setSize(100, 20);
138 fSetupWindow->moveAlong(label);
139
140 fAlgorithmChooser = makeSubwidget<SpinBoxChooser>(fSetupWindow, palette);
141 fAlgorithmChooser->setSize(150, 20);
142 fAlgorithmChooser->setAbsolutePos(100, y);
143 for (uint32_t algo = 0; algo < kNumAlgorithms; ++algo)
144 fAlgorithmChooser->addChoice(algo, getAlgorithmName((Algorithm)algo));
145 fAlgorithmChooser->ValueChangedCallback = [this](int32_t value)
146 { setParameterValue(kPidAlgorithm, value); };
147 fSetupWindow->moveAlong(fAlgorithmChooser);
148
149 y += 30;
150
151 label = makeSubwidget<TextLabel>(fSetupWindow, palette);
152 label->setText("Resolution");
153 label->setFont(fontLabel);
154 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
155 label->setAbsolutePos(10, y);
156 label->setSize(100, 20);
157 fSetupWindow->moveAlong(label);
158
159 fFftSizeChooser = makeSubwidget<SpinBoxChooser>(fSetupWindow, palette);
160 fFftSizeChooser->setSize(150, 20);
161 fFftSizeChooser->setAbsolutePos(100, y);
162 for (uint32_t sizeLog2 = kStftMinSizeLog2; sizeLog2 <= kStftMaxSizeLog2; ++sizeLog2)
163 fFftSizeChooser->addChoice(sizeLog2, std::to_string(1u << sizeLog2).c_str());
164 fFftSizeChooser->ValueChangedCallback = [this](int32_t value)
165 { setParameterValue(kPidFftSize, value); };
166 fSetupWindow->moveAlong(fFftSizeChooser);
167
168 y += 30;
169
170 label = makeSubwidget<TextLabel>(fSetupWindow, palette);
171 label->setText("Step");
172 label->setFont(fontLabel);
173 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
174 label->setAbsolutePos(10, y);
175 label->setSize(100, 20);
176 fSetupWindow->moveAlong(label);
177
178 fStepSizeChooser = makeSubwidget<SpinBoxChooser>(fSetupWindow, palette);
179 fStepSizeChooser->setSize(150, 20);
180 fStepSizeChooser->setAbsolutePos(100, y);
181 for (uint32_t stepLog2 = kStftMinStepLog2; stepLog2 <= kStftMaxStepLog2; ++stepLog2)
182 fStepSizeChooser->addChoice(stepLog2, std::to_string(1u << stepLog2).c_str());
183 fStepSizeChooser->ValueChangedCallback = [this](int32_t value)
184 { setParameterValue(kPidStepSize, value); };
185 fSetupWindow->moveAlong(fStepSizeChooser);
186
187 y += 30;
188
189 label = makeSubwidget<TextLabel>(fSetupWindow, palette);
190 label->setText("Attack time");
191 label->setFont(fontLabel);
192 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
193 label->setAbsolutePos(10, y);
194 label->setSize(100, 20);
195 fSetupWindow->moveAlong(label);
196
197 fAttackTimeSlider = makeSubwidget<Slider>(fSetupWindow, palette);
198 fAttackTimeSlider->setSize(150, 20);
199 fAttackTimeSlider->setAbsolutePos(100, y);
200 fAttackTimeSlider->setValueBounds(kStftMinAttackTime, kStftMaxAttackTime);
201 fAttackTimeSlider->ValueChangedCallback = [this](double value)
202 { setParameterValue(kPidAttackTime, value); };
203 fAttackTimeSlider->FormatCallback = [](double value) -> std::string
204 { return std::to_string(std::lround(value * 1e3)) + " ms"; };
205 fSetupWindow->moveAlong(fAttackTimeSlider);
206
207 y += 30;
208
209 label = makeSubwidget<TextLabel>(fSetupWindow, palette);
210 label->setText("Release time");
211 label->setFont(fontLabel);
212 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
213 label->setAbsolutePos(10, y);
214 label->setSize(100, 20);
215 fSetupWindow->moveAlong(label);
216
217 fReleaseTimeSlider = makeSubwidget<Slider>(fSetupWindow, palette);
218 fReleaseTimeSlider->setSize(150, 20);
219 fReleaseTimeSlider->setAbsolutePos(100, y);
220 fReleaseTimeSlider->setValueBounds(kStftMinReleaseTime, kStftMaxReleaseTime);
221 fReleaseTimeSlider->ValueChangedCallback = [this](double value)
222 { setParameterValue(kPidReleaseTime, value); };
223 fReleaseTimeSlider->FormatCallback = [](double value) -> std::string
224 { return std::to_string(std::lround(value * 1e3)) + " ms"; };
225 fSetupWindow->moveAlong(fReleaseTimeSlider);
226 }
227
228 fScaleWindow = makeSubwidget<FloatingWindow>(this, palette);
229 fScaleWindow->setVisible(false);
230 fScaleWindow->setSize(180, 70);
231 {
232 int y = 10;
233
234 TextLabel *label;
235
236 label = makeSubwidget<TextLabel>(fScaleWindow, palette);
237 label->setText("\uf8cc");
238 label->setFont(fontAwesome);
239 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
240 label->setAbsolutePos(10, y);
241 label->setSize(130, 20);
242 fScaleWindow->moveAlong(label);
243
244 label = makeSubwidget<TextLabel>(fScaleWindow, palette);
245 label->setText("left: select zoom region");
246 label->setFont(fontLabel);
247 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
248 label->setAbsolutePos(30, y);
249 label->setSize(130, 20);
250 fScaleWindow->moveAlong(label);
251
252 y += 30;
253
254 label = makeSubwidget<TextLabel>(fScaleWindow, palette);
255 label->setText("\uf8cc");
256 label->setFont(fontAwesome);
257 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
258 label->setAbsolutePos(10, y);
259 label->setSize(130, 20);
260 fScaleWindow->moveAlong(label);
261
262 label = makeSubwidget<TextLabel>(fScaleWindow, palette);
263 label->setText("right: reset zoom");
264 label->setFont(fontLabel);
265 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
266 label->setAbsolutePos(30, y);
267 label->setSize(130, 20);
268 fScaleWindow->moveAlong(label);
269 }
270
271 fSelectWindow = makeSubwidget<FloatingWindow>(this, palette);
272 fSelectWindow->setVisible(false);
273 fSelectWindow->setSize(420, 100);
274 {
275 int x = 0;
276 int y = 10;
277
278 TextLabel *label;
279
280 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
281 label->setText("Cursor");
282 label->setFont(fontLabel);
283 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
284 label->setAbsolutePos(x + 10, y);
285 label->setSize(100, 20);
286 fSelectWindow->moveAlong(label);
287
288 y += 30;
289
290 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
291 label->setText("\uf337");
292 label->setFont(fontAwesome);
293 label->setAlignment(kAlignCenter|kAlignInside);
294 label->setAbsolutePos(x + 5, y);
295 label->setSize(20, 20);
296 fSelectWindow->moveAlong(label);
297
298 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
299 fSelectLabelX = label;
300 //label->setText("");
301 label->setFont(fontLabel);
302 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
303 label->setAbsolutePos(x + 30, y);
304 label->setSize(100, 20);
305 fSelectWindow->moveAlong(label);
306
307 y += 30;
308
309 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
310 label->setText("\uf338");
311 label->setFont(fontAwesome);
312 label->setAlignment(kAlignCenter|kAlignInside);
313 label->setAbsolutePos(x + 5, y);
314 label->setSize(20, 20);
315 fSelectWindow->moveAlong(label);
316
317 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
318 fSelectLabelY = label;
319 //label->setText("");
320 label->setFont(fontLabel);
321 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
322 label->setAbsolutePos(x + 30, y);
323 label->setSize(100, 20);
324 fSelectWindow->moveAlong(label);
325
326 y = 10;
327 x += 100;
328
329 for (unsigned c = 0; c < kNumChannels; ++c) {
330 y += 30;
331
332 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
333 label->setText("\uf140");
334 label->setFont(fontChNAwesome[c]);
335 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
336 label->setAbsolutePos(x + 10, y);
337 label->setSize(100, 20);
338 fSelectWindow->moveAlong(label);
339
340 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
341 fSelectChannelY[c] = label;
342 //label->setText("");
343 label->setFont(fontChNLabel[c]);
344 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
345 label->setAbsolutePos(x + 30, y);
346 label->setSize(100, 20);
347 fSelectWindow->moveAlong(label);
348 }
349
350 y = 10;
351 x += 120;
352
353 for (unsigned c = 0; c < kNumChannels; ++c) {
354 y = 10;
355
356 if (c == 0) {
357 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
358 label->setText("Nearby peak");
359 label->setFont(fontLabel);
360 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
361 label->setAbsolutePos(x + 10, y);
362 label->setSize(100, 20);
363 fSelectWindow->moveAlong(label);
364 }
365
366 y += 30;
367
368 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
369 label->setText("\uf140");
370 label->setFont(fontChNAwesome[c]);
371 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
372 label->setAbsolutePos(x + 10, y);
373 label->setSize(100, 20);
374 fSelectWindow->moveAlong(label);
375
376 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
377 fSelectNearPeakX[c] = label;
378 //label->setText("");
379 label->setFont(fontChNLabel[c]);
380 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
381 label->setAbsolutePos(x + 30, y);
382 label->setSize(100, 20);
383 fSelectWindow->moveAlong(label);
384
385 y += 30;
386
387 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
388 label->setText("\uf140");
389 label->setFont(fontChNAwesome[c]);
390 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
391 label->setAbsolutePos(x + 10, y);
392 label->setSize(100, 20);
393 fSelectWindow->moveAlong(label);
394
395 label = makeSubwidget<TextLabel>(fSelectWindow, palette);
396 fSelectNearPeakY[c] = label;
397 //label->setText("");
398 label->setFont(fontChNLabel[c]);
399 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
400 label->setAbsolutePos(x + 30, y);
401 label->setSize(100, 20);
402 fSelectWindow->moveAlong(label);
403
404 x += 100;
405 }
406 }
407
408 fColorWindow = makeSubwidget<FloatingWindow>(this, palette);
409 fColorWindow->setVisible(false);
410 fColorWindow->setSize(260, 70);
411 {
412 int y = 10;
413
414 TextLabel *label;
415
416 label = makeSubwidget<TextLabel>(fColorWindow, palette);
417 label->setText("Theme");
418 label->setFont(fontLabel);
419 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
420 label->setAbsolutePos(10, y);
421 label->setSize(100, 20);
422 fColorWindow->moveAlong(label);
423
424 fThemeChooser = makeSubwidget<SpinBoxChooser>(fColorWindow, palette);
425 fThemeChooser->setSize(150, 20);
426 fThemeChooser->setAbsolutePos(100, y);
427 fColorWindow->moveAlong(fThemeChooser);
428
429 y += 30;
430
431 label = makeSubwidget<TextLabel>(fColorWindow, palette);
432 label->setText("Edit mode");
433 label->setFont(fontLabel);
434 label->setAlignment(kAlignLeft|kAlignCenter|kAlignInside);
435 label->setAbsolutePos(10, y);
436 label->setSize(100, 20);
437 fColorWindow->moveAlong(label);
438
439 fThemeEditChooser = makeSubwidget<SpinBoxChooser>(fColorWindow, palette);
440 fThemeEditChooser->setSize(150, 20);
441 fThemeEditChooser->setAbsolutePos(100, y);
442 fThemeEditChooser->addChoice(0, "off");
443 fThemeEditChooser->addChoice(1, "on");
444 fColorWindow->moveAlong(fThemeEditChooser);
445 }
446
447 fSelectionRectangle = makeSubwidget<SelectionRectangle>(this, palette);
448 fSelectionRectangle->setVisible(false);
449
450 fResizeHandle = makeSubwidget<ResizeHandle>(this, palette);
451 fResizeHandle->setSize(20, 20);
452 fResizeHandle->RequestToResizeCallback = [this](DGL::Size<uint> newSize) {
453 setSize(newSize);
454 };
455
456 uiReshape(getWidth(), getHeight());
457 }
458
~UISpectralAnalyzer()459 UISpectralAnalyzer::~UISpectralAnalyzer()
460 {
461 getPluginInstance()->setEditorIsVisible(false);
462 }
463
getPluginInstance()464 PluginSpectralAnalyzer *UISpectralAnalyzer::getPluginInstance()
465 {
466 return static_cast<PluginSpectralAnalyzer *>(getPluginInstancePointer());
467 }
468
469 // -----------------------------------------------------------------------
470 // DSP/Plugin callbacks
471
472 /**
473 A parameter has changed on the plugin side.
474 This is called by the host to inform the UI about parameter changes.
475 */
parameterChanged(uint32_t index,float value)476 void UISpectralAnalyzer::parameterChanged(uint32_t index, float value)
477 {
478 switch (index) {
479 case kPidFftSize:
480 fFftSizeChooser->setValue(value);
481 break;
482 case kPidStepSize:
483 fStepSizeChooser->setValue(value);
484 break;
485 case kPidAttackTime:
486 fAttackTimeSlider->setValue(value);
487 break;
488 case kPidReleaseTime:
489 fReleaseTimeSlider->setValue(value);
490 break;
491 case kPidAlgorithm:
492 fAlgorithmChooser->setValue(value);
493 break;
494 }
495 }
496
497 /**
498 A program has been loaded on the plugin side.
499 This is called by the host to inform the UI about program changes.
500 */
programLoaded(uint32_t index)501 void UISpectralAnalyzer::programLoaded(uint32_t index)
502 {
503 (void)index;
504 DISTRHO_SAFE_ASSERT(false);
505 }
506
507 /**
508 Optional callback to inform the UI about a sample rate change on the plugin side.
509 */
sampleRateChanged(double newSampleRate)510 void UISpectralAnalyzer::sampleRateChanged(double newSampleRate)
511 {
512 (void)newSampleRate;
513 }
514
515 // -----------------------------------------------------------------------
516 // Optional UI callbacks
517
518 /**
519 Idle callback.
520 This function is called at regular intervals.
521 */
uiIdle()522 void UISpectralAnalyzer::uiIdle()
523 {
524 getPluginInstance()->setEditorIsVisible(isVisible());
525
526 updateSpectrum();
527
528 // under theme developer mode, recheck periodically for edits
529 if (fThemeEditChooser->value()) {
530 const std::string path = get_theme_file(fCurrentTheme);
531 bool update = false;
532
533 struct stat st;
534 if (::stat(path.c_str(), &st) == 0) {
535 #if !defined(_WIN32)
536 update = fCurrentThemeMtime.tv_sec != st.st_mtim.tv_sec ||
537 fCurrentThemeMtime.tv_nsec != st.st_mtim.tv_nsec;
538 #else
539 update = fCurrentThemeMtime != st.st_mtime;
540 #endif
541 }
542
543 if (update)
544 loadTheme(fCurrentTheme.c_str());
545 }
546 }
547
548 /**
549 Window reshape function, called when the parent window is resized.
550 */
uiReshape(uint width,uint height)551 void UISpectralAnalyzer::uiReshape(uint width, uint height)
552 {
553 fSpectrumView->setAbsolutePos(0, 0);
554 fSpectrumView->setSize(width, height);
555
556 fMainToolBar->setAbsolutePos(0, 0);
557 fMainToolBar->setSize(fMainToolBar->getIdealWidth(), 40);
558
559 FloatingWindow *floats[] = {fSetupWindow, fScaleWindow, fSelectWindow, fColorWindow};
560
561 if (!fInitializedFloatingWindowPos) {
562 const int floatingPosX = 4;
563 const int floatingPosY = fMainToolBar->getAbsoluteY() + fMainToolBar->getHeight() + 4;
564 for (FloatingWindow *win : floats)
565 win->setAbsolutePos(floatingPosX, floatingPosY);
566 fInitializedFloatingWindowPos = true;
567 }
568
569 for (FloatingWindow *win : floats) {
570 win->setMoveLimits(getAbsolutePos(), getSize());
571 // TODO(jpc) I'd rather move the window relative to nearest corner
572 win->repositionWithinLimits();
573 }
574
575 fResizeHandle->setAbsolutePos(
576 width - fResizeHandle->getWidth(), height - fResizeHandle->getHeight());
577 }
578
579 // -----------------------------------------------------------------------
580
onNanoDisplay()581 void UISpectralAnalyzer::onNanoDisplay()
582 {
583 }
584
onMouse(const MouseEvent & ev)585 bool UISpectralAnalyzer::onMouse(const MouseEvent &ev)
586 {
587 if (fMode == kModeScale && ev.press && ev.button == 1) {
588 fSelectionRectangle->setVisible(true);
589 fSelectionRectangle->setAbsolutePos(ev.pos);
590 fSelectionRectangle->setSize(0, 0);
591 fSelectionOrigin = ::Point(ev.pos.getX(), ev.pos.getY());
592 fScaleRectDragging = true;
593 return true;
594 }
595 else if (fMode == kModeScale && fScaleRectDragging && !ev.press && ev.button == 1) {
596 fSelectionRectangle->setVisible(false);
597 fScaleRectDragging = false;
598 int x1 = fSelectionRectangle->getAbsoluteX() - fSpectrumView->getAbsoluteX();
599 int x2 = x1 + (int)fSelectionRectangle->getWidth();
600 int y1 = fSelectionRectangle->getAbsoluteY() - fSpectrumView->getAbsoluteY();
601 int y2 = y1 + (int)fSelectionRectangle->getHeight();
602 if (x1 != x2 && y1 != y2) {
603 double key1 = fSpectrumView->keyOfX(x1);
604 double key2 = fSpectrumView->keyOfX(x2);
605 double db1 = fSpectrumView->dbMagOfY(y1);
606 double db2 = fSpectrumView->dbMagOfY(y2);
607 if (key1 > key2)
608 std::swap(key1, key2);
609 if (db1 > db2)
610 std::swap(db1, db2);
611 fSpectrumView->setKeyScale(key1, key2);
612 fSpectrumView->setDbScale(db1, db2);
613 }
614 return true;
615 }
616 else if (fMode == kModeScale && ev.press && ev.button == 3) {
617 fSpectrumView->setDefaultScales();
618 return true;
619 }
620 else if (fMode == kModeSelect && ev.press && ev.button == 1) {
621 fSelectPositionFixed = !fSelectPositionFixed;
622 if (!fSelectPositionFixed)
623 setNewSelectionPositionByMouse(ev.pos);
624 return true;
625 }
626 else if (fMode == kModeHide && ev.press && ev.button == 1) {
627 switchMode(kModeNormal);
628 return true;
629 }
630
631 return false;
632 }
633
onMotion(const MotionEvent & ev)634 bool UISpectralAnalyzer::onMotion(const MotionEvent &ev)
635 {
636 switch (fMode) {
637 case kModeScale:
638 if (fScaleRectDragging) {
639 int x1 = ev.pos.getX();
640 int y1 = ev.pos.getY();
641 int x2 = fSelectionOrigin.x;
642 int y2 = fSelectionOrigin.y;
643 if (x1 > x2)
644 std::swap(x1, x2);
645 if (y1 > y2)
646 std::swap(y1, y2);
647 fSelectionRectangle->setAbsolutePos(x1, y1);
648 fSelectionRectangle->setSize(x2 - x1, y2 - y1);
649 }
650 break;
651 case kModeSelect:
652 if (!fSelectPositionFixed)
653 setNewSelectionPositionByMouse(ev.pos);
654 break;
655 }
656
657 return false;
658 }
659
660 // -----------------------------------------------------------------------
661
onToolBarItemClicked(int id)662 void UISpectralAnalyzer::onToolBarItemClicked(int id)
663 {
664 switch (id) {
665 case kToolBarIdSetup:
666 switchMode((fMode == kModeSetup) ? kModeNormal : kModeSetup);
667 break;
668 case kToolBarIdScale:
669 switchMode((fMode == kModeScale) ? kModeNormal : kModeScale);
670 break;
671 case kToolBarIdFreeze:
672 fSpectrumView->toggleFreeze();
673 fMainToolBar->setSelected(kToolBarIdFreeze, fSpectrumView->isFrozen());
674 break;
675 case kToolBarIdSelect:
676 switchMode((fMode == kModeSelect) ? kModeNormal : kModeSelect);
677 break;
678 case kToolBarIdHide:
679 switchMode(kModeHide);
680 break;
681 case kToolBarIdColor:
682 switchMode((fMode == kModeColor) ? kModeNormal : kModeColor);
683 break;
684 }
685 }
686
687 // -----------------------------------------------------------------------
688
switchMode(int mode)689 void UISpectralAnalyzer::switchMode(int mode)
690 {
691 int prevMode = fMode;
692 if (mode == prevMode)
693 return;
694
695 fMode = mode;
696
697 fSetupWindow->setAllVisible(false);
698 fScaleWindow->setAllVisible(false);
699 fSelectWindow->setAllVisible(false);
700 fColorWindow->setAllVisible(false);
701 fMainToolBar->setSelected(kToolBarIdSetup, false);
702 fMainToolBar->setSelected(kToolBarIdScale, false);
703 fMainToolBar->setSelected(kToolBarIdSelect, false);
704 fMainToolBar->setSelected(kToolBarIdColor, false);
705
706 switch (prevMode) {
707 case kModeScale:
708 fScaleRectDragging = false;
709 break;
710 case kModeSelect:
711 fSpectrumView->clearReferenceLine();
712 break;
713 case kModeHide:
714 fMainToolBar->setVisible(true);
715 fResizeHandle->setVisible(true);
716 break;
717 }
718
719 switch (mode) {
720 case kModeSetup:
721 fSetupWindow->setAllVisible(true);
722 fMainToolBar->setSelected(kToolBarIdSetup, fMode == kModeSetup);
723 break;
724 case kModeScale:
725 fScaleWindow->setAllVisible(true);
726 fMainToolBar->setSelected(kToolBarIdScale, fMode == kModeScale);
727 fScaleRectDragging = false;
728 break;
729 case kModeSelect:
730 fSelectWindow->setAllVisible(true);
731 fMainToolBar->setSelected(kToolBarIdSelect, fMode == kModeSelect);
732 fSelectPositionFixed = false;
733 fSelectLastCursorKey = 0.0;
734 fSelectLastCursorFreq = 0.0;
735 fSelectLastCursorMag = 0.0;
736 fSpectrumView->clearReferenceLine();
737 break;
738 case kModeHide:
739 fMainToolBar->setVisible(false);
740 fResizeHandle->setVisible(false);
741 break;
742 case kModeColor:
743 fColorWindow->setAllVisible(true);
744 fMainToolBar->setSelected(kToolBarIdColor, fMode == kModeColor);
745 reloadThemeList();
746 break;
747 }
748 }
749
setNewSelectionPositionByMouse(DGL::Point<int> pos)750 void UISpectralAnalyzer::setNewSelectionPositionByMouse(DGL::Point<int> pos)
751 {
752 const double key = fSpectrumView->keyOfX(pos.getX() - fSpectrumView->getAbsoluteX());
753 const double mag = fSpectrumView->dbMagOfY(pos.getY() - fSpectrumView->getAbsoluteY());
754 const double freq = 440.0 * std::exp2((key - 69.0) * (1.0 / 12.0));
755 fSelectLastCursorKey = key;
756 fSelectLastCursorFreq = freq;
757 fSelectLastCursorMag = mag;
758 updateSelectModeDisplays();
759 }
760
updateSpectrum()761 void UISpectralAnalyzer::updateSpectrum()
762 {
763 PluginSpectralAnalyzer *plugin = getPluginInstance();
764 DISTRHO_SAFE_ASSERT_RETURN(plugin, );
765
766 std::unique_lock<SpinMutex> lock(plugin->fSendMutex);
767 fFrequencies = plugin->fSendFrequencies;
768 fMagnitudes = plugin->fSendMagnitudes;
769 fSize = plugin->fSendSize;
770 lock.unlock();
771
772 fSpectrumView->setData(fFrequencies.data(), fMagnitudes.data(), fSize, kNumChannels);
773
774 if (fMode == kModeSelect)
775 updateSelectModeDisplays();
776 }
777
updateSelectModeDisplays()778 void UISpectralAnalyzer::updateSelectModeDisplays()
779 {
780 auto toHzString = [](double hz) -> std::string {
781 return format_string("%.2f", hz) + " Hz";
782 };
783 auto toDbString = [](double dB) -> std::string {
784 if (dB > kStftFloorMagnitudeInDB + kNegligibleDB)
785 return format_string("%.2f", dB) + " dB";
786 else
787 return "-\u221e dB";
788 };
789
790 fSpectrumView->setReferenceLine(fSelectLastCursorKey, fSelectLastCursorMag);
791 fSelectLabelX->setText(toHzString(fSelectLastCursorFreq));
792 fSelectLabelY->setText(toDbString(fSelectLastCursorMag));
793
794 for (unsigned c = 0; c < kNumChannels; ++c) {
795 double mag = fSpectrumView->evalMagnitudeOnDisplay(c, fSelectLastCursorFreq);
796 fSelectChannelY[c]->setText(toDbString(mag));
797
798 SpectrumView::Peak pk = fSpectrumView->findNearbyPeakOnDisplay(c, fSelectLastCursorFreq);
799 fSelectNearPeakX[c]->setText(toHzString(pk.frequency));
800 fSelectNearPeakY[c]->setText(toDbString(pk.magnitude));
801 }
802 }
803
loadTheme(const char * theme)804 void UISpectralAnalyzer::loadTheme(const char *theme)
805 {
806 ColorPalette &palette = *fPalette;
807 palette = ColorPalette::create_default();
808
809 std::unique_ptr<CSimpleIniA> ini;
810 const std::string path = get_theme_file(theme);
811
812 if (!path.empty()) {
813 ini = create_theme();
814 if (ini->LoadFile(path.c_str()) != SI_OK)
815 ini.reset();
816 }
817
818 fCurrentThemeMtime = {};
819
820 if (!ini)
821 fprintf(stderr, "Cannot load theme: %s\n", theme);
822 else {
823 palette.load(*ini, "color");
824
825 struct stat st;
826 if (::stat(path.c_str(), &st) == 0) {
827 #if !defined(_WIN32)
828 fCurrentThemeMtime = st.st_mtim;
829 #else
830 fCurrentThemeMtime = st.st_mtime;
831 #endif
832 }
833 }
834
835 ///
836 CSimpleIniA &uiConfig = *fUiConfig;
837 const char *themeInConfig = uiConfig.GetValue("ui", "theme");
838 if (!themeInConfig || std::strcmp(themeInConfig, theme) != 0) {
839 uiConfig.SetValue("ui", "theme", theme, "; Identifier of the theme which is active on program startup", true);
840 save_configuration("ui", uiConfig);
841 }
842
843 ///
844 fCurrentTheme = theme;
845 repaint();
846 }
847
reloadThemeList()848 void UISpectralAnalyzer::reloadThemeList()
849 {
850 fThemeChooser->ValueChangedCallback = {};
851 fThemeChooser->clearChoices();
852 {
853 int32_t index = -1;
854 std::vector<std::string> themes = list_themes();
855 for (uint32_t i = 0, n = themes.size(); i < n; ++i) {
856 fThemeChooser->addChoice(i, themes[i].c_str());
857 if (themes[i] == fCurrentTheme)
858 index = i;
859 }
860 if (index != -1)
861 fThemeChooser->setValueIndex(index);
862 }
863 fThemeChooser->ValueChangedCallback = [this](int32_t index) {
864 loadTheme(fThemeChooser->textAtIndex(index).c_str());
865 };
866 }
867
868 // -----------------------------------------------------------------------
869
createUI()870 UI *DISTRHO::createUI()
871 {
872 return new UISpectralAnalyzer();
873 }
874