volume-control.cpp 46 KB

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