volume-control.cpp 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594
  1. #include "window-basic-main.hpp"
  2. #include "volume-control.hpp"
  3. #include "qt-wrappers.hpp"
  4. #include "obs-app.hpp"
  5. #include "mute-checkbox.hpp"
  6. #include "slider-ignorewheel.hpp"
  7. #include "slider-absoluteset-style.hpp"
  8. #include "source-label.hpp"
  9. #include <QFontDatabase>
  10. #include <QHBoxLayout>
  11. #include <QPushButton>
  12. #include <QLabel>
  13. #include <QPainter>
  14. #include <QStyleFactory>
  15. using namespace std;
  16. #define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
  17. #define FADER_PRECISION 4096.0
  18. // Size of the audio indicator in pixels
  19. #define INDICATOR_THICKNESS 3
  20. // Padding on top and bottom of vertical meters
  21. #define METER_PADDING 1
  22. std::weak_ptr<VolumeMeterTimer> VolumeMeter::updateTimer;
  23. static inline Qt::CheckState GetCheckState(bool muted, bool unassigned)
  24. {
  25. if (muted)
  26. return Qt::Checked;
  27. else if (unassigned)
  28. return Qt::PartiallyChecked;
  29. else
  30. return Qt::Unchecked;
  31. }
  32. static inline bool IsSourceUnassigned(obs_source_t *source)
  33. {
  34. uint32_t mixes = (obs_source_get_audio_mixers(source) &
  35. ((1 << MAX_AUDIO_MIXES) - 1));
  36. obs_monitoring_type mt = obs_source_get_monitoring_type(source);
  37. return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY;
  38. }
  39. static void ShowUnassignedWarning(const char *name)
  40. {
  41. auto msgBox = [=]() {
  42. QMessageBox msgbox(App()->GetMainWindow());
  43. msgbox.setWindowTitle(
  44. QTStr("VolControl.UnassignedWarning.Title"));
  45. msgbox.setText(
  46. QTStr("VolControl.UnassignedWarning.Text").arg(name));
  47. msgbox.setIcon(QMessageBox::Icon::Information);
  48. msgbox.addButton(QMessageBox::Ok);
  49. QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain"));
  50. msgbox.setCheckBox(cb);
  51. msgbox.exec();
  52. if (cb->isChecked()) {
  53. config_set_bool(App()->GlobalConfig(), "General",
  54. "WarnedAboutUnassignedSources", true);
  55. config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
  56. }
  57. };
  58. QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection,
  59. Q_ARG(VoidFunc, msgBox));
  60. }
  61. void VolControl::OBSVolumeChanged(void *data, float db)
  62. {
  63. Q_UNUSED(db);
  64. VolControl *volControl = static_cast<VolControl *>(data);
  65. QMetaObject::invokeMethod(volControl, "VolumeChanged");
  66. }
  67. void VolControl::OBSVolumeLevel(void *data,
  68. const float magnitude[MAX_AUDIO_CHANNELS],
  69. const float peak[MAX_AUDIO_CHANNELS],
  70. const float inputPeak[MAX_AUDIO_CHANNELS])
  71. {
  72. VolControl *volControl = static_cast<VolControl *>(data);
  73. volControl->volMeter->setLevels(magnitude, peak, inputPeak);
  74. }
  75. void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata)
  76. {
  77. VolControl *volControl = static_cast<VolControl *>(data);
  78. bool muted = calldata_bool(calldata, "muted");
  79. QMetaObject::invokeMethod(volControl, "VolumeMuted",
  80. Q_ARG(bool, muted));
  81. }
  82. void VolControl::VolumeChanged()
  83. {
  84. slider->blockSignals(true);
  85. slider->setValue(
  86. (int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION));
  87. slider->blockSignals(false);
  88. updateText();
  89. }
  90. void VolControl::VolumeMuted(bool muted)
  91. {
  92. bool unassigned = IsSourceUnassigned(source);
  93. auto newState = GetCheckState(muted, unassigned);
  94. if (mute->checkState() != newState)
  95. mute->setCheckState(newState);
  96. volMeter->muted = muted || unassigned;
  97. }
  98. void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *)
  99. {
  100. VolControl *volControl = static_cast<VolControl *>(data);
  101. QMetaObject::invokeMethod(volControl, "MixersOrMonitoringChanged",
  102. Qt::QueuedConnection);
  103. }
  104. void VolControl::MixersOrMonitoringChanged()
  105. {
  106. bool muted = obs_source_muted(source);
  107. bool unassigned = IsSourceUnassigned(source);
  108. auto newState = GetCheckState(muted, unassigned);
  109. if (mute->checkState() != newState)
  110. mute->setCheckState(newState);
  111. volMeter->muted = muted || unassigned;
  112. }
  113. void VolControl::SetMuted(bool)
  114. {
  115. bool checked = mute->checkState() == Qt::Checked;
  116. bool prev = obs_source_muted(source);
  117. obs_source_set_muted(source, checked);
  118. bool unassigned = IsSourceUnassigned(source);
  119. if (!checked && unassigned) {
  120. mute->setCheckState(Qt::PartiallyChecked);
  121. /* Show notice about the source no being assigned to any tracks */
  122. bool has_shown_warning =
  123. config_get_bool(App()->GlobalConfig(), "General",
  124. "WarnedAboutUnassignedSources");
  125. if (!has_shown_warning)
  126. ShowUnassignedWarning(obs_source_get_name(source));
  127. }
  128. auto undo_redo = [](const std::string &uuid, bool val) {
  129. OBSSourceAutoRelease source =
  130. obs_get_source_by_uuid(uuid.c_str());
  131. obs_source_set_muted(source, val);
  132. };
  133. QString text =
  134. QTStr(checked ? "Undo.Volume.Mute" : "Undo.Volume.Unmute");
  135. const char *name = obs_source_get_name(source);
  136. const char *uuid = obs_source_get_uuid(source);
  137. OBSBasic::Get()->undo_s.add_action(
  138. text.arg(name),
  139. std::bind(undo_redo, std::placeholders::_1, prev),
  140. std::bind(undo_redo, std::placeholders::_1, checked), uuid,
  141. uuid);
  142. }
  143. void VolControl::SliderChanged(int vol)
  144. {
  145. float prev = obs_source_get_volume(source);
  146. obs_fader_set_deflection(obs_fader, float(vol) / FADER_PRECISION);
  147. updateText();
  148. auto undo_redo = [](const std::string &uuid, float val) {
  149. OBSSourceAutoRelease source =
  150. obs_get_source_by_uuid(uuid.c_str());
  151. obs_source_set_volume(source, val);
  152. };
  153. float val = obs_source_get_volume(source);
  154. const char *name = obs_source_get_name(source);
  155. const char *uuid = obs_source_get_uuid(source);
  156. OBSBasic::Get()->undo_s.add_action(
  157. QTStr("Undo.Volume.Change").arg(name),
  158. std::bind(undo_redo, std::placeholders::_1, prev),
  159. std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid,
  160. true);
  161. }
  162. void VolControl::updateText()
  163. {
  164. QString text;
  165. float db = obs_fader_get_db(obs_fader);
  166. if (db < -96.0f)
  167. text = "-inf dB";
  168. else
  169. text = QString::number(db, 'f', 1).append(" dB");
  170. volLabel->setText(text);
  171. bool muted = obs_source_muted(source);
  172. const char *accTextLookup = muted ? "VolControl.SliderMuted"
  173. : "VolControl.SliderUnmuted";
  174. QString sourceName = obs_source_get_name(source);
  175. QString accText = QTStr(accTextLookup).arg(sourceName);
  176. slider->setAccessibleName(accText);
  177. }
  178. void VolControl::EmitConfigClicked()
  179. {
  180. emit ConfigClicked();
  181. }
  182. void VolControl::SetMeterDecayRate(qreal q)
  183. {
  184. volMeter->setPeakDecayRate(q);
  185. }
  186. void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  187. {
  188. volMeter->setPeakMeterType(peakMeterType);
  189. }
  190. VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
  191. : source(std::move(source_)),
  192. levelTotal(0.0f),
  193. levelCount(0.0f),
  194. obs_fader(obs_fader_create(OBS_FADER_LOG)),
  195. obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)),
  196. vertical(vertical),
  197. contextMenu(nullptr)
  198. {
  199. nameLabel = new OBSSourceLabel(source);
  200. volLabel = new QLabel();
  201. mute = new MuteCheckBox();
  202. QString sourceName = obs_source_get_name(source);
  203. setObjectName(sourceName);
  204. if (showConfig) {
  205. config = new QPushButton(this);
  206. config->setProperty("themeID", "menuIconSmall");
  207. config->setAutoDefault(false);
  208. config->setAccessibleName(
  209. QTStr("VolControl.Properties").arg(sourceName));
  210. connect(config, &QAbstractButton::clicked, this,
  211. &VolControl::EmitConfigClicked);
  212. }
  213. QVBoxLayout *mainLayout = new QVBoxLayout;
  214. mainLayout->setContentsMargins(4, 4, 4, 4);
  215. mainLayout->setSpacing(2);
  216. if (vertical) {
  217. QHBoxLayout *nameLayout = new QHBoxLayout;
  218. QHBoxLayout *controlLayout = new QHBoxLayout;
  219. QHBoxLayout *volLayout = new QHBoxLayout;
  220. QHBoxLayout *meterLayout = new QHBoxLayout;
  221. volMeter = new VolumeMeter(nullptr, obs_volmeter, true);
  222. slider = new VolumeSlider(obs_fader, Qt::Vertical);
  223. slider->setLayoutDirection(Qt::LeftToRight);
  224. nameLayout->setAlignment(Qt::AlignCenter);
  225. meterLayout->setAlignment(Qt::AlignCenter);
  226. controlLayout->setAlignment(Qt::AlignCenter);
  227. volLayout->setAlignment(Qt::AlignCenter);
  228. nameLayout->setContentsMargins(0, 0, 0, 0);
  229. nameLayout->setSpacing(0);
  230. nameLayout->addWidget(nameLabel);
  231. controlLayout->setContentsMargins(0, 0, 0, 0);
  232. controlLayout->setSpacing(0);
  233. if (showConfig) {
  234. controlLayout->addWidget(config);
  235. controlLayout->setAlignment(config, Qt::AlignVCenter);
  236. }
  237. controlLayout->addItem(new QSpacerItem(3, 0));
  238. // Add Headphone (audio monitoring) widget here
  239. controlLayout->addWidget(mute);
  240. controlLayout->setAlignment(mute, Qt::AlignVCenter);
  241. meterLayout->setContentsMargins(0, 0, 0, 0);
  242. meterLayout->setSpacing(0);
  243. meterLayout->addWidget(volMeter);
  244. meterLayout->addWidget(slider);
  245. volLayout->setContentsMargins(0, 0, 0, 0);
  246. volLayout->setSpacing(0);
  247. volLayout->addWidget(volLabel);
  248. mainLayout->addItem(nameLayout);
  249. mainLayout->addItem(volLayout);
  250. mainLayout->addItem(meterLayout);
  251. mainLayout->addItem(controlLayout);
  252. volMeter->setFocusProxy(slider);
  253. // Default size can cause clipping of long names in vertical layout.
  254. QFont font = nameLabel->font();
  255. QFontInfo info(font);
  256. font.setPointSizeF(0.8 * info.pointSizeF());
  257. nameLabel->setFont(font);
  258. setMaximumWidth(110);
  259. } else {
  260. QHBoxLayout *textLayout = new QHBoxLayout;
  261. QHBoxLayout *botLayout = new QHBoxLayout;
  262. volMeter = new VolumeMeter(nullptr, obs_volmeter, false);
  263. slider = new VolumeSlider(obs_fader, Qt::Horizontal);
  264. slider->setLayoutDirection(Qt::LeftToRight);
  265. textLayout->setContentsMargins(0, 0, 0, 0);
  266. textLayout->addWidget(nameLabel);
  267. textLayout->addWidget(volLabel);
  268. textLayout->setAlignment(nameLabel, Qt::AlignLeft);
  269. textLayout->setAlignment(volLabel, Qt::AlignRight);
  270. botLayout->setContentsMargins(0, 0, 0, 0);
  271. botLayout->setSpacing(5);
  272. botLayout->addWidget(slider);
  273. botLayout->addWidget(mute);
  274. botLayout->setAlignment(slider, Qt::AlignVCenter);
  275. botLayout->setAlignment(mute, Qt::AlignVCenter);
  276. if (showConfig) {
  277. botLayout->addWidget(config);
  278. botLayout->setAlignment(config, Qt::AlignVCenter);
  279. }
  280. mainLayout->addItem(textLayout);
  281. mainLayout->addWidget(volMeter);
  282. mainLayout->addItem(botLayout);
  283. volMeter->setFocusProxy(slider);
  284. }
  285. setLayout(mainLayout);
  286. nameLabel->setText(sourceName);
  287. slider->setMinimum(0);
  288. slider->setMaximum(int(FADER_PRECISION));
  289. bool muted = obs_source_muted(source);
  290. bool unassigned = IsSourceUnassigned(source);
  291. mute->setCheckState(GetCheckState(muted, unassigned));
  292. volMeter->muted = muted || unassigned;
  293. mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName));
  294. obs_fader_add_callback(obs_fader, OBSVolumeChanged, this);
  295. obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this);
  296. sigs.emplace_back(obs_source_get_signal_handler(source), "mute",
  297. OBSVolumeMuted, this);
  298. sigs.emplace_back(obs_source_get_signal_handler(source), "audio_mixers",
  299. OBSMixersOrMonitoringChanged, this);
  300. sigs.emplace_back(obs_source_get_signal_handler(source),
  301. "audio_monitoring", OBSMixersOrMonitoringChanged,
  302. this);
  303. QWidget::connect(slider, &VolumeSlider::valueChanged, this,
  304. &VolControl::SliderChanged);
  305. QWidget::connect(mute, &MuteCheckBox::clicked, this,
  306. &VolControl::SetMuted);
  307. obs_fader_attach_source(obs_fader, source);
  308. obs_volmeter_attach_source(obs_volmeter, source);
  309. QString styleName = slider->style()->objectName();
  310. QStyle *style;
  311. style = QStyleFactory::create(styleName);
  312. if (!style) {
  313. style = new SliderAbsoluteSetStyle();
  314. } else {
  315. style = new SliderAbsoluteSetStyle(style);
  316. }
  317. style->setParent(slider);
  318. slider->setStyle(style);
  319. /* Call volume changed once to init the slider position and label */
  320. VolumeChanged();
  321. }
  322. void VolControl::EnableSlider(bool enable)
  323. {
  324. slider->setEnabled(enable);
  325. }
  326. VolControl::~VolControl()
  327. {
  328. obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
  329. obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this);
  330. sigs.clear();
  331. if (contextMenu)
  332. contextMenu->close();
  333. }
  334. static inline QColor color_from_int(long long val)
  335. {
  336. QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff,
  337. (val >> 24) & 0xff);
  338. color.setAlpha(255);
  339. return color;
  340. }
  341. QColor VolumeMeter::getBackgroundNominalColor() const
  342. {
  343. return p_backgroundNominalColor;
  344. }
  345. QColor VolumeMeter::getBackgroundNominalColorDisabled() const
  346. {
  347. return backgroundNominalColorDisabled;
  348. }
  349. void VolumeMeter::setBackgroundNominalColor(QColor c)
  350. {
  351. p_backgroundNominalColor = std::move(c);
  352. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  353. "OverrideColors")) {
  354. backgroundNominalColor = color_from_int(config_get_int(
  355. GetGlobalConfig(), "Accessibility", "MixerGreen"));
  356. } else {
  357. backgroundNominalColor = p_backgroundNominalColor;
  358. }
  359. }
  360. void VolumeMeter::setBackgroundNominalColorDisabled(QColor c)
  361. {
  362. backgroundNominalColorDisabled = std::move(c);
  363. }
  364. QColor VolumeMeter::getBackgroundWarningColor() const
  365. {
  366. return p_backgroundWarningColor;
  367. }
  368. QColor VolumeMeter::getBackgroundWarningColorDisabled() const
  369. {
  370. return backgroundWarningColorDisabled;
  371. }
  372. void VolumeMeter::setBackgroundWarningColor(QColor c)
  373. {
  374. p_backgroundWarningColor = std::move(c);
  375. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  376. "OverrideColors")) {
  377. backgroundWarningColor = color_from_int(config_get_int(
  378. GetGlobalConfig(), "Accessibility", "MixerYellow"));
  379. } else {
  380. backgroundWarningColor = p_backgroundWarningColor;
  381. }
  382. }
  383. void VolumeMeter::setBackgroundWarningColorDisabled(QColor c)
  384. {
  385. backgroundWarningColorDisabled = std::move(c);
  386. }
  387. QColor VolumeMeter::getBackgroundErrorColor() const
  388. {
  389. return p_backgroundErrorColor;
  390. }
  391. QColor VolumeMeter::getBackgroundErrorColorDisabled() const
  392. {
  393. return backgroundErrorColorDisabled;
  394. }
  395. void VolumeMeter::setBackgroundErrorColor(QColor c)
  396. {
  397. p_backgroundErrorColor = std::move(c);
  398. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  399. "OverrideColors")) {
  400. backgroundErrorColor = color_from_int(config_get_int(
  401. GetGlobalConfig(), "Accessibility", "MixerRed"));
  402. } else {
  403. backgroundErrorColor = p_backgroundErrorColor;
  404. }
  405. }
  406. void VolumeMeter::setBackgroundErrorColorDisabled(QColor c)
  407. {
  408. backgroundErrorColorDisabled = std::move(c);
  409. }
  410. QColor VolumeMeter::getForegroundNominalColor() const
  411. {
  412. return p_foregroundNominalColor;
  413. }
  414. QColor VolumeMeter::getForegroundNominalColorDisabled() const
  415. {
  416. return foregroundNominalColorDisabled;
  417. }
  418. void VolumeMeter::setForegroundNominalColor(QColor c)
  419. {
  420. p_foregroundNominalColor = std::move(c);
  421. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  422. "OverrideColors")) {
  423. foregroundNominalColor = color_from_int(
  424. config_get_int(GetGlobalConfig(), "Accessibility",
  425. "MixerGreenActive"));
  426. } else {
  427. foregroundNominalColor = p_foregroundNominalColor;
  428. }
  429. }
  430. void VolumeMeter::setForegroundNominalColorDisabled(QColor c)
  431. {
  432. foregroundNominalColorDisabled = std::move(c);
  433. }
  434. QColor VolumeMeter::getForegroundWarningColor() const
  435. {
  436. return p_foregroundWarningColor;
  437. }
  438. QColor VolumeMeter::getForegroundWarningColorDisabled() const
  439. {
  440. return foregroundWarningColorDisabled;
  441. }
  442. void VolumeMeter::setForegroundWarningColor(QColor c)
  443. {
  444. p_foregroundWarningColor = std::move(c);
  445. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  446. "OverrideColors")) {
  447. foregroundWarningColor = color_from_int(
  448. config_get_int(GetGlobalConfig(), "Accessibility",
  449. "MixerYellowActive"));
  450. } else {
  451. foregroundWarningColor = p_foregroundWarningColor;
  452. }
  453. }
  454. void VolumeMeter::setForegroundWarningColorDisabled(QColor c)
  455. {
  456. foregroundWarningColorDisabled = std::move(c);
  457. }
  458. QColor VolumeMeter::getForegroundErrorColor() const
  459. {
  460. return p_foregroundErrorColor;
  461. }
  462. QColor VolumeMeter::getForegroundErrorColorDisabled() const
  463. {
  464. return foregroundErrorColorDisabled;
  465. }
  466. void VolumeMeter::setForegroundErrorColor(QColor c)
  467. {
  468. p_foregroundErrorColor = std::move(c);
  469. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  470. "OverrideColors")) {
  471. foregroundErrorColor = color_from_int(config_get_int(
  472. GetGlobalConfig(), "Accessibility", "MixerRedActive"));
  473. } else {
  474. foregroundErrorColor = p_foregroundErrorColor;
  475. }
  476. }
  477. void VolumeMeter::setForegroundErrorColorDisabled(QColor c)
  478. {
  479. foregroundErrorColorDisabled = std::move(c);
  480. }
  481. QColor VolumeMeter::getClipColor() const
  482. {
  483. return clipColor;
  484. }
  485. void VolumeMeter::setClipColor(QColor c)
  486. {
  487. clipColor = std::move(c);
  488. }
  489. QColor VolumeMeter::getMagnitudeColor() const
  490. {
  491. return magnitudeColor;
  492. }
  493. void VolumeMeter::setMagnitudeColor(QColor c)
  494. {
  495. magnitudeColor = std::move(c);
  496. }
  497. QColor VolumeMeter::getMajorTickColor() const
  498. {
  499. return majorTickColor;
  500. }
  501. void VolumeMeter::setMajorTickColor(QColor c)
  502. {
  503. majorTickColor = std::move(c);
  504. }
  505. QColor VolumeMeter::getMinorTickColor() const
  506. {
  507. return minorTickColor;
  508. }
  509. void VolumeMeter::setMinorTickColor(QColor c)
  510. {
  511. minorTickColor = std::move(c);
  512. }
  513. int VolumeMeter::getMeterThickness() const
  514. {
  515. return meterThickness;
  516. }
  517. void VolumeMeter::setMeterThickness(int v)
  518. {
  519. meterThickness = v;
  520. recalculateLayout = true;
  521. }
  522. qreal VolumeMeter::getMeterFontScaling() const
  523. {
  524. return meterFontScaling;
  525. }
  526. void VolumeMeter::setMeterFontScaling(qreal v)
  527. {
  528. meterFontScaling = v;
  529. recalculateLayout = true;
  530. }
  531. void VolControl::refreshColors()
  532. {
  533. volMeter->setBackgroundNominalColor(
  534. volMeter->getBackgroundNominalColor());
  535. volMeter->setBackgroundWarningColor(
  536. volMeter->getBackgroundWarningColor());
  537. volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor());
  538. volMeter->setForegroundNominalColor(
  539. volMeter->getForegroundNominalColor());
  540. volMeter->setForegroundWarningColor(
  541. volMeter->getForegroundWarningColor());
  542. volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor());
  543. }
  544. qreal VolumeMeter::getMinimumLevel() const
  545. {
  546. return minimumLevel;
  547. }
  548. void VolumeMeter::setMinimumLevel(qreal v)
  549. {
  550. minimumLevel = v;
  551. }
  552. qreal VolumeMeter::getWarningLevel() const
  553. {
  554. return warningLevel;
  555. }
  556. void VolumeMeter::setWarningLevel(qreal v)
  557. {
  558. warningLevel = v;
  559. }
  560. qreal VolumeMeter::getErrorLevel() const
  561. {
  562. return errorLevel;
  563. }
  564. void VolumeMeter::setErrorLevel(qreal v)
  565. {
  566. errorLevel = v;
  567. }
  568. qreal VolumeMeter::getClipLevel() const
  569. {
  570. return clipLevel;
  571. }
  572. void VolumeMeter::setClipLevel(qreal v)
  573. {
  574. clipLevel = v;
  575. }
  576. qreal VolumeMeter::getMinimumInputLevel() const
  577. {
  578. return minimumInputLevel;
  579. }
  580. void VolumeMeter::setMinimumInputLevel(qreal v)
  581. {
  582. minimumInputLevel = v;
  583. }
  584. qreal VolumeMeter::getPeakDecayRate() const
  585. {
  586. return peakDecayRate;
  587. }
  588. void VolumeMeter::setPeakDecayRate(qreal v)
  589. {
  590. peakDecayRate = v;
  591. }
  592. qreal VolumeMeter::getMagnitudeIntegrationTime() const
  593. {
  594. return magnitudeIntegrationTime;
  595. }
  596. void VolumeMeter::setMagnitudeIntegrationTime(qreal v)
  597. {
  598. magnitudeIntegrationTime = v;
  599. }
  600. qreal VolumeMeter::getPeakHoldDuration() const
  601. {
  602. return peakHoldDuration;
  603. }
  604. void VolumeMeter::setPeakHoldDuration(qreal v)
  605. {
  606. peakHoldDuration = v;
  607. }
  608. qreal VolumeMeter::getInputPeakHoldDuration() const
  609. {
  610. return inputPeakHoldDuration;
  611. }
  612. void VolumeMeter::setInputPeakHoldDuration(qreal v)
  613. {
  614. inputPeakHoldDuration = v;
  615. }
  616. void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  617. {
  618. obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType);
  619. switch (peakMeterType) {
  620. case TRUE_PEAK_METER:
  621. // For true-peak meters EBU has defined the Permitted Maximum,
  622. // taking into account the accuracy of the meter and further
  623. // processing required by lossy audio compression.
  624. //
  625. // The alignment level was not specified, but I've adjusted
  626. // it compared to a sample-peak meter. Incidentally Youtube
  627. // uses this new Alignment Level as the maximum integrated
  628. // loudness of a video.
  629. //
  630. // * Permitted Maximum Level (PML) = -2.0 dBTP
  631. // * Alignment Level (AL) = -13 dBTP
  632. setErrorLevel(-2.0);
  633. setWarningLevel(-13.0);
  634. break;
  635. case SAMPLE_PEAK_METER:
  636. default:
  637. // For a sample Peak Meter EBU has the following level
  638. // definitions, taking into account inaccuracies of this meter:
  639. //
  640. // * Permitted Maximum Level (PML) = -9.0 dBFS
  641. // * Alignment Level (AL) = -20.0 dBFS
  642. setErrorLevel(-9.0);
  643. setWarningLevel(-20.0);
  644. break;
  645. }
  646. }
  647. void VolumeMeter::mousePressEvent(QMouseEvent *event)
  648. {
  649. setFocus(Qt::MouseFocusReason);
  650. event->accept();
  651. }
  652. void VolumeMeter::wheelEvent(QWheelEvent *event)
  653. {
  654. QApplication::sendEvent(focusProxy(), event);
  655. }
  656. VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
  657. bool vertical)
  658. : QWidget(parent),
  659. obs_volmeter(obs_volmeter),
  660. vertical(vertical)
  661. {
  662. setAttribute(Qt::WA_OpaquePaintEvent, true);
  663. // Default meter settings, they only show if
  664. // there is no stylesheet, do not remove.
  665. backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
  666. backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
  667. backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red
  668. foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green
  669. foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
  670. foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red
  671. backgroundNominalColorDisabled.setRgb(90, 90, 90);
  672. backgroundWarningColorDisabled.setRgb(117, 117, 117);
  673. backgroundErrorColorDisabled.setRgb(65, 65, 65);
  674. foregroundNominalColorDisabled.setRgb(163, 163, 163);
  675. foregroundWarningColorDisabled.setRgb(217, 217, 217);
  676. foregroundErrorColorDisabled.setRgb(113, 113, 113);
  677. clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
  678. magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
  679. majorTickColor.setRgb(0x00, 0x00, 0x00); // Black
  680. minorTickColor.setRgb(0x32, 0x32, 0x32); // Dark gray
  681. minimumLevel = -60.0; // -60 dB
  682. warningLevel = -20.0; // -20 dB
  683. errorLevel = -9.0; // -9 dB
  684. clipLevel = -0.5; // -0.5 dB
  685. minimumInputLevel = -50.0; // -50 dB
  686. peakDecayRate = 11.76; // 20 dB / 1.7 sec
  687. magnitudeIntegrationTime = 0.3; // 99% in 300 ms
  688. peakHoldDuration = 20.0; // 20 seconds
  689. inputPeakHoldDuration = 1.0; // 1 second
  690. meterThickness = 3; // Bar thickness in pixels
  691. meterFontScaling =
  692. 0.7; // Font size for numbers is 70% of Widget's font size
  693. channels = (int)audio_output_get_channels(obs_get_audio());
  694. doLayout();
  695. updateTimerRef = updateTimer.lock();
  696. if (!updateTimerRef) {
  697. updateTimerRef = std::make_shared<VolumeMeterTimer>();
  698. updateTimerRef->setTimerType(Qt::PreciseTimer);
  699. updateTimerRef->start(16);
  700. updateTimer = updateTimerRef;
  701. }
  702. updateTimerRef->AddVolControl(this);
  703. }
  704. VolumeMeter::~VolumeMeter()
  705. {
  706. updateTimerRef->RemoveVolControl(this);
  707. }
  708. void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
  709. const float peak[MAX_AUDIO_CHANNELS],
  710. const float inputPeak[MAX_AUDIO_CHANNELS])
  711. {
  712. uint64_t ts = os_gettime_ns();
  713. QMutexLocker locker(&dataMutex);
  714. currentLastUpdateTime = ts;
  715. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  716. currentMagnitude[channelNr] = magnitude[channelNr];
  717. currentPeak[channelNr] = peak[channelNr];
  718. currentInputPeak[channelNr] = inputPeak[channelNr];
  719. }
  720. // In case there are more updates then redraws we must make sure
  721. // that the ballistics of peak and hold are recalculated.
  722. locker.unlock();
  723. calculateBallistics(ts);
  724. }
  725. inline void VolumeMeter::resetLevels()
  726. {
  727. currentLastUpdateTime = 0;
  728. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  729. currentMagnitude[channelNr] = -M_INFINITE;
  730. currentPeak[channelNr] = -M_INFINITE;
  731. currentInputPeak[channelNr] = -M_INFINITE;
  732. displayMagnitude[channelNr] = -M_INFINITE;
  733. displayPeak[channelNr] = -M_INFINITE;
  734. displayPeakHold[channelNr] = -M_INFINITE;
  735. displayPeakHoldLastUpdateTime[channelNr] = 0;
  736. displayInputPeakHold[channelNr] = -M_INFINITE;
  737. displayInputPeakHoldLastUpdateTime[channelNr] = 0;
  738. }
  739. }
  740. bool VolumeMeter::needLayoutChange()
  741. {
  742. int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
  743. if (!currentNrAudioChannels) {
  744. struct obs_audio_info oai;
  745. obs_get_audio_info(&oai);
  746. currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1
  747. : 2;
  748. }
  749. if (displayNrAudioChannels != currentNrAudioChannels) {
  750. displayNrAudioChannels = currentNrAudioChannels;
  751. recalculateLayout = true;
  752. }
  753. return recalculateLayout;
  754. }
  755. // When this is called from the constructor, obs_volmeter_get_nr_channels has not
  756. // yet been called and Q_PROPERTY settings have not yet been read from the
  757. // stylesheet.
  758. inline void VolumeMeter::doLayout()
  759. {
  760. QMutexLocker locker(&dataMutex);
  761. recalculateLayout = false;
  762. tickFont = font();
  763. QFontInfo info(tickFont);
  764. tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling);
  765. QFontMetrics metrics(tickFont);
  766. if (vertical) {
  767. // Each meter channel is meterThickness pixels wide, plus one pixel
  768. // between channels, but not after the last.
  769. // Add 4 pixels for ticks, space to hold our longest label in this font,
  770. // and a few pixels before the fader.
  771. QRect scaleBounds = metrics.boundingRect("-88");
  772. setMinimumSize(displayNrAudioChannels * (meterThickness + 1) -
  773. 1 + 4 + scaleBounds.width() + 2,
  774. 130);
  775. } else {
  776. // Each meter channel is meterThickness pixels high, plus one pixel
  777. // between channels, but not after the last.
  778. // Add 4 pixels for ticks, and space high enough to hold our label in
  779. // this font, presuming that digits don't have descenders.
  780. setMinimumSize(130,
  781. displayNrAudioChannels * (meterThickness + 1) -
  782. 1 + 4 + metrics.capHeight());
  783. }
  784. resetLevels();
  785. }
  786. inline bool VolumeMeter::detectIdle(uint64_t ts)
  787. {
  788. double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001;
  789. if (timeSinceLastUpdate > 0.5) {
  790. resetLevels();
  791. return true;
  792. } else {
  793. return false;
  794. }
  795. }
  796. inline void
  797. VolumeMeter::calculateBallisticsForChannel(int channelNr, uint64_t ts,
  798. qreal timeSinceLastRedraw)
  799. {
  800. if (currentPeak[channelNr] >= displayPeak[channelNr] ||
  801. isnan(displayPeak[channelNr])) {
  802. // Attack of peak is immediate.
  803. displayPeak[channelNr] = currentPeak[channelNr];
  804. } else {
  805. // Decay of peak is 40 dB / 1.7 seconds for Fast Profile
  806. // 20 dB / 1.7 seconds for Medium Profile (Type I PPM)
  807. // 24 dB / 2.8 seconds for Slow Profile (Type II PPM)
  808. float decay = float(peakDecayRate * timeSinceLastRedraw);
  809. displayPeak[channelNr] = CLAMP(displayPeak[channelNr] - decay,
  810. currentPeak[channelNr], 0);
  811. }
  812. if (currentPeak[channelNr] >= displayPeakHold[channelNr] ||
  813. !isfinite(displayPeakHold[channelNr])) {
  814. // Attack of peak-hold is immediate, but keep track
  815. // when it was last updated.
  816. displayPeakHold[channelNr] = currentPeak[channelNr];
  817. displayPeakHoldLastUpdateTime[channelNr] = ts;
  818. } else {
  819. // The peak and hold falls back to peak
  820. // after 20 seconds.
  821. qreal timeSinceLastPeak =
  822. (uint64_t)(ts -
  823. displayPeakHoldLastUpdateTime[channelNr]) *
  824. 0.000000001;
  825. if (timeSinceLastPeak > peakHoldDuration) {
  826. displayPeakHold[channelNr] = currentPeak[channelNr];
  827. displayPeakHoldLastUpdateTime[channelNr] = ts;
  828. }
  829. }
  830. if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] ||
  831. !isfinite(displayInputPeakHold[channelNr])) {
  832. // Attack of peak-hold is immediate, but keep track
  833. // when it was last updated.
  834. displayInputPeakHold[channelNr] = currentInputPeak[channelNr];
  835. displayInputPeakHoldLastUpdateTime[channelNr] = ts;
  836. } else {
  837. // The peak and hold falls back to peak after 1 second.
  838. qreal timeSinceLastPeak =
  839. (uint64_t)(ts -
  840. displayInputPeakHoldLastUpdateTime[channelNr]) *
  841. 0.000000001;
  842. if (timeSinceLastPeak > inputPeakHoldDuration) {
  843. displayInputPeakHold[channelNr] =
  844. currentInputPeak[channelNr];
  845. displayInputPeakHoldLastUpdateTime[channelNr] = ts;
  846. }
  847. }
  848. if (!isfinite(displayMagnitude[channelNr])) {
  849. // The statements in the else-leg do not work with
  850. // NaN and infinite displayMagnitude.
  851. displayMagnitude[channelNr] = currentMagnitude[channelNr];
  852. } else {
  853. // A VU meter will integrate to the new value to 99% in 300 ms.
  854. // The calculation here is very simplified and is more accurate
  855. // with higher frame-rate.
  856. float attack =
  857. float((currentMagnitude[channelNr] -
  858. displayMagnitude[channelNr]) *
  859. (timeSinceLastRedraw / magnitudeIntegrationTime) *
  860. 0.99);
  861. displayMagnitude[channelNr] =
  862. CLAMP(displayMagnitude[channelNr] + attack,
  863. (float)minimumLevel, 0);
  864. }
  865. }
  866. inline void VolumeMeter::calculateBallistics(uint64_t ts,
  867. qreal timeSinceLastRedraw)
  868. {
  869. QMutexLocker locker(&dataMutex);
  870. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++)
  871. calculateBallisticsForChannel(channelNr, ts,
  872. timeSinceLastRedraw);
  873. }
  874. void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
  875. int height, float peakHold)
  876. {
  877. QMutexLocker locker(&dataMutex);
  878. QColor color;
  879. if (peakHold < minimumInputLevel)
  880. color = backgroundNominalColor;
  881. else if (peakHold < warningLevel)
  882. color = foregroundNominalColor;
  883. else if (peakHold < errorLevel)
  884. color = foregroundWarningColor;
  885. else if (peakHold <= clipLevel)
  886. color = foregroundErrorColor;
  887. else
  888. color = clipColor;
  889. painter.fillRect(x, y, width, height, color);
  890. }
  891. void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width)
  892. {
  893. qreal scale = width / minimumLevel;
  894. painter.setFont(tickFont);
  895. QFontMetrics metrics(tickFont);
  896. painter.setPen(majorTickColor);
  897. // Draw major tick lines and numeric indicators.
  898. for (int i = 0; i >= minimumLevel; i -= 5) {
  899. int position = int(x + width - (i * scale) - 1);
  900. QString str = QString::number(i);
  901. // Center the number on the tick, but don't overflow
  902. QRect textBounds = metrics.boundingRect(str);
  903. int pos;
  904. if (i == 0) {
  905. pos = position - textBounds.width();
  906. } else {
  907. pos = position - (textBounds.width() / 2);
  908. if (pos < 0)
  909. pos = 0;
  910. }
  911. painter.drawText(pos, y + 4 + metrics.capHeight(), str);
  912. painter.drawLine(position, y, position, y + 2);
  913. }
  914. // Draw minor tick lines.
  915. painter.setPen(minorTickColor);
  916. for (int i = 0; i >= minimumLevel; i--) {
  917. int position = int(x + width - (i * scale) - 1);
  918. if (i % 5 != 0)
  919. painter.drawLine(position, y, position, y + 1);
  920. }
  921. }
  922. void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height)
  923. {
  924. qreal scale = height / minimumLevel;
  925. painter.setFont(tickFont);
  926. QFontMetrics metrics(tickFont);
  927. painter.setPen(majorTickColor);
  928. // Draw major tick lines and numeric indicators.
  929. for (int i = 0; i >= minimumLevel; i -= 5) {
  930. int position = y + int(i * scale) + METER_PADDING;
  931. QString str = QString::number(i);
  932. // Center the number on the tick, but don't overflow
  933. if (i == 0) {
  934. painter.drawText(x + 6, position + metrics.capHeight(),
  935. str);
  936. } else {
  937. painter.drawText(x + 4,
  938. position + (metrics.capHeight() / 2),
  939. str);
  940. }
  941. painter.drawLine(x, position, x + 2, position);
  942. }
  943. // Draw minor tick lines.
  944. painter.setPen(minorTickColor);
  945. for (int i = 0; i >= minimumLevel; i--) {
  946. int position = y + int(i * scale) + METER_PADDING;
  947. if (i % 5 != 0)
  948. painter.drawLine(x, position, x + 1, position);
  949. }
  950. }
  951. #define CLIP_FLASH_DURATION_MS 1000
  952. inline int VolumeMeter::convertToInt(float number)
  953. {
  954. constexpr int min = std::numeric_limits<int>::min();
  955. constexpr int max = std::numeric_limits<int>::max();
  956. // NOTE: Conversion from 'const int' to 'float' changes max value from 2147483647 to 2147483648
  957. if (number >= (float)max)
  958. return max;
  959. else if (number < min)
  960. return min;
  961. else
  962. return int(number);
  963. }
  964. void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width,
  965. int height, float magnitude, float peak,
  966. float peakHold)
  967. {
  968. qreal scale = width / minimumLevel;
  969. QMutexLocker locker(&dataMutex);
  970. int minimumPosition = x + 0;
  971. int maximumPosition = x + width;
  972. int magnitudePosition = x + width - convertToInt(magnitude * scale);
  973. int peakPosition = x + width - convertToInt(peak * scale);
  974. int peakHoldPosition = x + width - convertToInt(peakHold * scale);
  975. int warningPosition = x + width - convertToInt(warningLevel * scale);
  976. int errorPosition = x + width - convertToInt(errorLevel * scale);
  977. int nominalLength = warningPosition - minimumPosition;
  978. int warningLength = errorPosition - warningPosition;
  979. int errorLength = maximumPosition - errorPosition;
  980. locker.unlock();
  981. if (clipping) {
  982. peakPosition = maximumPosition;
  983. }
  984. if (peakPosition < minimumPosition) {
  985. painter.fillRect(minimumPosition, y, nominalLength, height,
  986. muted ? backgroundNominalColorDisabled
  987. : backgroundNominalColor);
  988. painter.fillRect(warningPosition, y, warningLength, height,
  989. muted ? backgroundWarningColorDisabled
  990. : backgroundWarningColor);
  991. painter.fillRect(errorPosition, y, errorLength, height,
  992. muted ? backgroundErrorColorDisabled
  993. : backgroundErrorColor);
  994. } else if (peakPosition < warningPosition) {
  995. painter.fillRect(minimumPosition, y,
  996. peakPosition - minimumPosition, height,
  997. muted ? foregroundNominalColorDisabled
  998. : foregroundNominalColor);
  999. painter.fillRect(peakPosition, y,
  1000. warningPosition - peakPosition, height,
  1001. muted ? backgroundNominalColorDisabled
  1002. : backgroundNominalColor);
  1003. painter.fillRect(warningPosition, y, warningLength, height,
  1004. muted ? backgroundWarningColorDisabled
  1005. : backgroundWarningColor);
  1006. painter.fillRect(errorPosition, y, errorLength, height,
  1007. muted ? backgroundErrorColorDisabled
  1008. : backgroundErrorColor);
  1009. } else if (peakPosition < errorPosition) {
  1010. painter.fillRect(minimumPosition, y, nominalLength, height,
  1011. muted ? foregroundNominalColorDisabled
  1012. : foregroundNominalColor);
  1013. painter.fillRect(warningPosition, y,
  1014. peakPosition - warningPosition, height,
  1015. muted ? foregroundWarningColorDisabled
  1016. : foregroundWarningColor);
  1017. painter.fillRect(peakPosition, y, errorPosition - peakPosition,
  1018. height,
  1019. muted ? backgroundWarningColorDisabled
  1020. : backgroundWarningColor);
  1021. painter.fillRect(errorPosition, y, errorLength, height,
  1022. muted ? backgroundErrorColorDisabled
  1023. : backgroundErrorColor);
  1024. } else if (peakPosition < maximumPosition) {
  1025. painter.fillRect(minimumPosition, y, nominalLength, height,
  1026. muted ? foregroundNominalColorDisabled
  1027. : foregroundNominalColor);
  1028. painter.fillRect(warningPosition, y, warningLength, height,
  1029. muted ? foregroundWarningColorDisabled
  1030. : foregroundWarningColor);
  1031. painter.fillRect(errorPosition, y, peakPosition - errorPosition,
  1032. height,
  1033. muted ? foregroundErrorColorDisabled
  1034. : foregroundErrorColor);
  1035. painter.fillRect(peakPosition, y,
  1036. maximumPosition - peakPosition, height,
  1037. muted ? backgroundErrorColorDisabled
  1038. : backgroundErrorColor);
  1039. } else if (int(magnitude) != 0) {
  1040. if (!clipping) {
  1041. QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
  1042. [&]() { clipping = false; });
  1043. clipping = true;
  1044. }
  1045. int end = errorLength + warningLength + nominalLength;
  1046. painter.fillRect(minimumPosition, y, end, height,
  1047. QBrush(muted ? foregroundErrorColorDisabled
  1048. : foregroundErrorColor));
  1049. }
  1050. if (peakHoldPosition - 3 < minimumPosition)
  1051. ; // Peak-hold below minimum, no drawing.
  1052. else if (peakHoldPosition < warningPosition)
  1053. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  1054. muted ? foregroundNominalColorDisabled
  1055. : foregroundNominalColor);
  1056. else if (peakHoldPosition < errorPosition)
  1057. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  1058. muted ? foregroundWarningColorDisabled
  1059. : foregroundWarningColor);
  1060. else
  1061. painter.fillRect(peakHoldPosition - 3, y, 3, height,
  1062. muted ? foregroundErrorColorDisabled
  1063. : foregroundErrorColor);
  1064. if (magnitudePosition - 3 >= minimumPosition)
  1065. painter.fillRect(magnitudePosition - 3, y, 3, height,
  1066. magnitudeColor);
  1067. }
  1068. void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width,
  1069. int height, float magnitude, float peak,
  1070. float peakHold)
  1071. {
  1072. qreal scale = height / minimumLevel;
  1073. QMutexLocker locker(&dataMutex);
  1074. int minimumPosition = y + 0;
  1075. int maximumPosition = y + height;
  1076. int magnitudePosition = y + height - convertToInt(magnitude * scale);
  1077. int peakPosition = y + height - convertToInt(peak * scale);
  1078. int peakHoldPosition = y + height - convertToInt(peakHold * scale);
  1079. int warningPosition = y + height - convertToInt(warningLevel * scale);
  1080. int errorPosition = y + height - convertToInt(errorLevel * scale);
  1081. int nominalLength = warningPosition - minimumPosition;
  1082. int warningLength = errorPosition - warningPosition;
  1083. int errorLength = maximumPosition - errorPosition;
  1084. locker.unlock();
  1085. if (clipping) {
  1086. peakPosition = maximumPosition;
  1087. }
  1088. if (peakPosition < minimumPosition) {
  1089. painter.fillRect(x, minimumPosition, width, nominalLength,
  1090. muted ? backgroundNominalColorDisabled
  1091. : backgroundNominalColor);
  1092. painter.fillRect(x, warningPosition, width, warningLength,
  1093. muted ? backgroundWarningColorDisabled
  1094. : backgroundWarningColor);
  1095. painter.fillRect(x, errorPosition, width, errorLength,
  1096. muted ? backgroundErrorColorDisabled
  1097. : backgroundErrorColor);
  1098. } else if (peakPosition < warningPosition) {
  1099. painter.fillRect(x, minimumPosition, width,
  1100. peakPosition - minimumPosition,
  1101. muted ? foregroundNominalColorDisabled
  1102. : foregroundNominalColor);
  1103. painter.fillRect(x, peakPosition, width,
  1104. warningPosition - peakPosition,
  1105. muted ? backgroundNominalColorDisabled
  1106. : backgroundNominalColor);
  1107. painter.fillRect(x, warningPosition, width, warningLength,
  1108. muted ? backgroundWarningColorDisabled
  1109. : backgroundWarningColor);
  1110. painter.fillRect(x, errorPosition, width, errorLength,
  1111. muted ? backgroundErrorColorDisabled
  1112. : backgroundErrorColor);
  1113. } else if (peakPosition < errorPosition) {
  1114. painter.fillRect(x, minimumPosition, width, nominalLength,
  1115. muted ? foregroundNominalColorDisabled
  1116. : foregroundNominalColor);
  1117. painter.fillRect(x, warningPosition, width,
  1118. peakPosition - warningPosition,
  1119. muted ? foregroundWarningColorDisabled
  1120. : foregroundWarningColor);
  1121. painter.fillRect(x, peakPosition, width,
  1122. errorPosition - peakPosition,
  1123. muted ? backgroundWarningColorDisabled
  1124. : backgroundWarningColor);
  1125. painter.fillRect(x, errorPosition, width, errorLength,
  1126. muted ? backgroundErrorColorDisabled
  1127. : backgroundErrorColor);
  1128. } else if (peakPosition < maximumPosition) {
  1129. painter.fillRect(x, minimumPosition, width, nominalLength,
  1130. muted ? foregroundNominalColorDisabled
  1131. : foregroundNominalColor);
  1132. painter.fillRect(x, warningPosition, width, warningLength,
  1133. muted ? foregroundWarningColorDisabled
  1134. : foregroundWarningColor);
  1135. painter.fillRect(x, errorPosition, width,
  1136. peakPosition - errorPosition,
  1137. muted ? foregroundErrorColorDisabled
  1138. : foregroundErrorColor);
  1139. painter.fillRect(x, peakPosition, width,
  1140. maximumPosition - peakPosition,
  1141. muted ? backgroundErrorColorDisabled
  1142. : backgroundErrorColor);
  1143. } else {
  1144. if (!clipping) {
  1145. QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
  1146. [&]() { clipping = false; });
  1147. clipping = true;
  1148. }
  1149. int end = errorLength + warningLength + nominalLength;
  1150. painter.fillRect(x, minimumPosition, width, end,
  1151. QBrush(muted ? foregroundErrorColorDisabled
  1152. : foregroundErrorColor));
  1153. }
  1154. if (peakHoldPosition - 3 < minimumPosition)
  1155. ; // Peak-hold below minimum, no drawing.
  1156. else if (peakHoldPosition < warningPosition)
  1157. painter.fillRect(x, peakHoldPosition - 3, width, 3,
  1158. muted ? foregroundNominalColorDisabled
  1159. : foregroundNominalColor);
  1160. else if (peakHoldPosition < errorPosition)
  1161. painter.fillRect(x, peakHoldPosition - 3, width, 3,
  1162. muted ? foregroundWarningColorDisabled
  1163. : foregroundWarningColor);
  1164. else
  1165. painter.fillRect(x, peakHoldPosition - 3, width, 3,
  1166. muted ? foregroundErrorColorDisabled
  1167. : foregroundErrorColor);
  1168. if (magnitudePosition - 3 >= minimumPosition)
  1169. painter.fillRect(x, magnitudePosition - 3, width, 3,
  1170. magnitudeColor);
  1171. }
  1172. void VolumeMeter::paintEvent(QPaintEvent *event)
  1173. {
  1174. uint64_t ts = os_gettime_ns();
  1175. qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001;
  1176. calculateBallistics(ts, timeSinceLastRedraw);
  1177. bool idle = detectIdle(ts);
  1178. QRect widgetRect = rect();
  1179. int width = widgetRect.width();
  1180. int height = widgetRect.height();
  1181. QPainter painter(this);
  1182. // Paint window background color (as widget is opaque)
  1183. QColor background = palette().color(QPalette::ColorRole::Window);
  1184. painter.fillRect(event->region().boundingRect(), background);
  1185. if (vertical)
  1186. height -= METER_PADDING * 2;
  1187. // timerEvent requests update of the bar(s) only, so we can avoid the
  1188. // overhead of repainting the scale and labels.
  1189. if (event->region().boundingRect() != getBarRect()) {
  1190. if (needLayoutChange())
  1191. doLayout();
  1192. if (vertical) {
  1193. paintVTicks(painter,
  1194. displayNrAudioChannels *
  1195. (meterThickness + 1) -
  1196. 1,
  1197. 0, height - (INDICATOR_THICKNESS + 3));
  1198. } else {
  1199. paintHTicks(painter, INDICATOR_THICKNESS + 3,
  1200. displayNrAudioChannels *
  1201. (meterThickness + 1) -
  1202. 1,
  1203. width - (INDICATOR_THICKNESS + 3));
  1204. }
  1205. }
  1206. if (vertical) {
  1207. // Invert the Y axis to ease the math
  1208. painter.translate(0, height + METER_PADDING);
  1209. painter.scale(1, -1);
  1210. }
  1211. for (int channelNr = 0; channelNr < displayNrAudioChannels;
  1212. channelNr++) {
  1213. int channelNrFixed =
  1214. (displayNrAudioChannels == 1 && channels > 2)
  1215. ? 2
  1216. : channelNr;
  1217. if (vertical)
  1218. paintVMeter(painter, channelNr * (meterThickness + 1),
  1219. INDICATOR_THICKNESS + 2, meterThickness,
  1220. height - (INDICATOR_THICKNESS + 2),
  1221. displayMagnitude[channelNrFixed],
  1222. displayPeak[channelNrFixed],
  1223. displayPeakHold[channelNrFixed]);
  1224. else
  1225. paintHMeter(painter, INDICATOR_THICKNESS + 2,
  1226. channelNr * (meterThickness + 1),
  1227. width - (INDICATOR_THICKNESS + 2),
  1228. meterThickness,
  1229. displayMagnitude[channelNrFixed],
  1230. displayPeak[channelNrFixed],
  1231. displayPeakHold[channelNrFixed]);
  1232. if (idle)
  1233. continue;
  1234. // By not drawing the input meter boxes the user can
  1235. // see that the audio stream has been stopped, without
  1236. // having too much visual impact.
  1237. if (vertical)
  1238. paintInputMeter(painter,
  1239. channelNr * (meterThickness + 1), 0,
  1240. meterThickness, INDICATOR_THICKNESS,
  1241. displayInputPeakHold[channelNrFixed]);
  1242. else
  1243. paintInputMeter(painter, 0,
  1244. channelNr * (meterThickness + 1),
  1245. INDICATOR_THICKNESS, meterThickness,
  1246. displayInputPeakHold[channelNrFixed]);
  1247. }
  1248. lastRedrawTime = ts;
  1249. }
  1250. QRect VolumeMeter::getBarRect() const
  1251. {
  1252. QRect rec = rect();
  1253. if (vertical)
  1254. rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1);
  1255. else
  1256. rec.setHeight(displayNrAudioChannels * (meterThickness + 1) -
  1257. 1);
  1258. return rec;
  1259. }
  1260. void VolumeMeter::changeEvent(QEvent *e)
  1261. {
  1262. if (e->type() == QEvent::StyleChange)
  1263. recalculateLayout = true;
  1264. QWidget::changeEvent(e);
  1265. }
  1266. void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
  1267. {
  1268. volumeMeters.push_back(meter);
  1269. }
  1270. void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
  1271. {
  1272. volumeMeters.removeOne(meter);
  1273. }
  1274. void VolumeMeterTimer::timerEvent(QTimerEvent *)
  1275. {
  1276. for (VolumeMeter *meter : volumeMeters) {
  1277. if (meter->needLayoutChange()) {
  1278. // Tell paintEvent to update layout and paint everything
  1279. meter->update();
  1280. } else {
  1281. // Tell paintEvent to paint only the bars
  1282. meter->update(meter->getBarRect());
  1283. }
  1284. }
  1285. }
  1286. VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent)
  1287. : SliderIgnoreScroll(parent)
  1288. {
  1289. fad = fader;
  1290. }
  1291. VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation,
  1292. QWidget *parent)
  1293. : SliderIgnoreScroll(orientation, parent)
  1294. {
  1295. fad = fader;
  1296. }
  1297. VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w)
  1298. : QAccessibleWidget(w)
  1299. {
  1300. }
  1301. VolumeSlider *VolumeAccessibleInterface::slider() const
  1302. {
  1303. return qobject_cast<VolumeSlider *>(object());
  1304. }
  1305. QString VolumeAccessibleInterface::text(QAccessible::Text t) const
  1306. {
  1307. if (slider()->isVisible()) {
  1308. switch (t) {
  1309. case QAccessible::Text::Value:
  1310. return currentValue().toString();
  1311. default:
  1312. break;
  1313. }
  1314. }
  1315. return QAccessibleWidget::text(t);
  1316. }
  1317. QVariant VolumeAccessibleInterface::currentValue() const
  1318. {
  1319. QString text;
  1320. float db = obs_fader_get_db(slider()->fad);
  1321. if (db < -96.0f)
  1322. text = "-inf dB";
  1323. else
  1324. text = QString::number(db, 'f', 1).append(" dB");
  1325. return text;
  1326. }
  1327. void VolumeAccessibleInterface::setCurrentValue(const QVariant &value)
  1328. {
  1329. slider()->setValue(value.toInt());
  1330. }
  1331. QVariant VolumeAccessibleInterface::maximumValue() const
  1332. {
  1333. return slider()->maximum();
  1334. }
  1335. QVariant VolumeAccessibleInterface::minimumValue() const
  1336. {
  1337. return slider()->minimum();
  1338. }
  1339. QVariant VolumeAccessibleInterface::minimumStepSize() const
  1340. {
  1341. return slider()->singleStep();
  1342. }
  1343. QAccessible::Role VolumeAccessibleInterface::role() const
  1344. {
  1345. return QAccessible::Role::Slider;
  1346. }