volume-control.cpp 42 KB

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