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 CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
  16. #define FADER_PRECISION 4096.0
  17. // Size of the audio indicator in pixels
  18. #define INDICATOR_THICKNESS 3
  19. // Padding on top and bottom of vertical meters
  20. #define METER_PADDING 1
  21. std::weak_ptr<VolumeMeterTimer> VolumeMeter::updateTimer;
  22. static inline Qt::CheckState GetCheckState(bool muted, bool unassigned)
  23. {
  24. if (muted)
  25. return Qt::Checked;
  26. else if (unassigned)
  27. return Qt::PartiallyChecked;
  28. else
  29. return Qt::Unchecked;
  30. }
  31. static inline bool IsSourceUnassigned(obs_source_t *source)
  32. {
  33. uint32_t mixes = (obs_source_get_audio_mixers(source) &
  34. ((1 << MAX_AUDIO_MIXES) - 1));
  35. obs_monitoring_type mt = obs_source_get_monitoring_type(source);
  36. return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY;
  37. }
  38. static void ShowUnassignedWarning(const char *name)
  39. {
  40. auto msgBox = [=]() {
  41. QMessageBox msgbox(App()->GetMainWindow());
  42. msgbox.setWindowTitle(
  43. QTStr("VolControl.UnassignedWarning.Title"));
  44. msgbox.setText(
  45. QTStr("VolControl.UnassignedWarning.Text").arg(name));
  46. msgbox.setIcon(QMessageBox::Icon::Information);
  47. msgbox.addButton(QMessageBox::Ok);
  48. QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain"));
  49. msgbox.setCheckBox(cb);
  50. msgbox.exec();
  51. if (cb->isChecked()) {
  52. config_set_bool(App()->GlobalConfig(), "General",
  53. "WarnedAboutUnassignedSources", true);
  54. config_save_safe(App()->GlobalConfig(), "tmp", 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()->GlobalConfig(), "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(GetGlobalConfig(), "Accessibility",
  363. "OverrideColors")) {
  364. backgroundNominalColor = color_from_int(config_get_int(
  365. GetGlobalConfig(), "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(GetGlobalConfig(), "Accessibility",
  386. "OverrideColors")) {
  387. backgroundWarningColor = color_from_int(config_get_int(
  388. GetGlobalConfig(), "Accessibility", "MixerYellow"));
  389. } else {
  390. backgroundWarningColor = p_backgroundWarningColor;
  391. }
  392. }
  393. void VolumeMeter::setBackgroundWarningColorDisabled(QColor c)
  394. {
  395. backgroundWarningColorDisabled = std::move(c);
  396. }
  397. QColor VolumeMeter::getBackgroundErrorColor() const
  398. {
  399. return p_backgroundErrorColor;
  400. }
  401. QColor VolumeMeter::getBackgroundErrorColorDisabled() const
  402. {
  403. return backgroundErrorColorDisabled;
  404. }
  405. void VolumeMeter::setBackgroundErrorColor(QColor c)
  406. {
  407. p_backgroundErrorColor = std::move(c);
  408. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  409. "OverrideColors")) {
  410. backgroundErrorColor = color_from_int(config_get_int(
  411. GetGlobalConfig(), "Accessibility", "MixerRed"));
  412. } else {
  413. backgroundErrorColor = p_backgroundErrorColor;
  414. }
  415. }
  416. void VolumeMeter::setBackgroundErrorColorDisabled(QColor c)
  417. {
  418. backgroundErrorColorDisabled = std::move(c);
  419. }
  420. QColor VolumeMeter::getForegroundNominalColor() const
  421. {
  422. return p_foregroundNominalColor;
  423. }
  424. QColor VolumeMeter::getForegroundNominalColorDisabled() const
  425. {
  426. return foregroundNominalColorDisabled;
  427. }
  428. void VolumeMeter::setForegroundNominalColor(QColor c)
  429. {
  430. p_foregroundNominalColor = std::move(c);
  431. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  432. "OverrideColors")) {
  433. foregroundNominalColor = color_from_int(
  434. config_get_int(GetGlobalConfig(), "Accessibility",
  435. "MixerGreenActive"));
  436. } else {
  437. foregroundNominalColor = p_foregroundNominalColor;
  438. }
  439. }
  440. void VolumeMeter::setForegroundNominalColorDisabled(QColor c)
  441. {
  442. foregroundNominalColorDisabled = std::move(c);
  443. }
  444. QColor VolumeMeter::getForegroundWarningColor() const
  445. {
  446. return p_foregroundWarningColor;
  447. }
  448. QColor VolumeMeter::getForegroundWarningColorDisabled() const
  449. {
  450. return foregroundWarningColorDisabled;
  451. }
  452. void VolumeMeter::setForegroundWarningColor(QColor c)
  453. {
  454. p_foregroundWarningColor = std::move(c);
  455. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  456. "OverrideColors")) {
  457. foregroundWarningColor = color_from_int(
  458. config_get_int(GetGlobalConfig(), "Accessibility",
  459. "MixerYellowActive"));
  460. } else {
  461. foregroundWarningColor = p_foregroundWarningColor;
  462. }
  463. }
  464. void VolumeMeter::setForegroundWarningColorDisabled(QColor c)
  465. {
  466. foregroundWarningColorDisabled = std::move(c);
  467. }
  468. QColor VolumeMeter::getForegroundErrorColor() const
  469. {
  470. return p_foregroundErrorColor;
  471. }
  472. QColor VolumeMeter::getForegroundErrorColorDisabled() const
  473. {
  474. return foregroundErrorColorDisabled;
  475. }
  476. void VolumeMeter::setForegroundErrorColor(QColor c)
  477. {
  478. p_foregroundErrorColor = std::move(c);
  479. if (config_get_bool(GetGlobalConfig(), "Accessibility",
  480. "OverrideColors")) {
  481. foregroundErrorColor = color_from_int(config_get_int(
  482. GetGlobalConfig(), "Accessibility", "MixerRedActive"));
  483. } else {
  484. foregroundErrorColor = p_foregroundErrorColor;
  485. }
  486. }
  487. void VolumeMeter::setForegroundErrorColorDisabled(QColor c)
  488. {
  489. foregroundErrorColorDisabled = std::move(c);
  490. }
  491. QColor VolumeMeter::getClipColor() const
  492. {
  493. return clipColor;
  494. }
  495. void VolumeMeter::setClipColor(QColor c)
  496. {
  497. clipColor = std::move(c);
  498. }
  499. QColor VolumeMeter::getMagnitudeColor() const
  500. {
  501. return magnitudeColor;
  502. }
  503. void VolumeMeter::setMagnitudeColor(QColor c)
  504. {
  505. magnitudeColor = std::move(c);
  506. }
  507. QColor VolumeMeter::getMajorTickColor() const
  508. {
  509. return majorTickColor;
  510. }
  511. void VolumeMeter::setMajorTickColor(QColor c)
  512. {
  513. majorTickColor = std::move(c);
  514. }
  515. QColor VolumeMeter::getMinorTickColor() const
  516. {
  517. return minorTickColor;
  518. }
  519. void VolumeMeter::setMinorTickColor(QColor c)
  520. {
  521. minorTickColor = std::move(c);
  522. }
  523. int VolumeMeter::getMeterThickness() const
  524. {
  525. return meterThickness;
  526. }
  527. void VolumeMeter::setMeterThickness(int v)
  528. {
  529. meterThickness = v;
  530. recalculateLayout = true;
  531. }
  532. qreal VolumeMeter::getMeterFontScaling() const
  533. {
  534. return meterFontScaling;
  535. }
  536. void VolumeMeter::setMeterFontScaling(qreal v)
  537. {
  538. meterFontScaling = v;
  539. recalculateLayout = true;
  540. }
  541. void VolControl::refreshColors()
  542. {
  543. volMeter->setBackgroundNominalColor(
  544. volMeter->getBackgroundNominalColor());
  545. volMeter->setBackgroundWarningColor(
  546. volMeter->getBackgroundWarningColor());
  547. volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor());
  548. volMeter->setForegroundNominalColor(
  549. volMeter->getForegroundNominalColor());
  550. volMeter->setForegroundWarningColor(
  551. volMeter->getForegroundWarningColor());
  552. volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor());
  553. }
  554. qreal VolumeMeter::getMinimumLevel() const
  555. {
  556. return minimumLevel;
  557. }
  558. void VolumeMeter::setMinimumLevel(qreal v)
  559. {
  560. minimumLevel = v;
  561. }
  562. qreal VolumeMeter::getWarningLevel() const
  563. {
  564. return warningLevel;
  565. }
  566. void VolumeMeter::setWarningLevel(qreal v)
  567. {
  568. warningLevel = v;
  569. }
  570. qreal VolumeMeter::getErrorLevel() const
  571. {
  572. return errorLevel;
  573. }
  574. void VolumeMeter::setErrorLevel(qreal v)
  575. {
  576. errorLevel = v;
  577. }
  578. qreal VolumeMeter::getClipLevel() const
  579. {
  580. return clipLevel;
  581. }
  582. void VolumeMeter::setClipLevel(qreal v)
  583. {
  584. clipLevel = v;
  585. }
  586. qreal VolumeMeter::getMinimumInputLevel() const
  587. {
  588. return minimumInputLevel;
  589. }
  590. void VolumeMeter::setMinimumInputLevel(qreal v)
  591. {
  592. minimumInputLevel = v;
  593. }
  594. qreal VolumeMeter::getPeakDecayRate() const
  595. {
  596. return peakDecayRate;
  597. }
  598. void VolumeMeter::setPeakDecayRate(qreal v)
  599. {
  600. peakDecayRate = v;
  601. }
  602. qreal VolumeMeter::getMagnitudeIntegrationTime() const
  603. {
  604. return magnitudeIntegrationTime;
  605. }
  606. void VolumeMeter::setMagnitudeIntegrationTime(qreal v)
  607. {
  608. magnitudeIntegrationTime = v;
  609. }
  610. qreal VolumeMeter::getPeakHoldDuration() const
  611. {
  612. return peakHoldDuration;
  613. }
  614. void VolumeMeter::setPeakHoldDuration(qreal v)
  615. {
  616. peakHoldDuration = v;
  617. }
  618. qreal VolumeMeter::getInputPeakHoldDuration() const
  619. {
  620. return inputPeakHoldDuration;
  621. }
  622. void VolumeMeter::setInputPeakHoldDuration(qreal v)
  623. {
  624. inputPeakHoldDuration = v;
  625. }
  626. void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  627. {
  628. obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType);
  629. switch (peakMeterType) {
  630. case TRUE_PEAK_METER:
  631. // For true-peak meters EBU has defined the Permitted Maximum,
  632. // taking into account the accuracy of the meter and further
  633. // processing required by lossy audio compression.
  634. //
  635. // The alignment level was not specified, but I've adjusted
  636. // it compared to a sample-peak meter. Incidentally Youtube
  637. // uses this new Alignment Level as the maximum integrated
  638. // loudness of a video.
  639. //
  640. // * Permitted Maximum Level (PML) = -2.0 dBTP
  641. // * Alignment Level (AL) = -13 dBTP
  642. setErrorLevel(-2.0);
  643. setWarningLevel(-13.0);
  644. break;
  645. case SAMPLE_PEAK_METER:
  646. default:
  647. // For a sample Peak Meter EBU has the following level
  648. // definitions, taking into account inaccuracies of this meter:
  649. //
  650. // * Permitted Maximum Level (PML) = -9.0 dBFS
  651. // * Alignment Level (AL) = -20.0 dBFS
  652. setErrorLevel(-9.0);
  653. setWarningLevel(-20.0);
  654. break;
  655. }
  656. }
  657. void VolumeMeter::mousePressEvent(QMouseEvent *event)
  658. {
  659. setFocus(Qt::MouseFocusReason);
  660. event->accept();
  661. }
  662. void VolumeMeter::wheelEvent(QWheelEvent *event)
  663. {
  664. QApplication::sendEvent(focusProxy(), event);
  665. }
  666. VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
  667. bool vertical)
  668. : QWidget(parent),
  669. obs_volmeter(obs_volmeter),
  670. vertical(vertical)
  671. {
  672. setAttribute(Qt::WA_OpaquePaintEvent, true);
  673. // Default meter settings, they only show if
  674. // there is no stylesheet, do not remove.
  675. backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
  676. backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
  677. backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red
  678. foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green
  679. foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
  680. foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red
  681. backgroundNominalColorDisabled.setRgb(90, 90, 90);
  682. backgroundWarningColorDisabled.setRgb(117, 117, 117);
  683. backgroundErrorColorDisabled.setRgb(65, 65, 65);
  684. foregroundNominalColorDisabled.setRgb(163, 163, 163);
  685. foregroundWarningColorDisabled.setRgb(217, 217, 217);
  686. foregroundErrorColorDisabled.setRgb(113, 113, 113);
  687. clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
  688. magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
  689. majorTickColor.setRgb(0x00, 0x00, 0x00); // Black
  690. minorTickColor.setRgb(0x32, 0x32, 0x32); // Dark gray
  691. minimumLevel = -60.0; // -60 dB
  692. warningLevel = -20.0; // -20 dB
  693. errorLevel = -9.0; // -9 dB
  694. clipLevel = -0.5; // -0.5 dB
  695. minimumInputLevel = -50.0; // -50 dB
  696. peakDecayRate = 11.76; // 20 dB / 1.7 sec
  697. magnitudeIntegrationTime = 0.3; // 99% in 300 ms
  698. peakHoldDuration = 20.0; // 20 seconds
  699. inputPeakHoldDuration = 1.0; // 1 second
  700. meterThickness = 3; // Bar thickness in pixels
  701. meterFontScaling =
  702. 0.7; // Font size for numbers is 70% of Widget's font size
  703. channels = (int)audio_output_get_channels(obs_get_audio());
  704. doLayout();
  705. updateTimerRef = updateTimer.lock();
  706. if (!updateTimerRef) {
  707. updateTimerRef = std::make_shared<VolumeMeterTimer>();
  708. updateTimerRef->setTimerType(Qt::PreciseTimer);
  709. updateTimerRef->start(16);
  710. updateTimer = updateTimerRef;
  711. }
  712. updateTimerRef->AddVolControl(this);
  713. }
  714. VolumeMeter::~VolumeMeter()
  715. {
  716. updateTimerRef->RemoveVolControl(this);
  717. }
  718. void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
  719. const float peak[MAX_AUDIO_CHANNELS],
  720. const float inputPeak[MAX_AUDIO_CHANNELS])
  721. {
  722. uint64_t ts = os_gettime_ns();
  723. QMutexLocker locker(&dataMutex);
  724. currentLastUpdateTime = ts;
  725. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  726. currentMagnitude[channelNr] = magnitude[channelNr];
  727. currentPeak[channelNr] = peak[channelNr];
  728. currentInputPeak[channelNr] = inputPeak[channelNr];
  729. }
  730. // In case there are more updates then redraws we must make sure
  731. // that the ballistics of peak and hold are recalculated.
  732. locker.unlock();
  733. calculateBallistics(ts);
  734. }
  735. inline void VolumeMeter::resetLevels()
  736. {
  737. currentLastUpdateTime = 0;
  738. for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) {
  739. currentMagnitude[channelNr] = -M_INFINITE;
  740. currentPeak[channelNr] = -M_INFINITE;
  741. currentInputPeak[channelNr] = -M_INFINITE;
  742. displayMagnitude[channelNr] = -M_INFINITE;
  743. displayPeak[channelNr] = -M_INFINITE;
  744. displayPeakHold[channelNr] = -M_INFINITE;
  745. displayPeakHoldLastUpdateTime[channelNr] = 0;
  746. displayInputPeakHold[channelNr] = -M_INFINITE;
  747. displayInputPeakHoldLastUpdateTime[channelNr] = 0;
  748. }
  749. }
  750. bool VolumeMeter::needLayoutChange()
  751. {
  752. int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
  753. if (!currentNrAudioChannels) {
  754. struct obs_audio_info oai;
  755. obs_get_audio_info(&oai);
  756. currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1
  757. : 2;
  758. }
  759. if (displayNrAudioChannels != currentNrAudioChannels) {
  760. displayNrAudioChannels = currentNrAudioChannels;
  761. recalculateLayout = true;
  762. }
  763. return recalculateLayout;
  764. }
  765. // When this is called from the constructor, obs_volmeter_get_nr_channels has not
  766. // yet been called and Q_PROPERTY settings have not yet been read from the
  767. // stylesheet.
  768. inline void VolumeMeter::doLayout()
  769. {
  770. QMutexLocker locker(&dataMutex);
  771. if (displayNrAudioChannels) {
  772. int meterSize = std::floor(22 / displayNrAudioChannels);
  773. setMeterThickness(std::clamp(meterSize, 3, 7));
  774. }
  775. recalculateLayout = false;
  776. tickFont = font();
  777. QFontInfo info(tickFont);
  778. tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling);
  779. QFontMetrics metrics(tickFont);
  780. if (vertical) {
  781. // Each meter channel is meterThickness pixels wide, plus one pixel
  782. // between channels, but not after the last.
  783. // Add 4 pixels for ticks, space to hold our longest label in this font,
  784. // and a few pixels before the fader.
  785. QRect scaleBounds = metrics.boundingRect("-88");
  786. setMinimumSize(displayNrAudioChannels * (meterThickness + 1) -
  787. 1 + 10 + scaleBounds.width() + 2,
  788. 100);
  789. } else {
  790. // Each meter channel is meterThickness pixels high, plus one pixel
  791. // between channels, but not after the last.
  792. // Add 4 pixels for ticks, and space high enough to hold our label in
  793. // this font, presuming that digits don't have descenders.
  794. setMinimumSize(100,
  795. displayNrAudioChannels * (meterThickness + 1) -
  796. 1 + 4 + metrics.capHeight());
  797. }
  798. resetLevels();
  799. }
  800. inline bool VolumeMeter::detectIdle(uint64_t ts)
  801. {
  802. double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001;
  803. if (timeSinceLastUpdate > 0.5) {
  804. resetLevels();
  805. return true;
  806. } else {
  807. return false;
  808. }
  809. }
  810. inline void
  811. VolumeMeter::calculateBallisticsForChannel(int channelNr, uint64_t ts,
  812. qreal timeSinceLastRedraw)
  813. {
  814. if (currentPeak[channelNr] >= displayPeak[channelNr] ||
  815. isnan(displayPeak[channelNr])) {
  816. // Attack of peak is immediate.
  817. displayPeak[channelNr] = currentPeak[channelNr];
  818. } else {
  819. // Decay of peak is 40 dB / 1.7 seconds for Fast Profile
  820. // 20 dB / 1.7 seconds for Medium Profile (Type I PPM)
  821. // 24 dB / 2.8 seconds for Slow Profile (Type II PPM)
  822. float decay = float(peakDecayRate * timeSinceLastRedraw);
  823. displayPeak[channelNr] = CLAMP(displayPeak[channelNr] - decay,
  824. currentPeak[channelNr], 0);
  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. CLAMP(displayMagnitude[channelNr] + attack,
  877. (float)minimumLevel, 0);
  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. }