From 1df513585db04eff507879114c7459b3ce6a3675 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Thu, 3 Mar 2022 20:39:34 +0100 Subject: [PATCH] Add option to specifiy rate of change instead of duration for audio fade --- data/locale/en-US.ini | 5 +- src/headers/macro-action-audio.hpp | 12 +++ src/macro-action-audio.cpp | 160 +++++++++++++++++++++++++---- 3 files changed, 156 insertions(+), 21 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index f76da220..11e09518 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -325,7 +325,10 @@ AdvSceneSwitcher.action.audio.type.mute="Mute" AdvSceneSwitcher.action.audio.type.unmute="Unmute" AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume" AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume" -AdvSceneSwitcher.action.audio.fade="{{fade}}Fade over {{duration}} seconds. {{wait}} Wait for fade to complete." +AdvSceneSwitcher.action.audio.fade.type.duration="over a duration of" +AdvSceneSwitcher.action.audio.fade.type.rate="at a rate of" +AdvSceneSwitcher.action.audio.fade.duration="{{fade}}Fade {{fadeTypes}} {{duration}} seconds. {{wait}} Wait for fade to complete." +AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade {{fadeTypes}} {{rate}}per second. {{wait}} Wait for fade to complete." AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}" AdvSceneSwitcher.action.recording="Recording" AdvSceneSwitcher.action.recording.type.stop="Stop recording" diff --git a/src/headers/macro-action-audio.hpp b/src/headers/macro-action-audio.hpp index afa81685..8b18cf41 100644 --- a/src/headers/macro-action-audio.hpp +++ b/src/headers/macro-action-audio.hpp @@ -3,6 +3,7 @@ #include "duration-control.hpp" #include +#include #include #include @@ -13,6 +14,11 @@ enum class AudioAction { MASTER_VOLUME, }; +enum class FadeType { + DURATION, + RATE, +}; + class MacroActionAudio : public MacroAction { public: MacroActionAudio(Macro *m) : MacroAction(m) {} @@ -29,9 +35,11 @@ public: OBSWeakSource _audioSource; AudioAction _action = AudioAction::MUTE; + FadeType _fadeType = FadeType::DURATION; int _volume = 0; bool _fade = false; Duration _duration; + double _rate = 100.; bool _wait = false; private: @@ -67,16 +75,20 @@ private slots: void VolumeChanged(int value); void FadeChanged(int value); void DurationChanged(double seconds); + void RateChanged(double value); void WaitChanged(int value); + void FadeTypeChanged(int value); signals: void HeaderInfoChanged(const QString &); protected: QComboBox *_audioSources; QComboBox *_actions; + QComboBox *_fadeTypes; QSpinBox *_volumePercent; QCheckBox *_fade; DurationSelection *_duration; + QDoubleSpinBox *_rate; QCheckBox *_wait; QHBoxLayout *_fadeLayout; std::shared_ptr _entryData; diff --git a/src/macro-action-audio.cpp b/src/macro-action-audio.cpp index 52177e11..3b464256 100644 --- a/src/macro-action-audio.cpp +++ b/src/macro-action-audio.cpp @@ -18,6 +18,12 @@ const static std::map actionTypes = { "AdvSceneSwitcher.action.audio.type.masterVolume"}, }; +const static std::map fadeTypes = { + {FadeType::DURATION, + "AdvSceneSwitcher.action.audio.fade.type.duration"}, + {FadeType::RATE, "AdvSceneSwitcher.action.audio.fade.type.rate"}, +}; + constexpr auto fadeInterval = std::chrono::milliseconds(100); constexpr float minFade = 0.000001f; @@ -31,18 +37,29 @@ void MacroActionAudio::FadeSourceVolume() float curVol = obs_source_get_volume(s); obs_source_release(s); bool volIncrease = curVol <= vol; - int nrSteps = _duration.seconds * 1000 / fadeInterval.count(); float volDiff = (volIncrease) ? vol - curVol : curVol - vol; - float volStep = volDiff / nrSteps; + int nrSteps = 0; + float volStep = 0.; + if (_fadeType == FadeType::DURATION) { + nrSteps = _duration.seconds * 1000 / fadeInterval.count(); + volStep = volDiff / nrSteps; + } else { + volStep = _rate / 1000.0f; + nrSteps = volDiff / volStep; + } - if (volStep < minFade) { + if (volStep < minFade || nrSteps <= 1) { + auto s = obs_weak_source_get_source(_audioSource); + obs_source_set_volume(s, vol); switcher->activeAudioFades[GetWeakSourceName(_audioSource)] = false; + obs_source_release(s); return; } auto macro = GetMacro(); - for (int step = 0; step < nrSteps && !macro->GetStop(); ++step) { + int step = 0; + for (; step < nrSteps && !macro->GetStop(); ++step) { auto s = obs_weak_source_get_source(_audioSource); if (!s) { return; @@ -53,6 +70,13 @@ void MacroActionAudio::FadeSourceVolume() obs_source_release(s); } + // As a final step set desired volume once again in case floating-point + // precision errors compounded to a noticeable error + if (step == nrSteps) { + s = obs_weak_source_get_source(_audioSource); + obs_source_set_volume(s, vol); + obs_source_release(s); + } switcher->activeAudioFades[GetWeakSourceName(_audioSource)] = false; } void MacroActionAudio::FadeMasterVolume() @@ -60,22 +84,36 @@ void MacroActionAudio::FadeMasterVolume() float vol = (float)_volume / 100.0f; float curVol = obs_get_master_volume(); bool volIncrease = curVol <= vol; - int nrSteps = _duration.seconds * 1000 / fadeInterval.count(); float volDiff = (volIncrease) ? vol - curVol : curVol - vol; - float volStep = volDiff / nrSteps; + int nrSteps = 0; + float volStep = 0.; + if (_fadeType == FadeType::DURATION) { + nrSteps = _duration.seconds * 1000 / fadeInterval.count(); + volStep = volDiff / nrSteps; + } else { + volStep = _rate / 100.0f; + nrSteps = volDiff / volStep; + } - if (volStep < minFade) { + if (volStep < minFade || nrSteps <= 1) { + obs_set_master_volume(vol); switcher->masterAudioFadeActive = false; return; } auto macro = GetMacro(); - for (int step = 0; step < nrSteps && !macro->GetStop(); ++step) { + int step = 0; + for (; step < nrSteps && !macro->GetStop(); ++step) { curVol = (volIncrease) ? curVol + volStep : curVol - volStep; obs_set_master_volume(curVol); std::this_thread::sleep_for(fadeInterval); } + // As a final step set desired volume once again in case floating-point + // precision errors compounded to a noticeable error + if (step == nrSteps) { + obs_set_master_volume(vol); + } switcher->masterAudioFadeActive = false; } @@ -172,7 +210,9 @@ bool MacroActionAudio::Save(obs_data_t *obj) GetWeakSourceName(_audioSource).c_str()); obs_data_set_int(obj, "action", static_cast(_action)); obs_data_set_int(obj, "volume", _volume); + obs_data_set_double(obj, "rate", _rate); obs_data_set_bool(obj, "fade", _fade); + obs_data_set_int(obj, "fadeType", static_cast(_fadeType)); obs_data_set_bool(obj, "wait", _wait); return true; } @@ -185,12 +225,19 @@ bool MacroActionAudio::Load(obs_data_t *obj) _audioSource = GetWeakSourceByName(audioSourceName); _action = static_cast(obs_data_get_int(obj, "action")); _volume = obs_data_get_int(obj, "volume"); + _rate = obs_data_get_double(obj, "rate"); _fade = obs_data_get_bool(obj, "fade"); if (obs_data_has_user_value(obj, "wait")) { _wait = obs_data_get_bool(obj, "wait"); } else { _wait = false; } + if (obs_data_has_user_value(obj, "fadeType")) { + _fadeType = static_cast( + obs_data_get_int(obj, "fadeType")); + } else { + _fadeType = FadeType::DURATION; + } return true; } @@ -209,22 +256,36 @@ static inline void populateActionSelection(QComboBox *list) } } +static inline void populateFadeTypeSelection(QComboBox *list) +{ + for (auto entry : fadeTypes) { + list->addItem(obs_module_text(entry.second.c_str())); + } +} + MacroActionAudioEdit::MacroActionAudioEdit( QWidget *parent, std::shared_ptr entryData) - : QWidget(parent) + : QWidget(parent), + _audioSources(new QComboBox), + _actions(new QComboBox), + _volumePercent(new QSpinBox), + _fade(new QCheckBox), + _wait(new QCheckBox), + _duration(new DurationSelection(parent, false)), + _rate(new QDoubleSpinBox), + _fadeTypes(new QComboBox) { - _audioSources = new QComboBox(); - _actions = new QComboBox(); - _volumePercent = new QSpinBox(); _volumePercent->setMinimum(0); _volumePercent->setMaximum(2000); _volumePercent->setSuffix("%"); - _fade = new QCheckBox(); - _wait = new QCheckBox(); - _duration = new DurationSelection(parent, false); + + _rate->setMinimum(0.01); + _rate->setMaximum(999.); + _rate->setSuffix("%"); populateActionSelection(_actions); populateAudioSelection(_audioSources); + populateFadeTypeSelection(_fadeTypes); QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, SLOT(ActionChanged(int))); @@ -237,20 +298,30 @@ MacroActionAudioEdit::MacroActionAudioEdit( SLOT(FadeChanged(int))); QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this, SLOT(DurationChanged(double))); + QWidget::connect(_rate, SIGNAL(valueChanged(double)), this, + SLOT(RateChanged(double))); QWidget::connect(_wait, SIGNAL(stateChanged(int)), this, SLOT(WaitChanged(int))); + QWidget::connect(_fadeTypes, SIGNAL(currentIndexChanged(int)), this, + SLOT(FadeTypeChanged(int))); std::unordered_map widgetPlaceholders = { - {"{{audioSources}}", _audioSources}, {"{{actions}}", _actions}, - {"{{volume}}", _volumePercent}, {"{{fade}}", _fade}, - {"{{duration}}", _duration}, {"{{wait}}", _wait}, + {"{{audioSources}}", _audioSources}, + {"{{actions}}", _actions}, + {"{{volume}}", _volumePercent}, + {"{{fade}}", _fade}, + {"{{duration}}", _duration}, + {"{{rate}}", _rate}, + {"{{wait}}", _wait}, + {"{{fadeTypes}}", _fadeTypes}, }; QHBoxLayout *entryLayout = new QHBoxLayout; placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"), entryLayout, widgetPlaceholders); _fadeLayout = new QHBoxLayout; - placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.fade"), - _fadeLayout, widgetPlaceholders); + placeWidgets( + obs_module_text("AdvSceneSwitcher.action.audio.fade.duration"), + _fadeLayout, widgetPlaceholders); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(entryLayout); mainLayout->addLayout(_fadeLayout); @@ -276,6 +347,32 @@ void MacroActionAudioEdit::SetWidgetVisibility() { _volumePercent->setVisible(hasVolumeControl(_entryData->_action)); _audioSources->setVisible(hasSourceControl(_entryData->_action)); + + _fadeLayout->removeWidget(_fade); + _fadeLayout->removeWidget(_fadeTypes); + _fadeLayout->removeWidget(_duration); + _fadeLayout->removeWidget(_rate); + _fadeLayout->removeWidget(_wait); + clearLayout(_fadeLayout); + std::unordered_map widgetPlaceholders = { + {"{{fade}}", _fade}, {"{{duration}}", _duration}, + {"{{rate}}", _rate}, {"{{wait}}", _wait}, + {"{{fadeTypes}}", _fadeTypes}, + }; + if (_entryData->_fadeType == FadeType::DURATION) { + placeWidgets( + obs_module_text( + "AdvSceneSwitcher.action.audio.fade.duration"), + _fadeLayout, widgetPlaceholders); + } else { + placeWidgets(obs_module_text( + "AdvSceneSwitcher.action.audio.fade.rate"), + _fadeLayout, widgetPlaceholders); + } + + _duration->setVisible(_entryData->_fadeType == FadeType::DURATION); + _rate->setVisible(_entryData->_fadeType == FadeType::RATE); + setLayoutVisible(_fadeLayout, hasVolumeControl(_entryData->_action)); adjustSize(); } @@ -292,7 +389,9 @@ void MacroActionAudioEdit::UpdateEntryData() _volumePercent->setValue(_entryData->_volume); _fade->setChecked(_entryData->_fade); _duration->SetDuration(_entryData->_duration); + _rate->setValue(_entryData->_rate); _wait->setChecked(_entryData->_wait); + _fadeTypes->setCurrentIndex(static_cast(_entryData->_fadeType)); SetWidgetVisibility(); } @@ -349,6 +448,16 @@ void MacroActionAudioEdit::DurationChanged(double seconds) _entryData->_duration.seconds = seconds; } +void MacroActionAudioEdit::RateChanged(double value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_rate = value; +} + void MacroActionAudioEdit::WaitChanged(int value) { if (_loading || !_entryData) { @@ -358,3 +467,14 @@ void MacroActionAudioEdit::WaitChanged(int value) std::lock_guard lock(switcher->m); _entryData->_wait = value; } + +void MacroActionAudioEdit::FadeTypeChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_fadeType = static_cast(value); + SetWidgetVisibility(); +}