volume-control.cpp 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  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, bool vertical)
  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. vertical (vertical)
  97. {
  98. nameLabel = new QLabel();
  99. volLabel = new QLabel();
  100. mute = new MuteCheckBox();
  101. QString sourceName = obs_source_get_name(source);
  102. setObjectName(sourceName);
  103. if (showConfig) {
  104. config = new QPushButton(this);
  105. config->setProperty("themeID", "configIconSmall");
  106. config->setFlat(true);
  107. config->setSizePolicy(QSizePolicy::Maximum,
  108. QSizePolicy::Maximum);
  109. config->setMaximumSize(22, 22);
  110. config->setAutoDefault(false);
  111. config->setAccessibleName(QTStr("VolControl.Properties")
  112. .arg(sourceName));
  113. connect(config, &QAbstractButton::clicked,
  114. this, &VolControl::EmitConfigClicked);
  115. }
  116. QVBoxLayout *mainLayout = new QVBoxLayout;
  117. mainLayout->setContentsMargins(4, 4, 4, 4);
  118. mainLayout->setSpacing(2);
  119. if (vertical) {
  120. QHBoxLayout *nameLayout = new QHBoxLayout;
  121. QHBoxLayout *controlLayout = new QHBoxLayout;
  122. QHBoxLayout *volLayout = new QHBoxLayout;
  123. QHBoxLayout *meterLayout = new QHBoxLayout;
  124. volMeter = new VolumeMeter(nullptr, obs_volmeter, true);
  125. slider = new QSlider(Qt::Vertical);
  126. nameLayout->setAlignment(Qt::AlignCenter);
  127. meterLayout->setAlignment(Qt::AlignCenter);
  128. controlLayout->setAlignment(Qt::AlignCenter);
  129. volLayout->setAlignment(Qt::AlignCenter);
  130. nameLayout->setContentsMargins(0, 0, 0, 0);
  131. nameLayout->setSpacing(0);
  132. nameLayout->addWidget(nameLabel);
  133. controlLayout->setContentsMargins(0, 0, 0, 0);
  134. controlLayout->setSpacing(0);
  135. if (showConfig)
  136. controlLayout->addWidget(config);
  137. controlLayout->addItem(new QSpacerItem(3, 0));
  138. // Add Headphone (audio monitoring) widget here
  139. controlLayout->addWidget(mute);
  140. meterLayout->setContentsMargins(0, 0, 0, 0);
  141. meterLayout->setSpacing(0);
  142. meterLayout->addWidget(volMeter);
  143. meterLayout->addWidget(slider);
  144. volLayout->setContentsMargins(0, 0, 0, 0);
  145. volLayout->setSpacing(0);
  146. volLayout->addWidget(volLabel);
  147. mainLayout->addItem(nameLayout);
  148. mainLayout->addItem(volLayout);
  149. mainLayout->addItem(meterLayout);
  150. mainLayout->addItem(controlLayout);
  151. setMaximumWidth(110);
  152. } else {
  153. QHBoxLayout *volLayout = new QHBoxLayout;
  154. QHBoxLayout *textLayout = new QHBoxLayout;
  155. QHBoxLayout *botLayout = new QHBoxLayout;
  156. volMeter = new VolumeMeter(nullptr, obs_volmeter, false);
  157. slider = new QSlider(Qt::Horizontal);
  158. textLayout->setContentsMargins(0, 0, 0, 0);
  159. textLayout->addWidget(nameLabel);
  160. textLayout->addWidget(volLabel);
  161. textLayout->setAlignment(nameLabel, Qt::AlignLeft);
  162. textLayout->setAlignment(volLabel, Qt::AlignRight);
  163. volLayout->addWidget(slider);
  164. volLayout->addWidget(mute);
  165. volLayout->setSpacing(5);
  166. botLayout->setContentsMargins(0, 0, 0, 0);
  167. botLayout->setSpacing(0);
  168. botLayout->addLayout(volLayout);
  169. if (showConfig)
  170. botLayout->addWidget(config);
  171. mainLayout->addItem(textLayout);
  172. mainLayout->addWidget(volMeter);
  173. mainLayout->addItem(botLayout);
  174. }
  175. setLayout(mainLayout);
  176. QFont font = nameLabel->font();
  177. font.setPointSize(font.pointSize()-1);
  178. nameLabel->setText(sourceName);
  179. nameLabel->setFont(font);
  180. volLabel->setFont(font);
  181. slider->setMinimum(0);
  182. slider->setMaximum(100);
  183. bool muted = obs_source_muted(source);
  184. mute->setChecked(muted);
  185. mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName));
  186. obs_fader_add_callback(obs_fader, OBSVolumeChanged, this);
  187. obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this);
  188. signal_handler_connect(obs_source_get_signal_handler(source),
  189. "mute", OBSVolumeMuted, this);
  190. QWidget::connect(slider, SIGNAL(valueChanged(int)),
  191. this, SLOT(SliderChanged(int)));
  192. QWidget::connect(mute, SIGNAL(clicked(bool)),
  193. this, SLOT(SetMuted(bool)));
  194. obs_fader_attach_source(obs_fader, source);
  195. obs_volmeter_attach_source(obs_volmeter, source);
  196. slider->setStyle(new SliderAbsoluteSetStyle(slider->style()));
  197. /* Call volume changed once to init the slider position and label */
  198. VolumeChanged();
  199. }
  200. VolControl::~VolControl()
  201. {
  202. obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
  203. obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this);
  204. signal_handler_disconnect(obs_source_get_signal_handler(source),
  205. "mute", OBSVolumeMuted, this);
  206. obs_fader_destroy(obs_fader);
  207. obs_volmeter_destroy(obs_volmeter);
  208. }
  209. QColor VolumeMeter::getBackgroundNominalColor() const
  210. {
  211. return backgroundNominalColor;
  212. }
  213. void VolumeMeter::setBackgroundNominalColor(QColor c)
  214. {
  215. backgroundNominalColor = std::move(c);
  216. }
  217. QColor VolumeMeter::getBackgroundWarningColor() const
  218. {
  219. return backgroundWarningColor;
  220. }
  221. void VolumeMeter::setBackgroundWarningColor(QColor c)
  222. {
  223. backgroundWarningColor = std::move(c);
  224. }
  225. QColor VolumeMeter::getBackgroundErrorColor() const
  226. {
  227. return backgroundErrorColor;
  228. }
  229. void VolumeMeter::setBackgroundErrorColor(QColor c)
  230. {
  231. backgroundErrorColor = std::move(c);
  232. }
  233. QColor VolumeMeter::getForegroundNominalColor() const
  234. {
  235. return foregroundNominalColor;
  236. }
  237. void VolumeMeter::setForegroundNominalColor(QColor c)
  238. {
  239. foregroundNominalColor = std::move(c);
  240. }
  241. QColor VolumeMeter::getForegroundWarningColor() const
  242. {
  243. return foregroundWarningColor;
  244. }
  245. void VolumeMeter::setForegroundWarningColor(QColor c)
  246. {
  247. foregroundWarningColor = std::move(c);
  248. }
  249. QColor VolumeMeter::getForegroundErrorColor() const
  250. {
  251. return foregroundErrorColor;
  252. }
  253. void VolumeMeter::setForegroundErrorColor(QColor c)
  254. {
  255. foregroundErrorColor = std::move(c);
  256. }
  257. QColor VolumeMeter::getClipColor() const
  258. {
  259. return clipColor;
  260. }
  261. void VolumeMeter::setClipColor(QColor c)
  262. {
  263. clipColor = std::move(c);
  264. }
  265. QColor VolumeMeter::getMagnitudeColor() const
  266. {
  267. return magnitudeColor;
  268. }
  269. void VolumeMeter::setMagnitudeColor(QColor c)
  270. {
  271. magnitudeColor = std::move(c);
  272. }
  273. QColor VolumeMeter::getMajorTickColor() const
  274. {
  275. return majorTickColor;
  276. }
  277. void VolumeMeter::setMajorTickColor(QColor c)
  278. {
  279. majorTickColor = std::move(c);
  280. }
  281. QColor VolumeMeter::getMinorTickColor() const
  282. {
  283. return minorTickColor;
  284. }
  285. void VolumeMeter::setMinorTickColor(QColor c)
  286. {
  287. minorTickColor = std::move(c);
  288. }
  289. qreal VolumeMeter::getMinimumLevel() const
  290. {
  291. return minimumLevel;
  292. }
  293. void VolumeMeter::setMinimumLevel(qreal v)
  294. {
  295. minimumLevel = v;
  296. }
  297. qreal VolumeMeter::getWarningLevel() const
  298. {
  299. return warningLevel;
  300. }
  301. void VolumeMeter::setWarningLevel(qreal v)
  302. {
  303. warningLevel = v;
  304. }
  305. qreal VolumeMeter::getErrorLevel() const
  306. {
  307. return errorLevel;
  308. }
  309. void VolumeMeter::setErrorLevel(qreal v)
  310. {
  311. errorLevel = v;
  312. }
  313. qreal VolumeMeter::getClipLevel() const
  314. {
  315. return clipLevel;
  316. }
  317. void VolumeMeter::setClipLevel(qreal v)
  318. {
  319. clipLevel = v;
  320. }
  321. qreal VolumeMeter::getMinimumInputLevel() const
  322. {
  323. return minimumInputLevel;
  324. }
  325. void VolumeMeter::setMinimumInputLevel(qreal v)
  326. {
  327. minimumInputLevel = v;
  328. }
  329. qreal VolumeMeter::getPeakDecayRate() const
  330. {
  331. return peakDecayRate;
  332. }
  333. void VolumeMeter::setPeakDecayRate(qreal v)
  334. {
  335. peakDecayRate = v;
  336. }
  337. qreal VolumeMeter::getMagnitudeIntegrationTime() const
  338. {
  339. return magnitudeIntegrationTime;
  340. }
  341. void VolumeMeter::setMagnitudeIntegrationTime(qreal v)
  342. {
  343. magnitudeIntegrationTime = v;
  344. }
  345. qreal VolumeMeter::getPeakHoldDuration() const
  346. {
  347. return peakHoldDuration;
  348. }
  349. void VolumeMeter::setPeakHoldDuration(qreal v)
  350. {
  351. peakHoldDuration = v;
  352. }
  353. qreal VolumeMeter::getInputPeakHoldDuration() const
  354. {
  355. return inputPeakHoldDuration;
  356. }
  357. void VolumeMeter::setInputPeakHoldDuration(qreal v)
  358. {
  359. inputPeakHoldDuration = v;
  360. }
  361. void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  362. {
  363. obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType);
  364. switch (peakMeterType) {
  365. case TRUE_PEAK_METER:
  366. // For true-peak meters EBU has defined the Permitted Maximum,
  367. // taking into account the accuracy of the meter and further
  368. // processing required by lossy audio compression.
  369. //
  370. // The alignment level was not specified, but I've adjusted
  371. // it compared to a sample-peak meter. Incidently Youtube
  372. // uses this new Alignment Level as the maximum integrated
  373. // loudness of a video.
  374. //
  375. // * Permitted Maximum Level (PML) = -2.0 dBTP
  376. // * Alignment Level (AL) = -13 dBTP
  377. setErrorLevel(-2.0);
  378. setWarningLevel(-13.0);
  379. break;
  380. case SAMPLE_PEAK_METER:
  381. default:
  382. // For a sample Peak Meter EBU has the following level
  383. // definitions, taking into account inaccuracies of this meter:
  384. //
  385. // * Permitted Maximum Level (PML) = -9.0 dBFS
  386. // * Alignment Level (AL) = -20.0 dBFS
  387. setErrorLevel(-9.0);
  388. setWarningLevel(-20.0);
  389. break;
  390. }
  391. }
  392. VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
  393. bool vertical)
  394. : QWidget(parent), obs_volmeter(obs_volmeter),
  395. vertical(vertical)
  396. {
  397. // Use a font that can be rendered small.
  398. tickFont = QFont("Arial");
  399. tickFont.setPixelSize(7);
  400. // Default meter color settings, they only show if
  401. // there is no stylesheet, do not remove.
  402. backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
  403. backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
  404. backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red
  405. foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green
  406. foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
  407. foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red
  408. clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
  409. magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
  410. majorTickColor.setRgb(0xff, 0xff, 0xff); // Black
  411. minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black
  412. minimumLevel = -60.0; // -60 dB
  413. warningLevel = -20.0; // -20 dB
  414. errorLevel = -9.0; // -9 dB
  415. clipLevel = -0.5; // -0.5 dB
  416. minimumInputLevel = -50.0; // -50 dB
  417. peakDecayRate = 11.76; // 20 dB / 1.7 sec
  418. magnitudeIntegrationTime = 0.3; // 99% in 300 ms
  419. peakHoldDuration = 20.0; // 20 seconds
  420. inputPeakHoldDuration = 1.0; // 1 second
  421. handleChannelCofigurationChange();
  422. updateTimerRef = updateTimer.toStrongRef();
  423. if (!updateTimerRef) {
  424. updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
  425. updateTimerRef->start(34);
  426. updateTimer = updateTimerRef;
  427. }
  428. updateTimerRef->AddVolControl(this);
  429. }
  430. VolumeMeter::~VolumeMeter()
  431. {
  432. updateTimerRef->RemoveVolControl(this);
  433. }
  434. void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
  435. const float peak[MAX_AUDIO_CHANNELS],
  436. const float inputPeak[MAX_AUDIO_CHANNELS])
  437. {
  438. uint64_t ts = os_gettime_ns();
  439. QMutexLocker locker(&dataMutex);
  440. currentLastUpdateTime = ts;
  441. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  442. currentMagnitude[channelNr] = magnitude[channelNr];
  443. currentPeak[channelNr] = peak[channelNr];
  444. currentInputPeak[channelNr] = inputPeak[channelNr];
  445. }
  446. // In case there are more updates then redraws we must make sure
  447. // that the ballistics of peak and hold are recalculated.
  448. locker.unlock();
  449. calculateBallistics(ts);
  450. }
  451. inline void VolumeMeter::resetLevels()
  452. {
  453. currentLastUpdateTime = 0;
  454. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  455. currentMagnitude[channelNr] = -M_INFINITE;
  456. currentPeak[channelNr] = -M_INFINITE;
  457. currentInputPeak[channelNr] = -M_INFINITE;
  458. displayMagnitude[channelNr] = -M_INFINITE;
  459. displayPeak[channelNr] = -M_INFINITE;
  460. displayPeakHold[channelNr] = -M_INFINITE;
  461. displayPeakHoldLastUpdateTime[channelNr] = 0;
  462. displayInputPeakHold[channelNr] = -M_INFINITE;
  463. displayInputPeakHoldLastUpdateTime[channelNr] = 0;
  464. }
  465. }
  466. inline void VolumeMeter::handleChannelCofigurationChange()
  467. {
  468. QMutexLocker locker(&dataMutex);
  469. int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
  470. if (displayNrAudioChannels != currentNrAudioChannels) {
  471. displayNrAudioChannels = currentNrAudioChannels;
  472. // Make room for 3 pixels meter, with one pixel between each.
  473. // Then 9/13 pixels for ticks and numbers.
  474. if (vertical)
  475. setMinimumSize(displayNrAudioChannels * 4 + 14, 130);
  476. else
  477. setMinimumSize(130, displayNrAudioChannels * 4 + 8);
  478. resetLevels();
  479. }
  480. }
  481. inline bool VolumeMeter::detectIdle(uint64_t ts)
  482. {
  483. double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001;
  484. if (timeSinceLastUpdate > 0.5) {
  485. resetLevels();
  486. return true;
  487. } else {
  488. return false;
  489. }
  490. }
  491. inline void VolumeMeter::calculateBallisticsForChannel(int channelNr,
  492. uint64_t ts, qreal timeSinceLastRedraw)
  493. {
  494. if (currentPeak[channelNr] >= displayPeak[channelNr] ||
  495. isnan(displayPeak[channelNr])) {
  496. // Attack of peak is immediate.
  497. displayPeak[channelNr] = currentPeak[channelNr];
  498. } else {
  499. // Decay of peak is 40 dB / 1.7 seconds for Fast Profile
  500. // 20 dB / 1.7 seconds for Medium Profile (Type I PPM)
  501. // 24 dB / 2.8 seconds for Slow Profile (Type II PPM)
  502. float decay = float(peakDecayRate * timeSinceLastRedraw);
  503. displayPeak[channelNr] = CLAMP(displayPeak[channelNr] - decay,
  504. currentPeak[channelNr], 0);
  505. }
  506. if (currentPeak[channelNr] >= displayPeakHold[channelNr] ||
  507. !isfinite(displayPeakHold[channelNr])) {
  508. // Attack of peak-hold is immediate, but keep track
  509. // when it was last updated.
  510. displayPeakHold[channelNr] = currentPeak[channelNr];
  511. displayPeakHoldLastUpdateTime[channelNr] = ts;
  512. } else {
  513. // The peak and hold falls back to peak
  514. // after 20 seconds.
  515. qreal timeSinceLastPeak = (uint64_t)(ts -
  516. displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001;
  517. if (timeSinceLastPeak > peakHoldDuration) {
  518. displayPeakHold[channelNr] = currentPeak[channelNr];
  519. displayPeakHoldLastUpdateTime[channelNr] = ts;
  520. }
  521. }
  522. if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] ||
  523. !isfinite(displayInputPeakHold[channelNr])) {
  524. // Attack of peak-hold is immediate, but keep track
  525. // when it was last updated.
  526. displayInputPeakHold[channelNr] = currentInputPeak[channelNr];
  527. displayInputPeakHoldLastUpdateTime[channelNr] = ts;
  528. } else {
  529. // The peak and hold falls back to peak after 1 second.
  530. qreal timeSinceLastPeak = (uint64_t)(ts -
  531. displayInputPeakHoldLastUpdateTime[channelNr]) *
  532. 0.000000001;
  533. if (timeSinceLastPeak > inputPeakHoldDuration) {
  534. displayInputPeakHold[channelNr] =
  535. currentInputPeak[channelNr];
  536. displayInputPeakHoldLastUpdateTime[channelNr] =
  537. ts;
  538. }
  539. }
  540. if (!isfinite(displayMagnitude[channelNr])) {
  541. // The statements in the else-leg do not work with
  542. // NaN and infinite displayMagnitude.
  543. displayMagnitude[channelNr] = currentMagnitude[channelNr];
  544. } else {
  545. // A VU meter will integrate to the new value to 99% in 300 ms.
  546. // The calculation here is very simplified and is more accurate
  547. // with higher frame-rate.
  548. float attack = float((currentMagnitude[channelNr] -
  549. displayMagnitude[channelNr]) *
  550. (timeSinceLastRedraw /
  551. magnitudeIntegrationTime) * 0.99);
  552. displayMagnitude[channelNr] = CLAMP(displayMagnitude[channelNr]
  553. + attack, (float)minimumLevel, 0);
  554. }
  555. }
  556. inline void VolumeMeter::calculateBallistics(uint64_t ts,
  557. qreal timeSinceLastRedraw)
  558. {
  559. QMutexLocker locker(&dataMutex);
  560. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++)
  561. calculateBallisticsForChannel(channelNr, ts,
  562. timeSinceLastRedraw);
  563. }
  564. void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
  565. int height, float peakHold)
  566. {
  567. QMutexLocker locker(&dataMutex);
  568. QColor color;
  569. if (peakHold < minimumInputLevel)
  570. color = backgroundNominalColor;
  571. else if (peakHold < warningLevel)
  572. color = foregroundNominalColor;
  573. else if (peakHold < errorLevel)
  574. color = foregroundWarningColor;
  575. else if (peakHold <= clipLevel)
  576. color = foregroundErrorColor;
  577. else
  578. color = clipColor;
  579. painter.fillRect(x, y, width, height, color);
  580. }
  581. void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width,
  582. int height)
  583. {
  584. qreal scale = width / minimumLevel;
  585. painter.setFont(tickFont);
  586. painter.setPen(majorTickColor);
  587. // Draw major tick lines and numeric indicators.
  588. for (int i = 0; i >= minimumLevel; i-= 5) {
  589. int position = int(x + width - (i * scale) - 1);
  590. QString str = QString::number(i);
  591. if (i == 0 || i == -5)
  592. painter.drawText(position - 3, height, str);
  593. else
  594. painter.drawText(position - 5, height, str);
  595. painter.drawLine(position, y, position, y + 2);
  596. }
  597. // Draw minor tick lines.
  598. painter.setPen(minorTickColor);
  599. for (int i = 0; i >= minimumLevel; i--) {
  600. int position = int(x + width - (i * scale) - 1);
  601. if (i % 5 != 0)
  602. painter.drawLine(position, y, position, y + 1);
  603. }
  604. }
  605. void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height)
  606. {
  607. qreal scale = height / minimumLevel;
  608. painter.setFont(tickFont);
  609. painter.setPen(majorTickColor);
  610. // Draw major tick lines and numeric indicators.
  611. for (int i = 0; i >= minimumLevel; i-= 5) {
  612. int position = y + int((i * scale) - 1);
  613. QString str = QString::number(i);
  614. if (i == 0)
  615. painter.drawText(x + 5, position + 4, str);
  616. else if (i == -60)
  617. painter.drawText(x + 4, position, str);
  618. else
  619. painter.drawText(x + 4, position + 2, str);
  620. painter.drawLine(x, position, x + 2, position);
  621. }
  622. // Draw minor tick lines.
  623. painter.setPen(minorTickColor);
  624. for (int i = 0; i >= minimumLevel; i--) {
  625. int position = y + int((i * scale) - 1);
  626. if (i % 5 != 0)
  627. painter.drawLine(x, position, x + 1, position);
  628. }
  629. }
  630. #define CLIP_FLASH_DURATION_MS 1000
  631. void VolumeMeter::ClipEnding()
  632. {
  633. clipping = false;
  634. }
  635. void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width,
  636. int height, float magnitude, float peak, float peakHold)
  637. {
  638. qreal scale = width / minimumLevel;
  639. QMutexLocker locker(&dataMutex);
  640. int minimumPosition = x + 0;
  641. int maximumPosition = x + width;
  642. int magnitudePosition = int(x + width - (magnitude * scale));
  643. int peakPosition = int(x + width - (peak * scale));
  644. int peakHoldPosition = int(x + width - (peakHold * scale));
  645. int warningPosition = int(x + width - (warningLevel * scale));
  646. int errorPosition = int(x + width - (errorLevel * scale));
  647. int nominalLength = warningPosition - minimumPosition;
  648. int warningLength = errorPosition - warningPosition;
  649. int errorLength = maximumPosition - errorPosition;
  650. locker.unlock();
  651. if (clipping) {
  652. peakPosition = maximumPosition;
  653. }
  654. if (peakPosition < minimumPosition) {
  655. painter.fillRect(minimumPosition, y, nominalLength, height,
  656. backgroundNominalColor);
  657. painter.fillRect(warningPosition, y, warningLength, height,
  658. backgroundWarningColor);
  659. painter.fillRect(errorPosition, y, errorLength, height,
  660. backgroundErrorColor);
  661. } else if (peakPosition < warningPosition) {
  662. painter.fillRect(minimumPosition, y, peakPosition -
  663. minimumPosition, height,
  664. foregroundNominalColor);
  665. painter.fillRect(peakPosition, y, warningPosition -
  666. peakPosition, height, backgroundNominalColor);
  667. painter.fillRect(warningPosition, y, warningLength, height,
  668. backgroundWarningColor);
  669. painter.fillRect(errorPosition, y, errorLength, height,
  670. backgroundErrorColor);
  671. } else if (peakPosition < errorPosition) {
  672. painter.fillRect(minimumPosition, y, nominalLength, height,
  673. foregroundNominalColor);
  674. painter.fillRect(warningPosition, y,
  675. peakPosition - warningPosition, height,
  676. foregroundWarningColor);
  677. painter.fillRect(peakPosition, y, errorPosition -
  678. peakPosition, height, backgroundWarningColor);
  679. painter.fillRect(errorPosition, y, errorLength, height,
  680. backgroundErrorColor);
  681. } else if (peakPosition < maximumPosition) {
  682. painter.fillRect(minimumPosition, y, nominalLength, height,
  683. foregroundNominalColor);
  684. painter.fillRect(warningPosition, y, warningLength, height,
  685. foregroundWarningColor);
  686. painter.fillRect(errorPosition, y, peakPosition - errorPosition,
  687. height, foregroundErrorColor);
  688. painter.fillRect(peakPosition, y,
  689. maximumPosition - peakPosition, height,
  690. backgroundErrorColor);
  691. } else {
  692. if (!clipping) {
  693. QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
  694. SLOT(ClipEnding()));
  695. clipping = true;
  696. }
  697. int end = errorLength + warningLength + nominalLength;
  698. painter.fillRect(minimumPosition, y, end, height,
  699. QBrush(foregroundErrorColor));
  700. }
  701. if (peakHoldPosition - 3 < minimumPosition)
  702. ;// Peak-hold below minimum, no drawing.
  703. else if (peakHoldPosition < warningPosition)
  704. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  705. foregroundNominalColor);
  706. else if (peakHoldPosition < errorPosition)
  707. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  708. foregroundWarningColor);
  709. else
  710. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  711. foregroundErrorColor);
  712. if (magnitudePosition - 3 >= minimumPosition)
  713. painter.fillRect(magnitudePosition - 3, y, 3, height,
  714. magnitudeColor);
  715. }
  716. void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width,
  717. int height, float magnitude, float peak, float peakHold)
  718. {
  719. qreal scale = height / minimumLevel;
  720. QMutexLocker locker(&dataMutex);
  721. int minimumPosition = y + 0;
  722. int maximumPosition = y + height;
  723. int magnitudePosition = int(y + height - (magnitude * scale));
  724. int peakPosition = int(y + height - (peak * scale));
  725. int peakHoldPosition = int(y + height - (peakHold * scale));
  726. int warningPosition = int(y + height - (warningLevel * scale));
  727. int errorPosition = int(y + height - (errorLevel * scale));
  728. int nominalLength = warningPosition - minimumPosition;
  729. int warningLength = errorPosition - warningPosition;
  730. int errorLength = maximumPosition - errorPosition;
  731. locker.unlock();
  732. if (clipping) {
  733. peakPosition = maximumPosition;
  734. }
  735. if (peakPosition < minimumPosition) {
  736. painter.fillRect(x, minimumPosition, width, nominalLength,
  737. backgroundNominalColor);
  738. painter.fillRect(x, warningPosition, width, warningLength,
  739. backgroundWarningColor);
  740. painter.fillRect(x, errorPosition, width, errorLength,
  741. backgroundErrorColor);
  742. } else if (peakPosition < warningPosition) {
  743. painter.fillRect(x, minimumPosition, width, peakPosition -
  744. minimumPosition, foregroundNominalColor);
  745. painter.fillRect(x, peakPosition, width, warningPosition -
  746. peakPosition, backgroundNominalColor);
  747. painter.fillRect(x, warningPosition, width, warningLength,
  748. backgroundWarningColor);
  749. painter.fillRect(x, errorPosition, width, errorLength,
  750. backgroundErrorColor);
  751. } else if (peakPosition < errorPosition) {
  752. painter.fillRect(x,minimumPosition, width, nominalLength,
  753. foregroundNominalColor);
  754. painter.fillRect(x, warningPosition, width, peakPosition -
  755. warningPosition, foregroundWarningColor);
  756. painter.fillRect(x, peakPosition, width, errorPosition -
  757. peakPosition, backgroundWarningColor);
  758. painter.fillRect(x, errorPosition, width, errorLength,
  759. backgroundErrorColor);
  760. } else if (peakPosition < maximumPosition) {
  761. painter.fillRect(x, minimumPosition, width, nominalLength,
  762. foregroundNominalColor);
  763. painter.fillRect(x, warningPosition, width, warningLength,
  764. foregroundWarningColor);
  765. painter.fillRect(x, errorPosition, width, peakPosition -
  766. errorPosition, foregroundErrorColor);
  767. painter.fillRect(x, peakPosition, width, maximumPosition -
  768. peakPosition, backgroundErrorColor);
  769. } else {
  770. if (!clipping) {
  771. QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
  772. SLOT(ClipEnding()));
  773. clipping = true;
  774. }
  775. int end = errorLength + warningLength + nominalLength;
  776. painter.fillRect(x, minimumPosition, width, end,
  777. QBrush(foregroundErrorColor));
  778. }
  779. if (peakHoldPosition - 3 < minimumPosition)
  780. ;// Peak-hold below minimum, no drawing.
  781. else if (peakHoldPosition < warningPosition)
  782. painter.fillRect(x, peakHoldPosition - 3, width, 3,
  783. foregroundNominalColor);
  784. else if (peakHoldPosition < errorPosition)
  785. painter.fillRect(x, peakHoldPosition - 3, width, 3,
  786. foregroundWarningColor);
  787. else
  788. painter.fillRect(x, peakHoldPosition - 3, width, 3,
  789. foregroundErrorColor);
  790. if (magnitudePosition - 3 >= minimumPosition)
  791. painter.fillRect(x, magnitudePosition - 3, width, 3,
  792. magnitudeColor);
  793. }
  794. void VolumeMeter::paintEvent(QPaintEvent *event)
  795. {
  796. uint64_t ts = os_gettime_ns();
  797. qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001;
  798. const QRect rect = event->region().boundingRect();
  799. int width = rect.width();
  800. int height = rect.height();
  801. handleChannelCofigurationChange();
  802. calculateBallistics(ts, timeSinceLastRedraw);
  803. bool idle = detectIdle(ts);
  804. // Draw the ticks in a off-screen buffer when the widget changes size.
  805. QSize tickPaintCacheSize;
  806. if (vertical)
  807. tickPaintCacheSize = QSize(14, height);
  808. else
  809. tickPaintCacheSize = QSize(width, 9);
  810. if (tickPaintCache == nullptr ||
  811. tickPaintCache->size() != tickPaintCacheSize) {
  812. delete tickPaintCache;
  813. tickPaintCache = new QPixmap(tickPaintCacheSize);
  814. QColor clearColor(0, 0, 0, 0);
  815. tickPaintCache->fill(clearColor);
  816. QPainter tickPainter(tickPaintCache);
  817. if (vertical) {
  818. tickPainter.translate(0, height);
  819. tickPainter.scale(1, -1);
  820. paintVTicks(tickPainter, 0, 11,
  821. tickPaintCacheSize.height() - 11);
  822. } else {
  823. paintHTicks(tickPainter, 6, 0,
  824. tickPaintCacheSize.width() - 6,
  825. tickPaintCacheSize.height());
  826. }
  827. tickPainter.end();
  828. }
  829. // Actual painting of the widget starts here.
  830. QPainter painter(this);
  831. if (vertical) {
  832. // Invert the Y axis to ease the math
  833. painter.translate(0, height);
  834. painter.scale(1, -1);
  835. painter.drawPixmap(displayNrAudioChannels * 4 - 1, 7,
  836. *tickPaintCache);
  837. } else {
  838. painter.drawPixmap(0, height - 9, *tickPaintCache);
  839. }
  840. for (int channelNr = 0; channelNr < displayNrAudioChannels;
  841. channelNr++) {
  842. if (vertical)
  843. paintVMeter(painter, channelNr * 4, 8, 3, height - 10,
  844. displayMagnitude[channelNr],
  845. displayPeak[channelNr],
  846. displayPeakHold[channelNr]);
  847. else
  848. paintHMeter(painter, 5, channelNr * 4, width - 5, 3,
  849. displayMagnitude[channelNr],
  850. displayPeak[channelNr],
  851. displayPeakHold[channelNr]);
  852. if (idle)
  853. continue;
  854. // By not drawing the input meter boxes the user can
  855. // see that the audio stream has been stopped, without
  856. // having too much visual impact.
  857. if (vertical)
  858. paintInputMeter(painter, channelNr * 4, 3, 3, 3,
  859. displayInputPeakHold[channelNr]);
  860. else
  861. paintInputMeter(painter, 0, channelNr * 4, 3, 3,
  862. displayInputPeakHold[channelNr]);
  863. }
  864. lastRedrawTime = ts;
  865. }
  866. void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
  867. {
  868. volumeMeters.push_back(meter);
  869. }
  870. void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
  871. {
  872. volumeMeters.removeOne(meter);
  873. }
  874. void VolumeMeterTimer::timerEvent(QTimerEvent*)
  875. {
  876. for (VolumeMeter *meter : volumeMeters)
  877. meter->update();
  878. }