SaControlsDialog.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*
  2. * SaControlsDialog.cpp - definition of SaControlsDialog class.
  3. *
  4. * Copyright (c) 2019 Martin Pavelek <he29/dot/HS/at/gmail/dot/com>
  5. *
  6. * This file is part of LMMS - https://lmms.io
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public
  10. * License as published by the Free Software Foundation; either
  11. * version 2 of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public
  19. * License along with this program (see COPYING); if not, write to the
  20. * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  21. * Boston, MA 02110-1301 USA.
  22. *
  23. */
  24. #include "SaControlsDialog.h"
  25. #include <QGridLayout>
  26. #include <QLabel>
  27. #include <QSizePolicy>
  28. #include <QSplitter>
  29. #include <QWidget>
  30. #include "ComboBox.h"
  31. #include "ComboBoxModel.h"
  32. #include "embed.h"
  33. #include "Engine.h"
  34. #include "Knob.h"
  35. #include "LedCheckbox.h"
  36. #include "PixmapButton.h"
  37. #include "SaControls.h"
  38. #include "SaProcessor.h"
  39. // The entire GUI layout is built here.
  40. SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) :
  41. EffectControlDialog(controls),
  42. m_controls(controls),
  43. m_processor(processor)
  44. {
  45. // Top level placement of sections is handled by QSplitter widget.
  46. QHBoxLayout *master_layout = new QHBoxLayout;
  47. QSplitter *display_splitter = new QSplitter(Qt::Vertical);
  48. master_layout->addWidget(display_splitter);
  49. master_layout->setContentsMargins(2, 6, 2, 8);
  50. setLayout(master_layout);
  51. // Display splitter top: controls section
  52. QWidget *controls_widget = new QWidget;
  53. QHBoxLayout *controls_layout = new QHBoxLayout;
  54. controls_layout->setContentsMargins(0, 0, 0, 0);
  55. controls_widget->setLayout(controls_layout);
  56. controls_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
  57. controls_widget->setMaximumHeight(m_configHeight);
  58. display_splitter->addWidget(controls_widget);
  59. // Basic configuration
  60. QWidget *config_widget = new QWidget;
  61. QGridLayout *config_layout = new QGridLayout;
  62. config_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
  63. config_widget->setMaximumHeight(m_configHeight);
  64. config_widget->setLayout(config_layout);
  65. controls_layout->addWidget(config_widget);
  66. controls_layout->setStretchFactor(config_widget, 10);
  67. // Pre-compute target pixmap size based on monitor DPI.
  68. // Using setDevicePixelRatio() on pixmap allows the SVG image to be razor
  69. // sharp on High-DPI screens, but the desired size must be manually
  70. // enlarged. No idea how to make Qt do it in a more reasonable way.
  71. QSize iconSize = QSize(22.0 * devicePixelRatio(), 22.0 * devicePixelRatio());
  72. QSize buttonSize = 1.2 * iconSize;
  73. QSize advButtonSize = QSize((m_configHeight * devicePixelRatio()) / 3, m_configHeight * devicePixelRatio());
  74. // pause and freeze buttons
  75. PixmapButton *pauseButton = new PixmapButton(this, tr("Pause"));
  76. pauseButton->setToolTip(tr("Pause data acquisition"));
  77. QPixmap *pauseOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("play").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  78. QPixmap *pauseOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("pause").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  79. pauseOnPixmap->setDevicePixelRatio(devicePixelRatio());
  80. pauseOffPixmap->setDevicePixelRatio(devicePixelRatio());
  81. pauseButton->setActiveGraphic(*pauseOnPixmap);
  82. pauseButton->setInactiveGraphic(*pauseOffPixmap);
  83. pauseButton->setCheckable(true);
  84. pauseButton->setModel(&controls->m_pauseModel);
  85. config_layout->addWidget(pauseButton, 0, 0, 2, 1, Qt::AlignHCenter);
  86. PixmapButton *refFreezeButton = new PixmapButton(this, tr("Reference freeze"));
  87. refFreezeButton->setToolTip(tr("Freeze current input as a reference / disable falloff in peak-hold mode."));
  88. QPixmap *freezeOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("freeze").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  89. QPixmap *freezeOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("freeze_off").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  90. freezeOnPixmap->setDevicePixelRatio(devicePixelRatio());
  91. freezeOffPixmap->setDevicePixelRatio(devicePixelRatio());
  92. refFreezeButton->setActiveGraphic(*freezeOnPixmap);
  93. refFreezeButton->setInactiveGraphic(*freezeOffPixmap);
  94. refFreezeButton->setCheckable(true);
  95. refFreezeButton->setModel(&controls->m_refFreezeModel);
  96. config_layout->addWidget(refFreezeButton, 2, 0, 2, 1, Qt::AlignHCenter);
  97. // misc configuration switches
  98. LedCheckBox *waterfallButton = new LedCheckBox(tr("Waterfall"), this);
  99. waterfallButton->setToolTip(tr("Display real-time spectrogram"));
  100. waterfallButton->setCheckable(true);
  101. waterfallButton->setMinimumSize(70, 12);
  102. waterfallButton->setModel(&controls->m_waterfallModel);
  103. config_layout->addWidget(waterfallButton, 0, 1);
  104. LedCheckBox *smoothButton = new LedCheckBox(tr("Averaging"), this);
  105. smoothButton->setToolTip(tr("Enable exponential moving average"));
  106. smoothButton->setCheckable(true);
  107. smoothButton->setMinimumSize(70, 12);
  108. smoothButton->setModel(&controls->m_smoothModel);
  109. config_layout->addWidget(smoothButton, 1, 1);
  110. LedCheckBox *stereoButton = new LedCheckBox(tr("Stereo"), this);
  111. stereoButton->setToolTip(tr("Display stereo channels separately"));
  112. stereoButton->setCheckable(true);
  113. stereoButton->setMinimumSize(70, 12);
  114. stereoButton->setModel(&controls->m_stereoModel);
  115. config_layout->addWidget(stereoButton, 2, 1);
  116. LedCheckBox *peakHoldButton = new LedCheckBox(tr("Peak hold"), this);
  117. peakHoldButton->setToolTip(tr("Display envelope of peak values"));
  118. peakHoldButton->setCheckable(true);
  119. peakHoldButton->setMinimumSize(70, 12);
  120. peakHoldButton->setModel(&controls->m_peakHoldModel);
  121. config_layout->addWidget(peakHoldButton, 3, 1);
  122. // frequency: linear / log. switch and range selector
  123. PixmapButton *logXButton = new PixmapButton(this, tr("Logarithmic frequency"));
  124. logXButton->setToolTip(tr("Switch between logarithmic and linear frequency scale"));
  125. QPixmap *logXOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("x_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  126. QPixmap *logXOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("x_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  127. logXOnPixmap->setDevicePixelRatio(devicePixelRatio());
  128. logXOffPixmap->setDevicePixelRatio(devicePixelRatio());
  129. logXButton->setActiveGraphic(*logXOnPixmap);
  130. logXButton->setInactiveGraphic(*logXOffPixmap);
  131. logXButton->setCheckable(true);
  132. logXButton->setModel(&controls->m_logXModel);
  133. config_layout->addWidget(logXButton, 0, 2, 2, 1, Qt::AlignRight);
  134. ComboBox *freqRangeCombo = new ComboBox(this, tr("Frequency range"));
  135. freqRangeCombo->setToolTip(tr("Frequency range"));
  136. freqRangeCombo->setMinimumSize(100, ComboBox::DEFAULT_HEIGHT);
  137. freqRangeCombo->setMaximumSize(200, ComboBox::DEFAULT_HEIGHT);
  138. freqRangeCombo->setModel(&controls->m_freqRangeModel);
  139. config_layout->addWidget(freqRangeCombo, 0, 3, 2, 1);
  140. // amplitude: linear / log switch and range selector
  141. PixmapButton *logYButton = new PixmapButton(this, tr("Logarithmic amplitude"));
  142. logYButton->setToolTip(tr("Switch between logarithmic and linear amplitude scale"));
  143. QPixmap *logYOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("y_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  144. QPixmap *logYOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("y_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  145. logYOnPixmap->setDevicePixelRatio(devicePixelRatio());
  146. logYOffPixmap->setDevicePixelRatio(devicePixelRatio());
  147. logYButton->setActiveGraphic(*logYOnPixmap);
  148. logYButton->setInactiveGraphic(*logYOffPixmap);
  149. logYButton->setCheckable(true);
  150. logYButton->setModel(&controls->m_logYModel);
  151. config_layout->addWidget(logYButton, 2, 2, 2, 1, Qt::AlignRight);
  152. ComboBox *ampRangeCombo = new ComboBox(this, tr("Amplitude range"));
  153. ampRangeCombo->setToolTip(tr("Amplitude range"));
  154. ampRangeCombo->setMinimumSize(100, ComboBox::DEFAULT_HEIGHT);
  155. ampRangeCombo->setMaximumSize(200, ComboBox::DEFAULT_HEIGHT);
  156. ampRangeCombo->setModel(&controls->m_ampRangeModel);
  157. config_layout->addWidget(ampRangeCombo, 2, 3, 2, 1);
  158. // FFT: block size: icon and selector
  159. QLabel *blockSizeLabel = new QLabel("", this);
  160. QPixmap *blockSizeIcon = new QPixmap(PLUGIN_NAME::getIconPixmap("block_size"));
  161. blockSizeIcon->setDevicePixelRatio(devicePixelRatio());
  162. blockSizeLabel->setPixmap(blockSizeIcon->scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  163. config_layout->addWidget(blockSizeLabel, 0, 4, 2, 1, Qt::AlignRight);
  164. ComboBox *blockSizeCombo = new ComboBox(this, tr("FFT block size"));
  165. blockSizeCombo->setToolTip(tr("FFT block size"));
  166. blockSizeCombo->setMinimumSize(100, 22);
  167. blockSizeCombo->setMaximumSize(200, 22);
  168. blockSizeCombo->setModel(&controls->m_blockSizeModel);
  169. config_layout->addWidget(blockSizeCombo, 0, 5, 2, 1);
  170. processor->reallocateBuffers();
  171. connect(&controls->m_blockSizeModel, &ComboBoxModel::dataChanged, [=] {processor->reallocateBuffers();});
  172. // FFT: window type: icon and selector
  173. QLabel *windowLabel = new QLabel("", this);
  174. QPixmap *windowIcon = new QPixmap(PLUGIN_NAME::getIconPixmap("window"));
  175. windowIcon->setDevicePixelRatio(devicePixelRatio());
  176. windowLabel->setPixmap(windowIcon->scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  177. config_layout->addWidget(windowLabel, 2, 4, 2, 1, Qt::AlignRight);
  178. ComboBox *windowCombo = new ComboBox(this, tr("FFT window type"));
  179. windowCombo->setToolTip(tr("FFT window type"));
  180. windowCombo->setMinimumSize(100, ComboBox::DEFAULT_HEIGHT);
  181. windowCombo->setMaximumSize(200, ComboBox::DEFAULT_HEIGHT);
  182. windowCombo->setModel(&controls->m_windowModel);
  183. config_layout->addWidget(windowCombo, 2, 5, 2, 1);
  184. processor->rebuildWindow();
  185. connect(&controls->m_windowModel, &ComboBoxModel::dataChanged, [=] {processor->rebuildWindow();});
  186. // set stretch factors so that combo boxes expand first
  187. config_layout->setColumnStretch(3, 2);
  188. config_layout->setColumnStretch(5, 3);
  189. // Advanced configuration
  190. QWidget *advanced_widget = new QWidget;
  191. QGridLayout *advanced_layout = new QGridLayout;
  192. advanced_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
  193. advanced_widget->setMaximumHeight(m_configHeight);
  194. advanced_widget->setLayout(advanced_layout);
  195. advanced_widget->hide();
  196. controls_layout->addWidget(advanced_widget);
  197. controls_layout->setStretchFactor(advanced_widget, 10);
  198. // Peak envelope resolution
  199. Knob *envelopeResolutionKnob = new Knob(knobSmall_17, this);
  200. envelopeResolutionKnob->setModel(&controls->m_envelopeResolutionModel);
  201. envelopeResolutionKnob->setLabel(tr("Envelope res."));
  202. envelopeResolutionKnob->setToolTip(tr("Increase envelope resolution for better details, decrease for better GUI performance."));
  203. envelopeResolutionKnob->setHintText(tr("Draw at most"), tr(" envelope points per pixel"));
  204. advanced_layout->addWidget(envelopeResolutionKnob, 0, 0, 1, 1, Qt::AlignCenter);
  205. // Spectrum graph resolution
  206. Knob *spectrumResolutionKnob = new Knob(knobSmall_17, this);
  207. spectrumResolutionKnob->setModel(&controls->m_spectrumResolutionModel);
  208. spectrumResolutionKnob->setLabel(tr("Spectrum res."));
  209. spectrumResolutionKnob->setToolTip(tr("Increase spectrum resolution for better details, decrease for better GUI performance."));
  210. spectrumResolutionKnob->setHintText(tr("Draw at most"), tr(" spectrum points per pixel"));
  211. advanced_layout->addWidget(spectrumResolutionKnob, 1, 0, 1, 1, Qt::AlignCenter);
  212. // Peak falloff speed
  213. Knob *peakDecayFactorKnob = new Knob(knobSmall_17, this);
  214. peakDecayFactorKnob->setModel(&controls->m_peakDecayFactorModel);
  215. peakDecayFactorKnob->setLabel(tr("Falloff factor"));
  216. peakDecayFactorKnob->setToolTip(tr("Decrease to make peaks fall faster."));
  217. peakDecayFactorKnob->setHintText(tr("Multiply buffered value by"), "");
  218. advanced_layout->addWidget(peakDecayFactorKnob, 0, 1, 1, 1, Qt::AlignCenter);
  219. // Averaging weight
  220. Knob *averagingWeightKnob = new Knob(knobSmall_17, this);
  221. averagingWeightKnob->setModel(&controls->m_averagingWeightModel);
  222. averagingWeightKnob->setLabel(tr("Averaging weight"));
  223. averagingWeightKnob->setToolTip(tr("Decrease to make averaging slower and smoother."));
  224. averagingWeightKnob->setHintText(tr("New sample contributes"), "");
  225. advanced_layout->addWidget(averagingWeightKnob, 1, 1, 1, 1, Qt::AlignCenter);
  226. // Waterfall history size
  227. Knob *waterfallHeightKnob = new Knob(knobSmall_17, this);
  228. waterfallHeightKnob->setModel(&controls->m_waterfallHeightModel);
  229. waterfallHeightKnob->setLabel(tr("Waterfall height"));
  230. waterfallHeightKnob->setToolTip(tr("Increase to get slower scrolling, decrease to see fast transitions better. Warning: medium CPU usage."));
  231. waterfallHeightKnob->setHintText(tr("Keep"), tr(" lines"));
  232. advanced_layout->addWidget(waterfallHeightKnob, 0, 2, 1, 1, Qt::AlignCenter);
  233. processor->reallocateBuffers();
  234. connect(&controls->m_waterfallHeightModel, &FloatModel::dataChanged, [=] {processor->reallocateBuffers();});
  235. // Waterfall gamma correction
  236. Knob *waterfallGammaKnob = new Knob(knobSmall_17, this);
  237. waterfallGammaKnob->setModel(&controls->m_waterfallGammaModel);
  238. waterfallGammaKnob->setLabel(tr("Waterfall gamma"));
  239. waterfallGammaKnob->setToolTip(tr("Decrease to see very weak signals, increase to get better contrast."));
  240. waterfallGammaKnob->setHintText(tr("Gamma value:"), "");
  241. advanced_layout->addWidget(waterfallGammaKnob, 1, 2, 1, 1, Qt::AlignCenter);
  242. // FFT window overlap
  243. Knob *windowOverlapKnob = new Knob(knobSmall_17, this);
  244. windowOverlapKnob->setModel(&controls->m_windowOverlapModel);
  245. windowOverlapKnob->setLabel(tr("Window overlap"));
  246. windowOverlapKnob->setToolTip(tr("Increase to prevent missing fast transitions arriving near FFT window edges. Warning: high CPU usage."));
  247. windowOverlapKnob->setHintText(tr("Each sample processed"), tr(" times"));
  248. advanced_layout->addWidget(windowOverlapKnob, 0, 3, 1, 1, Qt::AlignCenter);
  249. // FFT zero padding
  250. Knob *zeroPaddingKnob = new Knob(knobSmall_17, this);
  251. zeroPaddingKnob->setModel(&controls->m_zeroPaddingModel);
  252. zeroPaddingKnob->setLabel(tr("Zero padding"));
  253. zeroPaddingKnob->setToolTip(tr("Increase to get smoother-looking spectrum. Warning: high CPU usage."));
  254. zeroPaddingKnob->setHintText(tr("Processing buffer is"), tr(" steps larger than input block"));
  255. advanced_layout->addWidget(zeroPaddingKnob, 1, 3, 1, 1, Qt::AlignCenter);
  256. processor->reallocateBuffers();
  257. connect(&controls->m_zeroPaddingModel, &FloatModel::dataChanged, [=] {processor->reallocateBuffers();});
  258. // Advanced settings button
  259. PixmapButton *advancedButton = new PixmapButton(this, tr("Advanced settings"));
  260. advancedButton->setToolTip(tr("Access advanced settings"));
  261. QPixmap *advancedOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("advanced_on").scaled(advButtonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  262. QPixmap *advancedOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("advanced_off").scaled(advButtonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
  263. advancedOnPixmap->setDevicePixelRatio(devicePixelRatio());
  264. advancedOffPixmap->setDevicePixelRatio(devicePixelRatio());
  265. advancedButton->setActiveGraphic(*advancedOnPixmap);
  266. advancedButton->setInactiveGraphic(*advancedOffPixmap);
  267. advancedButton->setCheckable(true);
  268. controls_layout->addStretch(0);
  269. controls_layout->addWidget(advancedButton);
  270. connect(advancedButton, &PixmapButton::toggled, [=](bool checked)
  271. {
  272. if (checked)
  273. {
  274. config_widget->hide();
  275. advanced_widget->show();
  276. }
  277. else
  278. {
  279. config_widget->show();
  280. advanced_widget->hide();
  281. }
  282. }
  283. );
  284. // QSplitter middle and bottom: spectrum display widgets
  285. m_spectrum = new SaSpectrumView(controls, processor, this);
  286. display_splitter->addWidget(m_spectrum);
  287. m_waterfall = new SaWaterfallView(controls, processor, this);
  288. display_splitter->addWidget(m_waterfall);
  289. m_waterfall->setVisible(m_controls->m_waterfallModel.value());
  290. connect(&controls->m_waterfallModel, &BoolModel::dataChanged, [=] {m_waterfall->updateVisibility();});
  291. }
  292. // Suggest the best current widget size.
  293. QSize SaControlsDialog::sizeHint() const
  294. {
  295. // Best width is determined by spectrum display sizeHint.
  296. // Best height depends on whether waterfall is visible and
  297. // consists of heights of the config section, spectrum, waterfall
  298. // and some reserve for margins.
  299. if (m_waterfall->isVisible())
  300. {
  301. return QSize(m_spectrum->sizeHint().width(),
  302. m_configHeight + m_spectrum->sizeHint().height() + m_waterfall->sizeHint().height() + 50);
  303. }
  304. else
  305. {
  306. return QSize(m_spectrum->sizeHint().width(),
  307. m_configHeight + m_spectrum->sizeHint().height() + 50);
  308. }
  309. }