volume-control.cpp 46 KB

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