volume-control.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. #include "volume-control.hpp"
  2. #include "qt-wrappers.hpp"
  3. #include "obs-app.hpp"
  4. #include "mute-checkbox.hpp"
  5. #include "slider-absoluteset-style.hpp"
  6. #include <QFontDatabase>
  7. #include <QHBoxLayout>
  8. #include <QPushButton>
  9. #include <QSlider>
  10. #include <QLabel>
  11. #include <QPainter>
  12. using namespace std;
  13. #define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
  14. QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer;
  15. void VolControl::OBSVolumeChanged(void *data, float db)
  16. {
  17. Q_UNUSED(db);
  18. VolControl *volControl = static_cast<VolControl*>(data);
  19. QMetaObject::invokeMethod(volControl, "VolumeChanged");
  20. }
  21. void VolControl::OBSVolumeLevel(void *data,
  22. const float magnitude[MAX_AUDIO_CHANNELS],
  23. const float peak[MAX_AUDIO_CHANNELS],
  24. const float inputPeak[MAX_AUDIO_CHANNELS])
  25. {
  26. VolControl *volControl = static_cast<VolControl*>(data);
  27. volControl->volMeter->setLevels(magnitude, peak, inputPeak);
  28. }
  29. void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata)
  30. {
  31. VolControl *volControl = static_cast<VolControl*>(data);
  32. bool muted = calldata_bool(calldata, "muted");
  33. QMetaObject::invokeMethod(volControl, "VolumeMuted",
  34. Q_ARG(bool, muted));
  35. }
  36. void VolControl::VolumeChanged()
  37. {
  38. slider->blockSignals(true);
  39. slider->setValue((int) (obs_fader_get_deflection(obs_fader) * 100.0f));
  40. slider->blockSignals(false);
  41. updateText();
  42. }
  43. void VolControl::VolumeMuted(bool muted)
  44. {
  45. if (mute->isChecked() != muted)
  46. mute->setChecked(muted);
  47. }
  48. void VolControl::SetMuted(bool checked)
  49. {
  50. obs_source_set_muted(source, checked);
  51. }
  52. void VolControl::SliderChanged(int vol)
  53. {
  54. obs_fader_set_deflection(obs_fader, float(vol) * 0.01f);
  55. updateText();
  56. }
  57. void VolControl::updateText()
  58. {
  59. QString db = QString::number(obs_fader_get_db(obs_fader), 'f', 1)
  60. .append(" dB");
  61. volLabel->setText(db);
  62. bool muted = obs_source_muted(source);
  63. const char *accTextLookup = muted
  64. ? "VolControl.SliderMuted"
  65. : "VolControl.SliderUnmuted";
  66. QString sourceName = obs_source_get_name(source);
  67. QString accText = QTStr(accTextLookup).arg(sourceName, db);
  68. slider->setAccessibleName(accText);
  69. }
  70. QString VolControl::GetName() const
  71. {
  72. return nameLabel->text();
  73. }
  74. void VolControl::SetName(const QString &newName)
  75. {
  76. nameLabel->setText(newName);
  77. }
  78. void VolControl::EmitConfigClicked()
  79. {
  80. emit ConfigClicked();
  81. }
  82. void VolControl::SetMeterDecayRate(qreal q)
  83. {
  84. volMeter->setPeakDecayRate(q);
  85. }
  86. void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  87. {
  88. volMeter->setPeakMeterType(peakMeterType);
  89. }
  90. VolControl::VolControl(OBSSource source_, bool showConfig)
  91. : source (std::move(source_)),
  92. levelTotal (0.0f),
  93. levelCount (0.0f),
  94. obs_fader (obs_fader_create(OBS_FADER_CUBIC)),
  95. obs_volmeter (obs_volmeter_create(OBS_FADER_LOG))
  96. {
  97. QHBoxLayout *volLayout = new QHBoxLayout();
  98. QVBoxLayout *mainLayout = new QVBoxLayout();
  99. QHBoxLayout *textLayout = new QHBoxLayout();
  100. QHBoxLayout *botLayout = new QHBoxLayout();
  101. nameLabel = new QLabel();
  102. volLabel = new QLabel();
  103. volMeter = new VolumeMeter(nullptr, obs_volmeter);
  104. mute = new MuteCheckBox();
  105. slider = new QSlider(Qt::Horizontal);
  106. QFont font = nameLabel->font();
  107. font.setPointSize(font.pointSize()-1);
  108. QString sourceName = obs_source_get_name(source);
  109. setObjectName(sourceName);
  110. nameLabel->setText(sourceName);
  111. nameLabel->setFont(font);
  112. volLabel->setFont(font);
  113. slider->setMinimum(0);
  114. slider->setMaximum(100);
  115. // slider->setMaximumHeight(13);
  116. textLayout->setContentsMargins(0, 0, 0, 0);
  117. textLayout->addWidget(nameLabel);
  118. textLayout->addWidget(volLabel);
  119. textLayout->setAlignment(nameLabel, Qt::AlignLeft);
  120. textLayout->setAlignment(volLabel, Qt::AlignRight);
  121. bool muted = obs_source_muted(source);
  122. mute->setChecked(muted);
  123. mute->setAccessibleName(
  124. QTStr("VolControl.Mute").arg(sourceName));
  125. volLayout->addWidget(slider);
  126. volLayout->addWidget(mute);
  127. volLayout->setSpacing(5);
  128. botLayout->setContentsMargins(0, 0, 0, 0);
  129. botLayout->setSpacing(0);
  130. botLayout->addLayout(volLayout);
  131. if (showConfig) {
  132. config = new QPushButton(this);
  133. config->setProperty("themeID", "configIconSmall");
  134. config->setFlat(true);
  135. config->setSizePolicy(QSizePolicy::Maximum,
  136. QSizePolicy::Maximum);
  137. config->setMaximumSize(22, 22);
  138. config->setAutoDefault(false);
  139. config->setAccessibleName(QTStr("VolControl.Properties")
  140. .arg(sourceName));
  141. connect(config, &QAbstractButton::clicked,
  142. this, &VolControl::EmitConfigClicked);
  143. botLayout->addWidget(config);
  144. }
  145. mainLayout->setContentsMargins(4, 4, 4, 4);
  146. mainLayout->setSpacing(2);
  147. mainLayout->addItem(textLayout);
  148. mainLayout->addWidget(volMeter);
  149. mainLayout->addItem(botLayout);
  150. setLayout(mainLayout);
  151. obs_fader_add_callback(obs_fader, OBSVolumeChanged, this);
  152. obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this);
  153. signal_handler_connect(obs_source_get_signal_handler(source),
  154. "mute", OBSVolumeMuted, this);
  155. QWidget::connect(slider, SIGNAL(valueChanged(int)),
  156. this, SLOT(SliderChanged(int)));
  157. QWidget::connect(mute, SIGNAL(clicked(bool)),
  158. this, SLOT(SetMuted(bool)));
  159. obs_fader_attach_source(obs_fader, source);
  160. obs_volmeter_attach_source(obs_volmeter, source);
  161. slider->setStyle(new SliderAbsoluteSetStyle(slider->style()));
  162. /* Call volume changed once to init the slider position and label */
  163. VolumeChanged();
  164. }
  165. VolControl::~VolControl()
  166. {
  167. obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
  168. obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this);
  169. signal_handler_disconnect(obs_source_get_signal_handler(source),
  170. "mute", OBSVolumeMuted, this);
  171. obs_fader_destroy(obs_fader);
  172. obs_volmeter_destroy(obs_volmeter);
  173. }
  174. QColor VolumeMeter::getBackgroundNominalColor() const
  175. {
  176. return backgroundNominalColor;
  177. }
  178. void VolumeMeter::setBackgroundNominalColor(QColor c)
  179. {
  180. backgroundNominalColor = std::move(c);
  181. }
  182. QColor VolumeMeter::getBackgroundWarningColor() const
  183. {
  184. return backgroundWarningColor;
  185. }
  186. void VolumeMeter::setBackgroundWarningColor(QColor c)
  187. {
  188. backgroundWarningColor = std::move(c);
  189. }
  190. QColor VolumeMeter::getBackgroundErrorColor() const
  191. {
  192. return backgroundErrorColor;
  193. }
  194. void VolumeMeter::setBackgroundErrorColor(QColor c)
  195. {
  196. backgroundErrorColor = std::move(c);
  197. }
  198. QColor VolumeMeter::getForegroundNominalColor() const
  199. {
  200. return foregroundNominalColor;
  201. }
  202. void VolumeMeter::setForegroundNominalColor(QColor c)
  203. {
  204. foregroundNominalColor = std::move(c);
  205. }
  206. QColor VolumeMeter::getForegroundWarningColor() const
  207. {
  208. return foregroundWarningColor;
  209. }
  210. void VolumeMeter::setForegroundWarningColor(QColor c)
  211. {
  212. foregroundWarningColor = std::move(c);
  213. }
  214. QColor VolumeMeter::getForegroundErrorColor() const
  215. {
  216. return foregroundErrorColor;
  217. }
  218. void VolumeMeter::setForegroundErrorColor(QColor c)
  219. {
  220. foregroundErrorColor = std::move(c);
  221. }
  222. QColor VolumeMeter::getClipColor() const
  223. {
  224. return clipColor;
  225. }
  226. void VolumeMeter::setClipColor(QColor c)
  227. {
  228. clipColor = std::move(c);
  229. }
  230. QColor VolumeMeter::getMagnitudeColor() const
  231. {
  232. return magnitudeColor;
  233. }
  234. void VolumeMeter::setMagnitudeColor(QColor c)
  235. {
  236. magnitudeColor = std::move(c);
  237. }
  238. QColor VolumeMeter::getMajorTickColor() const
  239. {
  240. return majorTickColor;
  241. }
  242. void VolumeMeter::setMajorTickColor(QColor c)
  243. {
  244. majorTickColor = std::move(c);
  245. }
  246. QColor VolumeMeter::getMinorTickColor() const
  247. {
  248. return minorTickColor;
  249. }
  250. void VolumeMeter::setMinorTickColor(QColor c)
  251. {
  252. minorTickColor = std::move(c);
  253. }
  254. qreal VolumeMeter::getMinimumLevel() const
  255. {
  256. return minimumLevel;
  257. }
  258. void VolumeMeter::setMinimumLevel(qreal v)
  259. {
  260. minimumLevel = v;
  261. }
  262. qreal VolumeMeter::getWarningLevel() const
  263. {
  264. return warningLevel;
  265. }
  266. void VolumeMeter::setWarningLevel(qreal v)
  267. {
  268. warningLevel = v;
  269. }
  270. qreal VolumeMeter::getErrorLevel() const
  271. {
  272. return errorLevel;
  273. }
  274. void VolumeMeter::setErrorLevel(qreal v)
  275. {
  276. errorLevel = v;
  277. }
  278. qreal VolumeMeter::getClipLevel() const
  279. {
  280. return clipLevel;
  281. }
  282. void VolumeMeter::setClipLevel(qreal v)
  283. {
  284. clipLevel = v;
  285. }
  286. qreal VolumeMeter::getMinimumInputLevel() const
  287. {
  288. return minimumInputLevel;
  289. }
  290. void VolumeMeter::setMinimumInputLevel(qreal v)
  291. {
  292. minimumInputLevel = v;
  293. }
  294. qreal VolumeMeter::getPeakDecayRate() const
  295. {
  296. return peakDecayRate;
  297. }
  298. void VolumeMeter::setPeakDecayRate(qreal v)
  299. {
  300. peakDecayRate = v;
  301. }
  302. qreal VolumeMeter::getMagnitudeIntegrationTime() const
  303. {
  304. return magnitudeIntegrationTime;
  305. }
  306. void VolumeMeter::setMagnitudeIntegrationTime(qreal v)
  307. {
  308. magnitudeIntegrationTime = v;
  309. }
  310. qreal VolumeMeter::getPeakHoldDuration() const
  311. {
  312. return peakHoldDuration;
  313. }
  314. void VolumeMeter::setPeakHoldDuration(qreal v)
  315. {
  316. peakHoldDuration = v;
  317. }
  318. qreal VolumeMeter::getInputPeakHoldDuration() const
  319. {
  320. return inputPeakHoldDuration;
  321. }
  322. void VolumeMeter::setInputPeakHoldDuration(qreal v)
  323. {
  324. inputPeakHoldDuration = v;
  325. }
  326. void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  327. {
  328. obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType);
  329. switch (peakMeterType) {
  330. case TRUE_PEAK_METER:
  331. // For true-peak meters EBU has defined the Permitted Maximum,
  332. // taking into account the accuracy of the meter and further
  333. // processing required by lossy audio compression.
  334. //
  335. // The alignment level was not specified, but I've adjusted
  336. // it compared to a sample-peak meter. Incidently Youtube
  337. // uses this new Alignment Level as the maximum integrated
  338. // loudness of a video.
  339. //
  340. // * Permitted Maximum Level (PML) = -2.0 dBTP
  341. // * Alignment Level (AL) = -13 dBTP
  342. setErrorLevel(-2.0);
  343. setWarningLevel(-13.0);
  344. break;
  345. case SAMPLE_PEAK_METER:
  346. default:
  347. // For a sample Peak Meter EBU has the following level
  348. // definitions, taking into account inaccuracies of this meter:
  349. //
  350. // * Permitted Maximum Level (PML) = -9.0 dBFS
  351. // * Alignment Level (AL) = -20.0 dBFS
  352. setErrorLevel(-9.0);
  353. setWarningLevel(-20.0);
  354. break;
  355. }
  356. }
  357. VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter)
  358. : QWidget(parent), obs_volmeter(obs_volmeter)
  359. {
  360. // Use a font that can be rendered small.
  361. tickFont = QFont("Arial");
  362. tickFont.setPixelSize(7);
  363. // Default meter color settings, they only show if
  364. // there is no stylesheet, do not remove.
  365. backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
  366. backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
  367. backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red
  368. foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green
  369. foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
  370. foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red
  371. clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
  372. magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
  373. majorTickColor.setRgb(0xff, 0xff, 0xff); // Black
  374. minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black
  375. minimumLevel = -60.0; // -60 dB
  376. warningLevel = -20.0; // -20 dB
  377. errorLevel = -9.0; // -9 dB
  378. clipLevel = -0.5; // -0.5 dB
  379. minimumInputLevel = -50.0; // -50 dB
  380. peakDecayRate = 11.76; // 20 dB / 1.7 sec
  381. magnitudeIntegrationTime = 0.3; // 99% in 300 ms
  382. peakHoldDuration = 20.0; // 20 seconds
  383. inputPeakHoldDuration = 1.0; // 1 second
  384. handleChannelCofigurationChange();
  385. updateTimerRef = updateTimer.toStrongRef();
  386. if (!updateTimerRef) {
  387. updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
  388. updateTimerRef->start(34);
  389. updateTimer = updateTimerRef;
  390. }
  391. updateTimerRef->AddVolControl(this);
  392. }
  393. VolumeMeter::~VolumeMeter()
  394. {
  395. updateTimerRef->RemoveVolControl(this);
  396. }
  397. void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
  398. const float peak[MAX_AUDIO_CHANNELS],
  399. const float inputPeak[MAX_AUDIO_CHANNELS])
  400. {
  401. uint64_t ts = os_gettime_ns();
  402. QMutexLocker locker(&dataMutex);
  403. currentLastUpdateTime = ts;
  404. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  405. currentMagnitude[channelNr] = magnitude[channelNr];
  406. currentPeak[channelNr] = peak[channelNr];
  407. currentInputPeak[channelNr] = inputPeak[channelNr];
  408. }
  409. // In case there are more updates then redraws we must make sure
  410. // that the ballistics of peak and hold are recalculated.
  411. locker.unlock();
  412. calculateBallistics(ts);
  413. }
  414. inline void VolumeMeter::resetLevels()
  415. {
  416. currentLastUpdateTime = 0;
  417. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  418. currentMagnitude[channelNr] = -M_INFINITE;
  419. currentPeak[channelNr] = -M_INFINITE;
  420. currentInputPeak[channelNr] = -M_INFINITE;
  421. displayMagnitude[channelNr] = -M_INFINITE;
  422. displayPeak[channelNr] = -M_INFINITE;
  423. displayPeakHold[channelNr] = -M_INFINITE;
  424. displayPeakHoldLastUpdateTime[channelNr] = 0;
  425. displayInputPeakHold[channelNr] = -M_INFINITE;
  426. displayInputPeakHoldLastUpdateTime[channelNr] = 0;
  427. }
  428. }
  429. inline void VolumeMeter::handleChannelCofigurationChange()
  430. {
  431. QMutexLocker locker(&dataMutex);
  432. int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
  433. if (displayNrAudioChannels != currentNrAudioChannels) {
  434. displayNrAudioChannels = currentNrAudioChannels;
  435. // Make room for 3 pixels high meter, with one pixel between
  436. // each. Then 9 pixels below it for ticks and numbers.
  437. setMinimumSize(130, displayNrAudioChannels * 4 + 8);
  438. resetLevels();
  439. }
  440. }
  441. inline bool VolumeMeter::detectIdle(uint64_t ts)
  442. {
  443. double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001;
  444. if (timeSinceLastUpdate > 0.5) {
  445. resetLevels();
  446. return true;
  447. } else {
  448. return false;
  449. }
  450. }
  451. inline void VolumeMeter::calculateBallisticsForChannel(int channelNr,
  452. uint64_t ts, qreal timeSinceLastRedraw)
  453. {
  454. if (currentPeak[channelNr] >= displayPeak[channelNr] ||
  455. isnan(displayPeak[channelNr])) {
  456. // Attack of peak is immediate.
  457. displayPeak[channelNr] = currentPeak[channelNr];
  458. } else {
  459. // Decay of peak is 40 dB / 1.7 seconds for Fast Profile
  460. // 20 dB / 1.7 seconds for Medium Profile (Type I PPM)
  461. // 24 dB / 2.8 seconds for Slow Profile (Type II PPM)
  462. float decay = float(peakDecayRate * timeSinceLastRedraw);
  463. displayPeak[channelNr] = CLAMP(displayPeak[channelNr] - decay,
  464. currentPeak[channelNr], 0);
  465. }
  466. if (currentPeak[channelNr] >= displayPeakHold[channelNr] ||
  467. !isfinite(displayPeakHold[channelNr])) {
  468. // Attack of peak-hold is immediate, but keep track
  469. // when it was last updated.
  470. displayPeakHold[channelNr] = currentPeak[channelNr];
  471. displayPeakHoldLastUpdateTime[channelNr] = ts;
  472. } else {
  473. // The peak and hold falls back to peak
  474. // after 20 seconds.
  475. qreal timeSinceLastPeak = (uint64_t)(ts -
  476. displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001;
  477. if (timeSinceLastPeak > peakHoldDuration) {
  478. displayPeakHold[channelNr] = currentPeak[channelNr];
  479. displayPeakHoldLastUpdateTime[channelNr] = ts;
  480. }
  481. }
  482. if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] ||
  483. !isfinite(displayInputPeakHold[channelNr])) {
  484. // Attack of peak-hold is immediate, but keep track
  485. // when it was last updated.
  486. displayInputPeakHold[channelNr] = currentInputPeak[channelNr];
  487. displayInputPeakHoldLastUpdateTime[channelNr] = ts;
  488. } else {
  489. // The peak and hold falls back to peak after 1 second.
  490. qreal timeSinceLastPeak = (uint64_t)(ts -
  491. displayInputPeakHoldLastUpdateTime[channelNr]) *
  492. 0.000000001;
  493. if (timeSinceLastPeak > inputPeakHoldDuration) {
  494. displayInputPeakHold[channelNr] =
  495. currentInputPeak[channelNr];
  496. displayInputPeakHoldLastUpdateTime[channelNr] =
  497. ts;
  498. }
  499. }
  500. if (!isfinite(displayMagnitude[channelNr])) {
  501. // The statements in the else-leg do not work with
  502. // NaN and infinite displayMagnitude.
  503. displayMagnitude[channelNr] = currentMagnitude[channelNr];
  504. } else {
  505. // A VU meter will integrate to the new value to 99% in 300 ms.
  506. // The calculation here is very simplified and is more accurate
  507. // with higher frame-rate.
  508. float attack = float((currentMagnitude[channelNr] -
  509. displayMagnitude[channelNr]) *
  510. (timeSinceLastRedraw /
  511. magnitudeIntegrationTime) * 0.99);
  512. displayMagnitude[channelNr] = CLAMP(displayMagnitude[channelNr]
  513. + attack, (float)minimumLevel, 0);
  514. }
  515. }
  516. inline void VolumeMeter::calculateBallistics(uint64_t ts,
  517. qreal timeSinceLastRedraw)
  518. {
  519. QMutexLocker locker(&dataMutex);
  520. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++)
  521. calculateBallisticsForChannel(channelNr, ts,
  522. timeSinceLastRedraw);
  523. }
  524. void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
  525. int height, float peakHold)
  526. {
  527. QMutexLocker locker(&dataMutex);
  528. if (peakHold < minimumInputLevel)
  529. painter.fillRect(x, y, width, height, backgroundNominalColor);
  530. else if (peakHold < warningLevel)
  531. painter.fillRect(x, y, width, height, foregroundNominalColor);
  532. else if (peakHold < errorLevel)
  533. painter.fillRect(x, y, width, height, foregroundWarningColor);
  534. else if (peakHold <= clipLevel)
  535. painter.fillRect(x, y, width, height, foregroundErrorColor);
  536. else
  537. painter.fillRect(x, y, width, height, clipColor);
  538. }
  539. void VolumeMeter::paintTicks(QPainter &painter, int x, int y, int width,
  540. int height)
  541. {
  542. qreal scale = width / minimumLevel;
  543. painter.setFont(tickFont);
  544. painter.setPen(majorTickColor);
  545. // Draw major tick lines and numeric indicators.
  546. for (int i = 0; i >= minimumLevel; i-= 5) {
  547. int position = int(x + width - (i * scale) - 1);
  548. QString str = QString::number(i);
  549. if (i == 0 || i == -5)
  550. painter.drawText(position - 3, height, str);
  551. else
  552. painter.drawText(position - 5, height, str);
  553. painter.drawLine(position, y, position, y + 2);
  554. }
  555. // Draw minor tick lines.
  556. painter.setPen(minorTickColor);
  557. for (int i = 0; i >= minimumLevel; i--) {
  558. int position = int(x + width - (i * scale) - 1);
  559. if (i % 5 != 0)
  560. painter.drawLine(position, y, position, y + 1);
  561. }
  562. }
  563. #define CLIP_FLASH_DURATION_MS 1000
  564. void VolumeMeter::ClipEnding()
  565. {
  566. clipping = false;
  567. }
  568. void VolumeMeter::paintMeter(QPainter &painter, int x, int y, int width,
  569. int height, float magnitude, float peak, float peakHold)
  570. {
  571. qreal scale = width / minimumLevel;
  572. QMutexLocker locker(&dataMutex);
  573. int minimumPosition = x + 0;
  574. int maximumPosition = x + width;
  575. int magnitudePosition = int(x + width - (magnitude * scale));
  576. int peakPosition = int(x + width - (peak * scale));
  577. int peakHoldPosition = int(x + width - (peakHold * scale));
  578. int warningPosition = int(x + width - (warningLevel * scale));
  579. int errorPosition = int(x + width - (errorLevel * scale));
  580. int nominalLength = warningPosition - minimumPosition;
  581. int warningLength = errorPosition - warningPosition;
  582. int errorLength = maximumPosition - errorPosition;
  583. locker.unlock();
  584. if (clipping) {
  585. peakPosition = maximumPosition;
  586. }
  587. if (peakPosition < minimumPosition) {
  588. painter.fillRect(minimumPosition, y, nominalLength, height,
  589. backgroundNominalColor);
  590. painter.fillRect(warningPosition, y, warningLength, height,
  591. backgroundWarningColor);
  592. painter.fillRect(errorPosition, y, errorLength, height,
  593. backgroundErrorColor);
  594. } else if (peakPosition < warningPosition) {
  595. painter.fillRect(minimumPosition, y, peakPosition -
  596. minimumPosition, height,
  597. foregroundNominalColor);
  598. painter.fillRect(peakPosition, y, warningPosition -
  599. peakPosition, height, backgroundNominalColor);
  600. painter.fillRect(warningPosition, y, warningLength, height,
  601. backgroundWarningColor);
  602. painter.fillRect(errorPosition, y, errorLength, height,
  603. backgroundErrorColor);
  604. } else if (peakPosition < errorPosition) {
  605. painter.fillRect(minimumPosition, y, nominalLength, height,
  606. foregroundNominalColor);
  607. painter.fillRect(warningPosition, y,
  608. peakPosition - warningPosition, height,
  609. foregroundWarningColor);
  610. painter.fillRect(peakPosition, y, errorPosition -
  611. peakPosition, height, backgroundWarningColor);
  612. painter.fillRect(errorPosition, y, errorLength, height,
  613. backgroundErrorColor);
  614. } else if (peakPosition < maximumPosition) {
  615. painter.fillRect(minimumPosition, y, nominalLength, height,
  616. foregroundNominalColor);
  617. painter.fillRect(warningPosition, y, warningLength, height,
  618. foregroundWarningColor);
  619. painter.fillRect(errorPosition, y, peakPosition - errorPosition,
  620. height, foregroundErrorColor);
  621. painter.fillRect(peakPosition, y,
  622. maximumPosition - peakPosition, height,
  623. backgroundErrorColor);
  624. } else {
  625. if (!clipping) {
  626. QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
  627. SLOT(ClipEnding()));
  628. clipping = true;
  629. }
  630. int end = errorLength + warningLength + nominalLength;
  631. painter.fillRect(minimumPosition, y, end, height,
  632. QBrush(foregroundErrorColor));
  633. }
  634. if (peakHoldPosition - 3 < minimumPosition)
  635. ;// Peak-hold below minimum, no drawing.
  636. else if (peakHoldPosition < warningPosition)
  637. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  638. foregroundNominalColor);
  639. else if (peakHoldPosition < errorPosition)
  640. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  641. foregroundWarningColor);
  642. else
  643. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  644. foregroundErrorColor);
  645. if (magnitudePosition - 3 < minimumPosition)
  646. ;// Magnitude below minimum, no drawing.
  647. else if (magnitudePosition < warningPosition)
  648. painter.fillRect(magnitudePosition - 3, y, 3, height,
  649. magnitudeColor);
  650. else if (magnitudePosition < errorPosition)
  651. painter.fillRect(magnitudePosition - 3, y, 3, height,
  652. magnitudeColor);
  653. else
  654. painter.fillRect(magnitudePosition - 3, y, 3, height,
  655. magnitudeColor);
  656. }
  657. void VolumeMeter::paintEvent(QPaintEvent *event)
  658. {
  659. UNUSED_PARAMETER(event);
  660. uint64_t ts = os_gettime_ns();
  661. qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001;
  662. int width = size().width();
  663. int height = size().height();
  664. handleChannelCofigurationChange();
  665. calculateBallistics(ts, timeSinceLastRedraw);
  666. bool idle = detectIdle(ts);
  667. // Draw the ticks in a off-screen buffer when the widget changes size.
  668. QSize tickPaintCacheSize = QSize(width, 9);
  669. if (tickPaintCache == nullptr ||
  670. tickPaintCache->size() != tickPaintCacheSize) {
  671. delete tickPaintCache;
  672. tickPaintCache = new QPixmap(tickPaintCacheSize);
  673. QColor clearColor(0, 0, 0, 0);
  674. tickPaintCache->fill(clearColor);
  675. QPainter tickPainter(tickPaintCache);
  676. paintTicks(tickPainter, 6, 0, tickPaintCacheSize.width() - 6,
  677. tickPaintCacheSize.height());
  678. tickPainter.end();
  679. }
  680. // Actual painting of the widget starts here.
  681. QPainter painter(this);
  682. painter.drawPixmap(0, height - 9, *tickPaintCache);
  683. for (int channelNr = 0; channelNr < displayNrAudioChannels;
  684. channelNr++) {
  685. paintMeter(painter, 5, channelNr * 4, width - 5, 3,
  686. displayMagnitude[channelNr],
  687. displayPeak[channelNr],
  688. displayPeakHold[channelNr]);
  689. if (!idle) {
  690. // By not drawing the input meter boxes the user can
  691. // see that the audio stream has been stopped, without
  692. // having too much visual impact.
  693. paintInputMeter(painter, 0, channelNr * 4, 3, 3,
  694. displayInputPeakHold[channelNr]);
  695. }
  696. }
  697. lastRedrawTime = ts;
  698. }
  699. void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
  700. {
  701. volumeMeters.push_back(meter);
  702. }
  703. void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
  704. {
  705. volumeMeters.removeOne(meter);
  706. }
  707. void VolumeMeterTimer::timerEvent(QTimerEvent*)
  708. {
  709. for (VolumeMeter *meter : volumeMeters)
  710. meter->update();
  711. }