diff options
author | Boris I. Bendovsky <[email protected]> | 2022-01-30 14:47:32 +0200 |
---|---|---|
committer | GitHub <[email protected]> | 2022-01-30 04:47:32 -0800 |
commit | 19ed994dc30ed84ea7cbbb5152577669fc25caf6 (patch) | |
tree | f68933bf8f778806618bd6c0b1bf9ced1b0ccf08 /al/effects | |
parent | 619249371a40f03cf988d1f5750d643df797c485 (diff) |
Add EAX extensions (EAX 2.0-5.0, X-RAM) (#632)
* Add EAX extensions (EAX 2.0-5.0, X-RAM)
* Comment out C++17 leftovers
* Remove everything related to patching
* Update alsoftrc.sample
* Rewrite integration
* Fix GCC compilation under Linux
* Always reset EAX effect properties when loading it into FX slot
Diffstat (limited to 'al/effects')
-rw-r--r-- | al/effects/autowah.cpp | 471 | ||||
-rw-r--r-- | al/effects/chorus.cpp | 1240 | ||||
-rw-r--r-- | al/effects/compressor.cpp | 272 | ||||
-rw-r--r-- | al/effects/distortion.cpp | 536 | ||||
-rw-r--r-- | al/effects/echo.cpp | 534 | ||||
-rw-r--r-- | al/effects/effects.cpp | 106 | ||||
-rw-r--r-- | al/effects/effects.h | 11 | ||||
-rw-r--r-- | al/effects/equalizer.cpp | 866 | ||||
-rw-r--r-- | al/effects/fshifter.cpp | 414 | ||||
-rw-r--r-- | al/effects/modulator.cpp | 411 | ||||
-rw-r--r-- | al/effects/null.cpp | 57 | ||||
-rw-r--r-- | al/effects/pshifter.cpp | 338 | ||||
-rw-r--r-- | al/effects/reverb.cpp | 1922 | ||||
-rw-r--r-- | al/effects/vmorpher.cpp | 624 |
14 files changed, 7802 insertions, 0 deletions
diff --git a/al/effects/autowah.cpp b/al/effects/autowah.cpp index 9abef1f7..bdd1bc09 100644 --- a/al/effects/autowah.cpp +++ b/al/effects/autowah.cpp @@ -11,6 +11,14 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + + namespace { void Autowah_setParamf(EffectProps *props, ALenum param, float val) @@ -107,3 +115,466 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Autowah); const EffectProps AutowahEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxAutoWahEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxAutoWahEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxAutoWahEffectDirtyFlagsValue flAttackTime : 1; + EaxAutoWahEffectDirtyFlagsValue flReleaseTime : 1; + EaxAutoWahEffectDirtyFlagsValue lResonance : 1; + EaxAutoWahEffectDirtyFlagsValue lPeakLevel : 1; +}; // EaxAutoWahEffectDirtyFlags + + +class EaxAutoWahEffect final : + public EaxEffect +{ +public: + EaxAutoWahEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXAUTOWAHPROPERTIES eax_{}; + EAXAUTOWAHPROPERTIES eax_d_{}; + EaxAutoWahEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_attack_time(); + + void set_efx_release_time(); + + void set_efx_resonance(); + + void set_efx_peak_gain(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_attack_time( + float flAttackTime); + + void validate_release_time( + float flReleaseTime); + + void validate_resonance( + long lResonance); + + void validate_peak_level( + long lPeakLevel); + + void validate_all( + const EAXAUTOWAHPROPERTIES& eax_all); + + + void defer_attack_time( + float flAttackTime); + + void defer_release_time( + float flReleaseTime); + + void defer_resonance( + long lResonance); + + void defer_peak_level( + long lPeakLevel); + + void defer_all( + const EAXAUTOWAHPROPERTIES& eax_all); + + + void defer_attack_time( + const EaxEaxCall& eax_call); + + void defer_release_time( + const EaxEaxCall& eax_call); + + void defer_resonance( + const EaxEaxCall& eax_call); + + void defer_peak_level( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxAutoWahEffect + + +class EaxAutoWahEffectException : + public EaxException +{ +public: + explicit EaxAutoWahEffectException( + const char* message) + : + EaxException{"EAX_AUTO_WAH_EFFECT", message} + { + } +}; // EaxAutoWahEffectException + + +EaxAutoWahEffect::EaxAutoWahEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxAutoWahEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxAutoWahEffect::set_eax_defaults() +{ + eax_.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + eax_.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + eax_.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + eax_.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + + eax_d_ = eax_; +} + +void EaxAutoWahEffect::set_efx_attack_time() +{ + const auto attack_time = clamp( + eax_.flAttackTime, + AL_AUTOWAH_MIN_ATTACK_TIME, + AL_AUTOWAH_MAX_ATTACK_TIME); + + al_effect_props_.Autowah.AttackTime = attack_time; +} + +void EaxAutoWahEffect::set_efx_release_time() +{ + const auto release_time = clamp( + eax_.flReleaseTime, + AL_AUTOWAH_MIN_RELEASE_TIME, + AL_AUTOWAH_MAX_RELEASE_TIME); + + al_effect_props_.Autowah.ReleaseTime = release_time; +} + +void EaxAutoWahEffect::set_efx_resonance() +{ + const auto resonance = clamp( + level_mb_to_gain(static_cast<float>(eax_.lResonance)), + AL_AUTOWAH_MIN_RESONANCE, + AL_AUTOWAH_MAX_RESONANCE); + + al_effect_props_.Autowah.Resonance = resonance; +} + +void EaxAutoWahEffect::set_efx_peak_gain() +{ + const auto peak_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lPeakLevel)), + AL_AUTOWAH_MIN_PEAK_GAIN, + AL_AUTOWAH_MAX_PEAK_GAIN); + + al_effect_props_.Autowah.PeakGain = peak_gain; +} + +void EaxAutoWahEffect::set_efx_defaults() +{ + set_efx_attack_time(); + set_efx_release_time(); + set_efx_resonance(); + set_efx_peak_gain(); +} + +bool EaxAutoWahEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAUTOWAH_NONE: + break; + + case EAXAUTOWAH_ALLPARAMETERS: + eax_call.set_value<EaxAutoWahEffectException>(eax_); + break; + + case EAXAUTOWAH_ATTACKTIME: + eax_call.set_value<EaxAutoWahEffectException>(eax_.flAttackTime); + break; + + case EAXAUTOWAH_RELEASETIME: + eax_call.set_value<EaxAutoWahEffectException>(eax_.flReleaseTime); + break; + + case EAXAUTOWAH_RESONANCE: + eax_call.set_value<EaxAutoWahEffectException>(eax_.lResonance); + break; + + case EAXAUTOWAH_PEAKLEVEL: + eax_call.set_value<EaxAutoWahEffectException>(eax_.lPeakLevel); + break; + + default: + throw EaxAutoWahEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxAutoWahEffect::validate_attack_time( + float flAttackTime) +{ + eax_validate_range<EaxAutoWahEffectException>( + "Attack Time", + flAttackTime, + EAXAUTOWAH_MINATTACKTIME, + EAXAUTOWAH_MAXATTACKTIME); +} + +void EaxAutoWahEffect::validate_release_time( + float flReleaseTime) +{ + eax_validate_range<EaxAutoWahEffectException>( + "Release Time", + flReleaseTime, + EAXAUTOWAH_MINRELEASETIME, + EAXAUTOWAH_MAXRELEASETIME); +} + +void EaxAutoWahEffect::validate_resonance( + long lResonance) +{ + eax_validate_range<EaxAutoWahEffectException>( + "Resonance", + lResonance, + EAXAUTOWAH_MINRESONANCE, + EAXAUTOWAH_MAXRESONANCE); +} + +void EaxAutoWahEffect::validate_peak_level( + long lPeakLevel) +{ + eax_validate_range<EaxAutoWahEffectException>( + "Peak Level", + lPeakLevel, + EAXAUTOWAH_MINPEAKLEVEL, + EAXAUTOWAH_MAXPEAKLEVEL); +} + +void EaxAutoWahEffect::validate_all( + const EAXAUTOWAHPROPERTIES& eax_all) +{ + validate_attack_time(eax_all.flAttackTime); + validate_release_time(eax_all.flReleaseTime); + validate_resonance(eax_all.lResonance); + validate_peak_level(eax_all.lPeakLevel); +} + +void EaxAutoWahEffect::defer_attack_time( + float flAttackTime) +{ + eax_d_.flAttackTime = flAttackTime; + eax_dirty_flags_.flAttackTime = (eax_.flAttackTime != eax_d_.flAttackTime); +} + +void EaxAutoWahEffect::defer_release_time( + float flReleaseTime) +{ + eax_d_.flReleaseTime = flReleaseTime; + eax_dirty_flags_.flReleaseTime = (eax_.flReleaseTime != eax_d_.flReleaseTime); +} + +void EaxAutoWahEffect::defer_resonance( + long lResonance) +{ + eax_d_.lResonance = lResonance; + eax_dirty_flags_.lResonance = (eax_.lResonance != eax_d_.lResonance); +} + +void EaxAutoWahEffect::defer_peak_level( + long lPeakLevel) +{ + eax_d_.lPeakLevel = lPeakLevel; + eax_dirty_flags_.lPeakLevel = (eax_.lPeakLevel != eax_d_.lPeakLevel); +} + +void EaxAutoWahEffect::defer_all( + const EAXAUTOWAHPROPERTIES& eax_all) +{ + validate_all(eax_all); + + defer_attack_time(eax_all.flAttackTime); + defer_release_time(eax_all.flReleaseTime); + defer_resonance(eax_all.lResonance); + defer_peak_level(eax_all.lPeakLevel); +} + +void EaxAutoWahEffect::defer_attack_time( + const EaxEaxCall& eax_call) +{ + const auto& attack_time = + eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flAttackTime)>(); + + validate_attack_time(attack_time); + defer_attack_time(attack_time); +} + +void EaxAutoWahEffect::defer_release_time( + const EaxEaxCall& eax_call) +{ + const auto& release_time = + eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::flReleaseTime)>(); + + validate_release_time(release_time); + defer_release_time(release_time); +} + +void EaxAutoWahEffect::defer_resonance( + const EaxEaxCall& eax_call) +{ + const auto& resonance = + eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lResonance)>(); + + validate_resonance(resonance); + defer_resonance(resonance); +} + +void EaxAutoWahEffect::defer_peak_level( + const EaxEaxCall& eax_call) +{ + const auto& peak_level = + eax_call.get_value<EaxAutoWahEffectException, const decltype(EAXAUTOWAHPROPERTIES::lPeakLevel)>(); + + validate_peak_level(peak_level); + defer_peak_level(peak_level); +} + +void EaxAutoWahEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxAutoWahEffectException, const EAXAUTOWAHPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxAutoWahEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxAutoWahEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flAttackTime) + { + set_efx_attack_time(); + } + + if (eax_dirty_flags_.flReleaseTime) + { + set_efx_release_time(); + } + + if (eax_dirty_flags_.lResonance) + { + set_efx_resonance(); + } + + if (eax_dirty_flags_.lPeakLevel) + { + set_efx_peak_gain(); + } + + eax_dirty_flags_ = EaxAutoWahEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxAutoWahEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAUTOWAH_NONE: + break; + + case EAXAUTOWAH_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXAUTOWAH_ATTACKTIME: + defer_attack_time(eax_call); + break; + + case EAXAUTOWAH_RELEASETIME: + defer_release_time(eax_call); + break; + + case EAXAUTOWAH_RESONANCE: + defer_resonance(eax_call); + break; + + case EAXAUTOWAH_PEAKLEVEL: + defer_peak_level(eax_call); + break; + + default: + throw EaxAutoWahEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_auto_wah_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<::EaxAutoWahEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/chorus.cpp b/al/effects/chorus.cpp index 2466d97e..ed994fbb 100644 --- a/al/effects/chorus.cpp +++ b/al/effects/chorus.cpp @@ -11,6 +11,15 @@ #include "core/logging.h" #include "effects.h" +#if ALSOFT_EAX +#include <cassert> + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -279,3 +288,1234 @@ const EffectProps ChorusEffectProps{genDefaultChorusProps()}; DEFINE_ALEFFECT_VTABLE(Flanger); const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; + + +#if ALSOFT_EAX +namespace +{ + + +void eax_set_efx_waveform( + ALenum waveform, + EffectProps& al_effect_props) +{ + const auto efx_waveform = WaveformFromEnum(waveform); + assert(efx_waveform.has_value()); + al_effect_props.Chorus.Waveform = *efx_waveform; +} + +void eax_set_efx_phase( + ALint phase, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Phase = phase; +} + +void eax_set_efx_rate( + ALfloat rate, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Rate = rate; +} + +void eax_set_efx_depth( + ALfloat depth, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Depth = depth; +} + +void eax_set_efx_feedback( + ALfloat feedback, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Feedback = feedback; +} + +void eax_set_efx_delay( + ALfloat delay, + EffectProps& al_effect_props) +{ + al_effect_props.Chorus.Delay = delay; +} + + +using EaxChorusEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxChorusEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxChorusEffectDirtyFlagsValue ulWaveform : 1; + EaxChorusEffectDirtyFlagsValue lPhase : 1; + EaxChorusEffectDirtyFlagsValue flRate : 1; + EaxChorusEffectDirtyFlagsValue flDepth : 1; + EaxChorusEffectDirtyFlagsValue flFeedback : 1; + EaxChorusEffectDirtyFlagsValue flDelay : 1; +}; // EaxChorusEffectDirtyFlags + + +class EaxChorusEffect final : + public EaxEffect +{ +public: + EaxChorusEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + EAXCHORUSPROPERTIES eax_{}; + EAXCHORUSPROPERTIES eax_d_{}; + EaxChorusEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults() noexcept; + + + void set_efx_waveform(); + + void set_efx_phase(); + + void set_efx_rate(); + + void set_efx_depth(); + + void set_efx_feedback(); + + void set_efx_delay(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_waveform( + unsigned long ulWaveform); + + void validate_phase( + long lPhase); + + void validate_rate( + float flRate); + + void validate_depth( + float flDepth); + + void validate_feedback( + float flFeedback); + + void validate_delay( + float flDelay); + + void validate_all( + const EAXCHORUSPROPERTIES& eax_all); + + + void defer_waveform( + unsigned long ulWaveform); + + void defer_phase( + long lPhase); + + void defer_rate( + float flRate); + + void defer_depth( + float flDepth); + + void defer_feedback( + float flFeedback); + + void defer_delay( + float flDelay); + + void defer_all( + const EAXCHORUSPROPERTIES& eax_all); + + + void defer_waveform( + const EaxEaxCall& eax_call); + + void defer_phase( + const EaxEaxCall& eax_call); + + void defer_rate( + const EaxEaxCall& eax_call); + + void defer_depth( + const EaxEaxCall& eax_call); + + void defer_feedback( + const EaxEaxCall& eax_call); + + void defer_delay( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxChorusEffect + + +class EaxChorusEffectException : + public EaxException +{ +public: + explicit EaxChorusEffectException( + const char* message) + : + EaxException{"EAX_CHORUS_EFFECT", message} + { + } +}; // EaxChorusEffectException + + +EaxChorusEffect::EaxChorusEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxChorusEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxChorusEffect::set_eax_defaults() noexcept +{ + eax_.ulWaveform = EAXCHORUS_DEFAULTWAVEFORM; + eax_.lPhase = EAXCHORUS_DEFAULTPHASE; + eax_.flRate = EAXCHORUS_DEFAULTRATE; + eax_.flDepth = EAXCHORUS_DEFAULTDEPTH; + eax_.flFeedback = EAXCHORUS_DEFAULTFEEDBACK; + eax_.flDelay = EAXCHORUS_DEFAULTDELAY; + + eax_d_ = eax_; +} + +void EaxChorusEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast<ALint>(eax_.ulWaveform), + AL_CHORUS_MIN_WAVEFORM, + AL_CHORUS_MAX_WAVEFORM); + + eax_set_efx_waveform(waveform, al_effect_props_); +} + +void EaxChorusEffect::set_efx_phase() +{ + const auto phase = clamp( + static_cast<ALint>(eax_.lPhase), + AL_CHORUS_MIN_PHASE, + AL_CHORUS_MAX_PHASE); + + eax_set_efx_phase(phase, al_effect_props_); +} + +void EaxChorusEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_CHORUS_MIN_RATE, + AL_CHORUS_MAX_RATE); + + eax_set_efx_rate(rate, al_effect_props_); +} + +void EaxChorusEffect::set_efx_depth() +{ + const auto depth = clamp( + eax_.flDepth, + AL_CHORUS_MIN_DEPTH, + AL_CHORUS_MAX_DEPTH); + + eax_set_efx_depth(depth, al_effect_props_); +} + +void EaxChorusEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_CHORUS_MIN_FEEDBACK, + AL_CHORUS_MAX_FEEDBACK); + + eax_set_efx_feedback(feedback, al_effect_props_); +} + +void EaxChorusEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_CHORUS_MIN_DELAY, + AL_CHORUS_MAX_DELAY); + + eax_set_efx_delay(delay, al_effect_props_); +} + +void EaxChorusEffect::set_efx_defaults() +{ + set_efx_waveform(); + set_efx_phase(); + set_efx_rate(); + set_efx_depth(); + set_efx_feedback(); + set_efx_delay(); +} + +bool EaxChorusEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXCHORUS_NONE: + break; + + case EAXCHORUS_ALLPARAMETERS: + eax_call.set_value<EaxChorusEffectException>(eax_); + break; + + case EAXCHORUS_WAVEFORM: + eax_call.set_value<EaxChorusEffectException>(eax_.ulWaveform); + break; + + case EAXCHORUS_PHASE: + eax_call.set_value<EaxChorusEffectException>(eax_.lPhase); + break; + + case EAXCHORUS_RATE: + eax_call.set_value<EaxChorusEffectException>(eax_.flRate); + break; + + case EAXCHORUS_DEPTH: + eax_call.set_value<EaxChorusEffectException>(eax_.flDepth); + break; + + case EAXCHORUS_FEEDBACK: + eax_call.set_value<EaxChorusEffectException>(eax_.flFeedback); + break; + + case EAXCHORUS_DELAY: + eax_call.set_value<EaxChorusEffectException>(eax_.flDelay); + break; + + default: + throw EaxChorusEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxChorusEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range<EaxChorusEffectException>( + "Waveform", + ulWaveform, + EAXCHORUS_MINWAVEFORM, + EAXCHORUS_MAXWAVEFORM); +} + +void EaxChorusEffect::validate_phase( + long lPhase) +{ + eax_validate_range<EaxChorusEffectException>( + "Phase", + lPhase, + EAXCHORUS_MINPHASE, + EAXCHORUS_MAXPHASE); +} + +void EaxChorusEffect::validate_rate( + float flRate) +{ + eax_validate_range<EaxChorusEffectException>( + "Rate", + flRate, + EAXCHORUS_MINRATE, + EAXCHORUS_MAXRATE); +} + +void EaxChorusEffect::validate_depth( + float flDepth) +{ + eax_validate_range<EaxChorusEffectException>( + "Depth", + flDepth, + EAXCHORUS_MINDEPTH, + EAXCHORUS_MAXDEPTH); +} + +void EaxChorusEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range<EaxChorusEffectException>( + "Feedback", + flFeedback, + EAXCHORUS_MINFEEDBACK, + EAXCHORUS_MAXFEEDBACK); +} + +void EaxChorusEffect::validate_delay( + float flDelay) +{ + eax_validate_range<EaxChorusEffectException>( + "Delay", + flDelay, + EAXCHORUS_MINDELAY, + EAXCHORUS_MAXDELAY); +} + +void EaxChorusEffect::validate_all( + const EAXCHORUSPROPERTIES& eax_all) +{ + validate_waveform(eax_all.ulWaveform); + validate_phase(eax_all.lPhase); + validate_rate(eax_all.flRate); + validate_depth(eax_all.flDepth); + validate_feedback(eax_all.flFeedback); + validate_delay(eax_all.flDelay); +} + +void EaxChorusEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxChorusEffect::defer_phase( + long lPhase) +{ + eax_d_.lPhase = lPhase; + eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); +} + +void EaxChorusEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxChorusEffect::defer_depth( + float flDepth) +{ + eax_d_.flDepth = flDepth; + eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); +} + +void EaxChorusEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxChorusEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxChorusEffect::defer_all( + const EAXCHORUSPROPERTIES& eax_all) +{ + defer_waveform(eax_all.ulWaveform); + defer_phase(eax_all.lPhase); + defer_rate(eax_all.flRate); + defer_depth(eax_all.flDepth); + defer_feedback(eax_all.flFeedback); + defer_delay(eax_all.flDelay); +} + +void EaxChorusEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::ulWaveform)>(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxChorusEffect::defer_phase( + const EaxEaxCall& eax_call) +{ + const auto& phase = + eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::lPhase)>(); + + validate_phase(phase); + defer_phase(phase); +} + +void EaxChorusEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = + eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flRate)>(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxChorusEffect::defer_depth( + const EaxEaxCall& eax_call) +{ + const auto& depth = + eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flDepth)>(); + + validate_depth(depth); + defer_depth(depth); +} + +void EaxChorusEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flFeedback)>(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxChorusEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value<EaxChorusEffectException, const decltype(EAXCHORUSPROPERTIES::flDelay)>(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxChorusEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxChorusEffectException, const EAXCHORUSPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxChorusEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxChorusEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.lPhase) + { + set_efx_phase(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + if (eax_dirty_flags_.flDepth) + { + set_efx_depth(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + eax_dirty_flags_ = EaxChorusEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxChorusEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXCHORUS_NONE: + break; + + case EAXCHORUS_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXCHORUS_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXCHORUS_PHASE: + defer_phase(eax_call); + break; + + case EAXCHORUS_RATE: + defer_rate(eax_call); + break; + + case EAXCHORUS_DEPTH: + defer_depth(eax_call); + break; + + case EAXCHORUS_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXCHORUS_DELAY: + defer_delay(eax_call); + break; + + default: + throw EaxChorusEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_chorus_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<::EaxChorusEffect>(al_effect_props); +} + + +namespace +{ + + +using EaxFlangerEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxFlangerEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxFlangerEffectDirtyFlagsValue ulWaveform : 1; + EaxFlangerEffectDirtyFlagsValue lPhase : 1; + EaxFlangerEffectDirtyFlagsValue flRate : 1; + EaxFlangerEffectDirtyFlagsValue flDepth : 1; + EaxFlangerEffectDirtyFlagsValue flFeedback : 1; + EaxFlangerEffectDirtyFlagsValue flDelay : 1; +}; // EaxFlangerEffectDirtyFlags + + +class EaxFlangerEffect final : + public EaxEffect +{ +public: + EaxFlangerEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXFLANGERPROPERTIES eax_{}; + EAXFLANGERPROPERTIES eax_d_{}; + EaxFlangerEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_waveform(); + + void set_efx_phase(); + + void set_efx_rate(); + + void set_efx_depth(); + + void set_efx_feedback(); + + void set_efx_delay(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_waveform( + unsigned long ulWaveform); + + void validate_phase( + long lPhase); + + void validate_rate( + float flRate); + + void validate_depth( + float flDepth); + + void validate_feedback( + float flFeedback); + + void validate_delay( + float flDelay); + + void validate_all( + const EAXFLANGERPROPERTIES& all); + + + void defer_waveform( + unsigned long ulWaveform); + + void defer_phase( + long lPhase); + + void defer_rate( + float flRate); + + void defer_depth( + float flDepth); + + void defer_feedback( + float flFeedback); + + void defer_delay( + float flDelay); + + void defer_all( + const EAXFLANGERPROPERTIES& all); + + + void defer_waveform( + const EaxEaxCall& eax_call); + + void defer_phase( + const EaxEaxCall& eax_call); + + void defer_rate( + const EaxEaxCall& eax_call); + + void defer_depth( + const EaxEaxCall& eax_call); + + void defer_feedback( + const EaxEaxCall& eax_call); + + void defer_delay( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxFlangerEffect + + +class EaxFlangerEffectException : + public EaxException +{ +public: + explicit EaxFlangerEffectException( + const char* message) + : + EaxException{"EAX_FLANGER_EFFECT", message} + { + } +}; // EaxFlangerEffectException + + +EaxFlangerEffect::EaxFlangerEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxFlangerEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxFlangerEffect::set_eax_defaults() +{ + eax_.ulWaveform = EAXFLANGER_DEFAULTWAVEFORM; + eax_.lPhase = EAXFLANGER_DEFAULTPHASE; + eax_.flRate = EAXFLANGER_DEFAULTRATE; + eax_.flDepth = EAXFLANGER_DEFAULTDEPTH; + eax_.flFeedback = EAXFLANGER_DEFAULTFEEDBACK; + eax_.flDelay = EAXFLANGER_DEFAULTDELAY; + + eax_d_ = eax_; +} + +void EaxFlangerEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast<ALint>(eax_.ulWaveform), + AL_FLANGER_MIN_WAVEFORM, + AL_FLANGER_MAX_WAVEFORM); + + eax_set_efx_waveform(waveform, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_phase() +{ + const auto phase = clamp( + static_cast<ALint>(eax_.lPhase), + AL_FLANGER_MIN_PHASE, + AL_FLANGER_MAX_PHASE); + + eax_set_efx_phase(phase, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_FLANGER_MIN_RATE, + AL_FLANGER_MAX_RATE); + + eax_set_efx_rate(rate, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_depth() +{ + const auto depth = clamp( + eax_.flDepth, + AL_FLANGER_MIN_DEPTH, + AL_FLANGER_MAX_DEPTH); + + eax_set_efx_depth(depth, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_FLANGER_MIN_FEEDBACK, + AL_FLANGER_MAX_FEEDBACK); + + eax_set_efx_feedback(feedback, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_FLANGER_MIN_DELAY, + AL_FLANGER_MAX_DELAY); + + eax_set_efx_delay(delay, al_effect_props_); +} + +void EaxFlangerEffect::set_efx_defaults() +{ + set_efx_waveform(); + set_efx_phase(); + set_efx_rate(); + set_efx_depth(); + set_efx_feedback(); + set_efx_delay(); +} + +// [[nodiscard]] +bool EaxFlangerEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXFLANGER_NONE: + break; + + case EAXFLANGER_ALLPARAMETERS: + eax_call.set_value<EaxFlangerEffectException>(eax_); + break; + + case EAXFLANGER_WAVEFORM: + eax_call.set_value<EaxFlangerEffectException>(eax_.ulWaveform); + break; + + case EAXFLANGER_PHASE: + eax_call.set_value<EaxFlangerEffectException>(eax_.lPhase); + break; + + case EAXFLANGER_RATE: + eax_call.set_value<EaxFlangerEffectException>(eax_.flRate); + break; + + case EAXFLANGER_DEPTH: + eax_call.set_value<EaxFlangerEffectException>(eax_.flDepth); + break; + + case EAXFLANGER_FEEDBACK: + eax_call.set_value<EaxFlangerEffectException>(eax_.flFeedback); + break; + + case EAXFLANGER_DELAY: + eax_call.set_value<EaxFlangerEffectException>(eax_.flDelay); + break; + + default: + throw EaxFlangerEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxFlangerEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range<EaxFlangerEffectException>( + "Waveform", + ulWaveform, + EAXFLANGER_MINWAVEFORM, + EAXFLANGER_MAXWAVEFORM); +} + +void EaxFlangerEffect::validate_phase( + long lPhase) +{ + eax_validate_range<EaxFlangerEffectException>( + "Phase", + lPhase, + EAXFLANGER_MINPHASE, + EAXFLANGER_MAXPHASE); +} + +void EaxFlangerEffect::validate_rate( + float flRate) +{ + eax_validate_range<EaxFlangerEffectException>( + "Rate", + flRate, + EAXFLANGER_MINRATE, + EAXFLANGER_MAXRATE); +} + +void EaxFlangerEffect::validate_depth( + float flDepth) +{ + eax_validate_range<EaxFlangerEffectException>( + "Depth", + flDepth, + EAXFLANGER_MINDEPTH, + EAXFLANGER_MAXDEPTH); +} + +void EaxFlangerEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range<EaxFlangerEffectException>( + "Feedback", + flFeedback, + EAXFLANGER_MINFEEDBACK, + EAXFLANGER_MAXFEEDBACK); +} + +void EaxFlangerEffect::validate_delay( + float flDelay) +{ + eax_validate_range<EaxFlangerEffectException>( + "Delay", + flDelay, + EAXFLANGER_MINDELAY, + EAXFLANGER_MAXDELAY); +} + +void EaxFlangerEffect::validate_all( + const EAXFLANGERPROPERTIES& all) +{ + validate_waveform(all.ulWaveform); + validate_phase(all.lPhase); + validate_rate(all.flRate); + validate_depth(all.flDepth); + validate_feedback(all.flDelay); + validate_delay(all.flDelay); +} + +void EaxFlangerEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxFlangerEffect::defer_phase( + long lPhase) +{ + eax_d_.lPhase = lPhase; + eax_dirty_flags_.lPhase = (eax_.lPhase != eax_d_.lPhase); +} + +void EaxFlangerEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxFlangerEffect::defer_depth( + float flDepth) +{ + eax_d_.flDepth = flDepth; + eax_dirty_flags_.flDepth = (eax_.flDepth != eax_d_.flDepth); +} + +void EaxFlangerEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxFlangerEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxFlangerEffect::defer_all( + const EAXFLANGERPROPERTIES& all) +{ + defer_waveform(all.ulWaveform); + defer_phase(all.lPhase); + defer_rate(all.flRate); + defer_depth(all.flDepth); + defer_feedback(all.flDelay); + defer_delay(all.flDelay); +} + +void EaxFlangerEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::ulWaveform)>(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxFlangerEffect::defer_phase( + const EaxEaxCall& eax_call) +{ + const auto& phase = + eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::lPhase)>(); + + validate_phase(phase); + defer_phase(phase); +} + +void EaxFlangerEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = + eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flRate)>(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxFlangerEffect::defer_depth( + const EaxEaxCall& eax_call) +{ + const auto& depth = + eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDepth)>(); + + validate_depth(depth); + defer_depth(depth); +} + +void EaxFlangerEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flFeedback)>(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxFlangerEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value<EaxFlangerEffectException, const decltype(EAXFLANGERPROPERTIES::flDelay)>(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxFlangerEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxFlangerEffectException, const EAXFLANGERPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxFlangerEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxFlangerEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.lPhase) + { + set_efx_phase(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + if (eax_dirty_flags_.flDepth) + { + set_efx_depth(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + eax_dirty_flags_ = EaxFlangerEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxFlangerEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXFLANGER_NONE: + break; + + case EAXFLANGER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXFLANGER_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXFLANGER_PHASE: + defer_phase(eax_call); + break; + + case EAXFLANGER_RATE: + defer_rate(eax_call); + break; + + case EAXFLANGER_DEPTH: + defer_depth(eax_call); + break; + + case EAXFLANGER_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXFLANGER_DELAY: + defer_delay(eax_call); + break; + + default: + throw EaxFlangerEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_flanger_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxFlangerEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/compressor.cpp b/al/effects/compressor.cpp index f5db2a6f..868c5c1b 100644 --- a/al/effects/compressor.cpp +++ b/al/effects/compressor.cpp @@ -7,6 +7,13 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -70,3 +77,268 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Compressor); const EffectProps CompressorEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxCompressorEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxCompressorEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxCompressorEffectDirtyFlagsValue ulOnOff : 1; +}; // EaxCompressorEffectDirtyFlags + + +class EaxCompressorEffect final : + public EaxEffect +{ +public: + EaxCompressorEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXAGCCOMPRESSORPROPERTIES eax_{}; + EAXAGCCOMPRESSORPROPERTIES eax_d_{}; + EaxCompressorEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_on_off(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_on_off( + unsigned long ulOnOff); + + void validate_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all); + + + void defer_on_off( + unsigned long ulOnOff); + + void defer_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all); + + + void defer_on_off( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxCompressorEffect + + +class EaxCompressorEffectException : + public EaxException +{ +public: + explicit EaxCompressorEffectException( + const char* message) + : + EaxException{"EAX_COMPRESSOR_EFFECT", message} + { + } +}; // EaxCompressorEffectException + + +EaxCompressorEffect::EaxCompressorEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxCompressorEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxCompressorEffect::set_eax_defaults() +{ + eax_.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; + + eax_d_ = eax_; +} + +void EaxCompressorEffect::set_efx_on_off() +{ + const auto on_off = clamp( + static_cast<ALint>(eax_.ulOnOff), + AL_COMPRESSOR_MIN_ONOFF, + AL_COMPRESSOR_MAX_ONOFF); + + al_effect_props_.Compressor.OnOff = (on_off != AL_FALSE); +} + +void EaxCompressorEffect::set_efx_defaults() +{ + set_efx_on_off(); +} + +// [[nodiscard]] +bool EaxCompressorEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: + break; + + case EAXAGCCOMPRESSOR_ALLPARAMETERS: + eax_call.set_value<EaxCompressorEffectException>(eax_); + break; + + case EAXAGCCOMPRESSOR_ONOFF: + eax_call.set_value<EaxCompressorEffectException>(eax_.ulOnOff); + break; + + default: + throw EaxCompressorEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxCompressorEffect::validate_on_off( + unsigned long ulOnOff) +{ + eax_validate_range<EaxCompressorEffectException>( + "On-Off", + ulOnOff, + EAXAGCCOMPRESSOR_MINONOFF, + EAXAGCCOMPRESSOR_MAXONOFF); +} + +void EaxCompressorEffect::validate_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all) +{ + validate_on_off(eax_all.ulOnOff); +} + +void EaxCompressorEffect::defer_on_off( + unsigned long ulOnOff) +{ + eax_d_.ulOnOff = ulOnOff; + eax_dirty_flags_.ulOnOff = (eax_.ulOnOff != eax_d_.ulOnOff); +} + +void EaxCompressorEffect::defer_all( + const EAXAGCCOMPRESSORPROPERTIES& eax_all) +{ + defer_on_off(eax_all.ulOnOff); +} + +void EaxCompressorEffect::defer_on_off( + const EaxEaxCall& eax_call) +{ + const auto& on_off = + eax_call.get_value<EaxCompressorEffectException, const decltype(EAXAGCCOMPRESSORPROPERTIES::ulOnOff)>(); + + validate_on_off(on_off); + defer_on_off(on_off); +} + +void EaxCompressorEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxCompressorEffectException, const EAXAGCCOMPRESSORPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxCompressorEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxCompressorEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulOnOff) + { + set_efx_on_off(); + } + + eax_dirty_flags_ = EaxCompressorEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxCompressorEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: + break; + + case EAXAGCCOMPRESSOR_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXAGCCOMPRESSOR_ONOFF: + defer_on_off(eax_call); + break; + + default: + throw EaxCompressorEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_compressor_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxCompressorEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/distortion.cpp b/al/effects/distortion.cpp index f5d64a9a..062cdc54 100644 --- a/al/effects/distortion.cpp +++ b/al/effects/distortion.cpp @@ -7,6 +7,13 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -112,3 +119,532 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Distortion); const EffectProps DistortionEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxDistortionEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxDistortionEffectDirtyFlagsValue flEdge : 1; + EaxDistortionEffectDirtyFlagsValue lGain : 1; + EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1; + EaxDistortionEffectDirtyFlagsValue flEQCenter : 1; + EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1; +}; // EaxDistortionEffectDirtyFlags + + +class EaxDistortionEffect final : + public EaxEffect +{ +public: + EaxDistortionEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXDISTORTIONPROPERTIES eax_{}; + EAXDISTORTIONPROPERTIES eax_d_{}; + EaxDistortionEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_edge(); + + void set_efx_gain(); + + void set_efx_lowpass_cutoff(); + + void set_efx_eq_center(); + + void set_efx_eq_bandwidth(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_edge( + float flEdge); + + void validate_gain( + long lGain); + + void validate_lowpass_cutoff( + float flLowPassCutOff); + + void validate_eq_center( + float flEQCenter); + + void validate_eq_bandwidth( + float flEQBandwidth); + + void validate_all( + const EAXDISTORTIONPROPERTIES& eax_all); + + + void defer_edge( + float flEdge); + + void defer_gain( + long lGain); + + void defer_low_pass_cutoff( + float flLowPassCutOff); + + void defer_eq_center( + float flEQCenter); + + void defer_eq_bandwidth( + float flEQBandwidth); + + void defer_all( + const EAXDISTORTIONPROPERTIES& eax_all); + + + void defer_edge( + const EaxEaxCall& eax_call); + + void defer_gain( + const EaxEaxCall& eax_call); + + void defer_low_pass_cutoff( + const EaxEaxCall& eax_call); + + void defer_eq_center( + const EaxEaxCall& eax_call); + + void defer_eq_bandwidth( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxDistortionEffect + + +class EaxDistortionEffectException : + public EaxException +{ +public: + explicit EaxDistortionEffectException( + const char* message) + : + EaxException{"EAX_DISTORTION_EFFECT", message} + { + } +}; // EaxDistortionEffectException + + +EaxDistortionEffect::EaxDistortionEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxDistortionEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxDistortionEffect::set_eax_defaults() +{ + eax_.flEdge = EAXDISTORTION_DEFAULTEDGE; + eax_.lGain = EAXDISTORTION_DEFAULTGAIN; + eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; + eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; + eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + + eax_d_ = eax_; +} + +void EaxDistortionEffect::set_efx_edge() +{ + const auto edge = clamp( + eax_.flEdge, + AL_DISTORTION_MIN_EDGE, + AL_DISTORTION_MAX_EDGE); + + al_effect_props_.Distortion.Edge = edge; +} + +void EaxDistortionEffect::set_efx_gain() +{ + const auto gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lGain)), + AL_DISTORTION_MIN_GAIN, + AL_DISTORTION_MAX_GAIN); + + al_effect_props_.Distortion.Gain = gain; +} + +void EaxDistortionEffect::set_efx_lowpass_cutoff() +{ + const auto lowpass_cutoff = clamp( + eax_.flLowPassCutOff, + AL_DISTORTION_MIN_LOWPASS_CUTOFF, + AL_DISTORTION_MAX_LOWPASS_CUTOFF); + + al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff; +} + +void EaxDistortionEffect::set_efx_eq_center() +{ + const auto eq_center = clamp( + eax_.flEQCenter, + AL_DISTORTION_MIN_EQCENTER, + AL_DISTORTION_MAX_EQCENTER); + + al_effect_props_.Distortion.EQCenter = eq_center; +} + +void EaxDistortionEffect::set_efx_eq_bandwidth() +{ + const auto eq_bandwidth = clamp( + eax_.flEdge, + AL_DISTORTION_MIN_EQBANDWIDTH, + AL_DISTORTION_MAX_EQBANDWIDTH); + + al_effect_props_.Distortion.EQBandwidth = eq_bandwidth; +} + +void EaxDistortionEffect::set_efx_defaults() +{ + set_efx_edge(); + set_efx_gain(); + set_efx_lowpass_cutoff(); + set_efx_eq_center(); + set_efx_eq_bandwidth(); +} + +// [[nodiscard]] +bool EaxDistortionEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXDISTORTION_NONE: + break; + + case EAXDISTORTION_ALLPARAMETERS: + eax_call.set_value<EaxDistortionEffectException>(eax_); + break; + + case EAXDISTORTION_EDGE: + eax_call.set_value<EaxDistortionEffectException>(eax_.flEdge); + break; + + case EAXDISTORTION_GAIN: + eax_call.set_value<EaxDistortionEffectException>(eax_.lGain); + break; + + case EAXDISTORTION_LOWPASSCUTOFF: + eax_call.set_value<EaxDistortionEffectException>(eax_.flLowPassCutOff); + break; + + case EAXDISTORTION_EQCENTER: + eax_call.set_value<EaxDistortionEffectException>(eax_.flEQCenter); + break; + + case EAXDISTORTION_EQBANDWIDTH: + eax_call.set_value<EaxDistortionEffectException>(eax_.flEQBandwidth); + break; + + default: + throw EaxDistortionEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxDistortionEffect::validate_edge( + float flEdge) +{ + eax_validate_range<EaxDistortionEffectException>( + "Edge", + flEdge, + EAXDISTORTION_MINEDGE, + EAXDISTORTION_MAXEDGE); +} + +void EaxDistortionEffect::validate_gain( + long lGain) +{ + eax_validate_range<EaxDistortionEffectException>( + "Gain", + lGain, + EAXDISTORTION_MINGAIN, + EAXDISTORTION_MAXGAIN); +} + +void EaxDistortionEffect::validate_lowpass_cutoff( + float flLowPassCutOff) +{ + eax_validate_range<EaxDistortionEffectException>( + "Low-pass Cut-off", + flLowPassCutOff, + EAXDISTORTION_MINLOWPASSCUTOFF, + EAXDISTORTION_MAXLOWPASSCUTOFF); +} + +void EaxDistortionEffect::validate_eq_center( + float flEQCenter) +{ + eax_validate_range<EaxDistortionEffectException>( + "EQ Center", + flEQCenter, + EAXDISTORTION_MINEQCENTER, + EAXDISTORTION_MAXEQCENTER); +} + +void EaxDistortionEffect::validate_eq_bandwidth( + float flEQBandwidth) +{ + eax_validate_range<EaxDistortionEffectException>( + "EQ Bandwidth", + flEQBandwidth, + EAXDISTORTION_MINEQBANDWIDTH, + EAXDISTORTION_MAXEQBANDWIDTH); +} + +void EaxDistortionEffect::validate_all( + const EAXDISTORTIONPROPERTIES& eax_all) +{ + validate_edge(eax_all.flEdge); + validate_gain(eax_all.lGain); + validate_lowpass_cutoff(eax_all.flLowPassCutOff); + validate_eq_center(eax_all.flEQCenter); + validate_eq_bandwidth(eax_all.flEQBandwidth); +} + +void EaxDistortionEffect::defer_edge( + float flEdge) +{ + eax_d_.flEdge = flEdge; + eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge); +} + +void EaxDistortionEffect::defer_gain( + long lGain) +{ + eax_d_.lGain = lGain; + eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain); +} + +void EaxDistortionEffect::defer_low_pass_cutoff( + float flLowPassCutOff) +{ + eax_d_.flLowPassCutOff = flLowPassCutOff; + eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff); +} + +void EaxDistortionEffect::defer_eq_center( + float flEQCenter) +{ + eax_d_.flEQCenter = flEQCenter; + eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter); +} + +void EaxDistortionEffect::defer_eq_bandwidth( + float flEQBandwidth) +{ + eax_d_.flEQBandwidth = flEQBandwidth; + eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth); +} + +void EaxDistortionEffect::defer_all( + const EAXDISTORTIONPROPERTIES& eax_all) +{ + defer_edge(eax_all.flEdge); + defer_gain(eax_all.lGain); + defer_low_pass_cutoff(eax_all.flLowPassCutOff); + defer_eq_center(eax_all.flEQCenter); + defer_eq_bandwidth(eax_all.flEQBandwidth); +} + +void EaxDistortionEffect::defer_edge( + const EaxEaxCall& eax_call) +{ + const auto& edge = + eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEdge)>(); + + validate_edge(edge); + defer_edge(edge); +} + +void EaxDistortionEffect::defer_gain( + const EaxEaxCall& eax_call) +{ + const auto& gain = + eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::lGain)>(); + + validate_gain(gain); + defer_gain(gain); +} + +void EaxDistortionEffect::defer_low_pass_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& lowpass_cutoff = + eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flLowPassCutOff)>(); + + validate_lowpass_cutoff(lowpass_cutoff); + defer_low_pass_cutoff(lowpass_cutoff); +} + +void EaxDistortionEffect::defer_eq_center( + const EaxEaxCall& eax_call) +{ + const auto& eq_center = + eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQCenter)>(); + + validate_eq_center(eq_center); + defer_eq_center(eq_center); +} + +void EaxDistortionEffect::defer_eq_bandwidth( + const EaxEaxCall& eax_call) +{ + const auto& eq_bandwidth = + eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQBandwidth)>(); + + validate_eq_bandwidth(eq_bandwidth); + defer_eq_bandwidth(eq_bandwidth); +} + +void EaxDistortionEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxDistortionEffectException, const EAXDISTORTIONPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxDistortionEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flEdge) + { + set_efx_edge(); + } + + if (eax_dirty_flags_.lGain) + { + set_efx_gain(); + } + + if (eax_dirty_flags_.flLowPassCutOff) + { + set_efx_lowpass_cutoff(); + } + + if (eax_dirty_flags_.flEQCenter) + { + set_efx_eq_center(); + } + + if (eax_dirty_flags_.flEQBandwidth) + { + set_efx_eq_bandwidth(); + } + + eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxDistortionEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXDISTORTION_NONE: + break; + + case EAXDISTORTION_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXDISTORTION_EDGE: + defer_edge(eax_call); + break; + + case EAXDISTORTION_GAIN: + defer_gain(eax_call); + break; + + case EAXDISTORTION_LOWPASSCUTOFF: + defer_low_pass_cutoff(eax_call); + break; + + case EAXDISTORTION_EQCENTER: + defer_eq_center(eax_call); + break; + + case EAXDISTORTION_EQBANDWIDTH: + defer_eq_bandwidth(eax_call); + break; + + default: + throw EaxDistortionEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_distortion_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxDistortionEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/echo.cpp b/al/effects/echo.cpp index 65f691c6..5ceb161d 100644 --- a/al/effects/echo.cpp +++ b/al/effects/echo.cpp @@ -7,6 +7,13 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -109,3 +116,530 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Echo); const EffectProps EchoEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxEchoEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxEchoEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxEchoEffectDirtyFlagsValue flDelay : 1; + EaxEchoEffectDirtyFlagsValue flLRDelay : 1; + EaxEchoEffectDirtyFlagsValue flDamping : 1; + EaxEchoEffectDirtyFlagsValue flFeedback : 1; + EaxEchoEffectDirtyFlagsValue flSpread : 1; +}; // EaxEchoEffectDirtyFlags + + +class EaxEchoEffect final : + public EaxEffect +{ +public: + EaxEchoEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXECHOPROPERTIES eax_{}; + EAXECHOPROPERTIES eax_d_{}; + EaxEchoEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_delay(); + + void set_efx_lr_delay(); + + void set_efx_damping(); + + void set_efx_feedback(); + + void set_efx_spread(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_delay( + float flDelay); + + void validate_lr_delay( + float flLRDelay); + + void validate_damping( + float flDamping); + + void validate_feedback( + float flFeedback); + + void validate_spread( + float flSpread); + + void validate_all( + const EAXECHOPROPERTIES& all); + + + void defer_delay( + float flDelay); + + void defer_lr_delay( + float flLRDelay); + + void defer_damping( + float flDamping); + + void defer_feedback( + float flFeedback); + + void defer_spread( + float flSpread); + + void defer_all( + const EAXECHOPROPERTIES& all); + + + void defer_delay( + const EaxEaxCall& eax_call); + + void defer_lr_delay( + const EaxEaxCall& eax_call); + + void defer_damping( + const EaxEaxCall& eax_call); + + void defer_feedback( + const EaxEaxCall& eax_call); + + void defer_spread( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + bool apply_deferred(); + + bool set( + const EaxEaxCall& eax_call); +}; // EaxEchoEffect + + +class EaxEchoEffectException : + public EaxException +{ +public: + explicit EaxEchoEffectException( + const char* message) + : + EaxException{"EAX_ECHO_EFFECT", message} + { + } +}; // EaxEchoEffectException + + +EaxEchoEffect::EaxEchoEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxEchoEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxEchoEffect::set_eax_defaults() +{ + eax_.flDelay = EAXECHO_DEFAULTDELAY; + eax_.flLRDelay = EAXECHO_DEFAULTLRDELAY; + eax_.flDamping = EAXECHO_DEFAULTDAMPING; + eax_.flFeedback = EAXECHO_DEFAULTFEEDBACK; + eax_.flSpread = EAXECHO_DEFAULTSPREAD; + + eax_d_ = eax_; +} + +void EaxEchoEffect::set_efx_delay() +{ + const auto delay = clamp( + eax_.flDelay, + AL_ECHO_MIN_DELAY, + AL_ECHO_MAX_DELAY); + + al_effect_props_.Echo.Delay = delay; +} + +void EaxEchoEffect::set_efx_lr_delay() +{ + const auto lr_delay = clamp( + eax_.flLRDelay, + AL_ECHO_MIN_LRDELAY, + AL_ECHO_MAX_LRDELAY); + + al_effect_props_.Echo.LRDelay = lr_delay; +} + +void EaxEchoEffect::set_efx_damping() +{ + const auto damping = clamp( + eax_.flDamping, + AL_ECHO_MIN_DAMPING, + AL_ECHO_MAX_DAMPING); + + al_effect_props_.Echo.Damping = damping; +} + +void EaxEchoEffect::set_efx_feedback() +{ + const auto feedback = clamp( + eax_.flFeedback, + AL_ECHO_MIN_FEEDBACK, + AL_ECHO_MAX_FEEDBACK); + + al_effect_props_.Echo.Feedback = feedback; +} + +void EaxEchoEffect::set_efx_spread() +{ + const auto spread = clamp( + eax_.flSpread, + AL_ECHO_MIN_SPREAD, + AL_ECHO_MAX_SPREAD); + + al_effect_props_.Echo.Spread = spread; +} + +void EaxEchoEffect::set_efx_defaults() +{ + set_efx_delay(); + set_efx_lr_delay(); + set_efx_damping(); + set_efx_feedback(); + set_efx_spread(); +} + +// [[nodiscard]] +bool EaxEchoEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXECHO_NONE: + break; + + case EAXECHO_ALLPARAMETERS: + eax_call.set_value<EaxEchoEffectException>(eax_); + break; + + case EAXECHO_DELAY: + eax_call.set_value<EaxEchoEffectException>(eax_.flDelay); + break; + + case EAXECHO_LRDELAY: + eax_call.set_value<EaxEchoEffectException>(eax_.flLRDelay); + break; + + case EAXECHO_DAMPING: + eax_call.set_value<EaxEchoEffectException>(eax_.flDamping); + break; + + case EAXECHO_FEEDBACK: + eax_call.set_value<EaxEchoEffectException>(eax_.flFeedback); + break; + + case EAXECHO_SPREAD: + eax_call.set_value<EaxEchoEffectException>(eax_.flSpread); + break; + + default: + throw EaxEchoEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxEchoEffect::validate_delay( + float flDelay) +{ + eax_validate_range<EaxEchoEffectException>( + "Delay", + flDelay, + EAXECHO_MINDELAY, + EAXECHO_MAXDELAY); +} + +void EaxEchoEffect::validate_lr_delay( + float flLRDelay) +{ + eax_validate_range<EaxEchoEffectException>( + "LR Delay", + flLRDelay, + EAXECHO_MINLRDELAY, + EAXECHO_MAXLRDELAY); +} + +void EaxEchoEffect::validate_damping( + float flDamping) +{ + eax_validate_range<EaxEchoEffectException>( + "Damping", + flDamping, + EAXECHO_MINDAMPING, + EAXECHO_MAXDAMPING); +} + +void EaxEchoEffect::validate_feedback( + float flFeedback) +{ + eax_validate_range<EaxEchoEffectException>( + "Feedback", + flFeedback, + EAXECHO_MINFEEDBACK, + EAXECHO_MAXFEEDBACK); +} + +void EaxEchoEffect::validate_spread( + float flSpread) +{ + eax_validate_range<EaxEchoEffectException>( + "Spread", + flSpread, + EAXECHO_MINSPREAD, + EAXECHO_MAXSPREAD); +} + +void EaxEchoEffect::validate_all( + const EAXECHOPROPERTIES& all) +{ + validate_delay(all.flDelay); + validate_lr_delay(all.flLRDelay); + validate_damping(all.flDamping); + validate_feedback(all.flFeedback); + validate_spread(all.flSpread); +} + +void EaxEchoEffect::defer_delay( + float flDelay) +{ + eax_d_.flDelay = flDelay; + eax_dirty_flags_.flDelay = (eax_.flDelay != eax_d_.flDelay); +} + +void EaxEchoEffect::defer_lr_delay( + float flLRDelay) +{ + eax_d_.flLRDelay = flLRDelay; + eax_dirty_flags_.flLRDelay = (eax_.flLRDelay != eax_d_.flLRDelay); +} + +void EaxEchoEffect::defer_damping( + float flDamping) +{ + eax_d_.flDamping = flDamping; + eax_dirty_flags_.flDamping = (eax_.flDamping != eax_d_.flDamping); +} + +void EaxEchoEffect::defer_feedback( + float flFeedback) +{ + eax_d_.flFeedback = flFeedback; + eax_dirty_flags_.flFeedback = (eax_.flFeedback != eax_d_.flFeedback); +} + +void EaxEchoEffect::defer_spread( + float flSpread) +{ + eax_d_.flSpread = flSpread; + eax_dirty_flags_.flSpread = (eax_.flSpread != eax_d_.flSpread); +} + +void EaxEchoEffect::defer_all( + const EAXECHOPROPERTIES& all) +{ + defer_delay(all.flDelay); + defer_lr_delay(all.flLRDelay); + defer_damping(all.flDamping); + defer_feedback(all.flFeedback); + defer_spread(all.flSpread); +} + +void EaxEchoEffect::defer_delay( + const EaxEaxCall& eax_call) +{ + const auto& delay = + eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDelay)>(); + + validate_delay(delay); + defer_delay(delay); +} + +void EaxEchoEffect::defer_lr_delay( + const EaxEaxCall& eax_call) +{ + const auto& lr_delay = + eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flLRDelay)>(); + + validate_lr_delay(lr_delay); + defer_lr_delay(lr_delay); +} + +void EaxEchoEffect::defer_damping( + const EaxEaxCall& eax_call) +{ + const auto& damping = + eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flDamping)>(); + + validate_damping(damping); + defer_damping(damping); +} + +void EaxEchoEffect::defer_feedback( + const EaxEaxCall& eax_call) +{ + const auto& feedback = + eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flFeedback)>(); + + validate_feedback(feedback); + defer_feedback(feedback); +} + +void EaxEchoEffect::defer_spread( + const EaxEaxCall& eax_call) +{ + const auto& spread = + eax_call.get_value<EaxEchoEffectException, const decltype(EAXECHOPROPERTIES::flSpread)>(); + + validate_spread(spread); + defer_spread(spread); +} + +void EaxEchoEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxEchoEffectException, const EAXECHOPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxEchoEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxEchoEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flDelay) + { + set_efx_delay(); + } + + if (eax_dirty_flags_.flLRDelay) + { + set_efx_lr_delay(); + } + + if (eax_dirty_flags_.flDamping) + { + set_efx_damping(); + } + + if (eax_dirty_flags_.flFeedback) + { + set_efx_feedback(); + } + + if (eax_dirty_flags_.flSpread) + { + set_efx_spread(); + } + + eax_dirty_flags_ = EaxEchoEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxEchoEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXECHO_NONE: + break; + + case EAXECHO_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXECHO_DELAY: + defer_delay(eax_call); + break; + + case EAXECHO_LRDELAY: + defer_lr_delay(eax_call); + break; + + case EAXECHO_DAMPING: + defer_damping(eax_call); + break; + + case EAXECHO_FEEDBACK: + defer_feedback(eax_call); + break; + + case EAXECHO_SPREAD: + defer_spread(eax_call); + break; + + default: + throw EaxEchoEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_echo_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxEchoEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/effects.cpp b/al/effects/effects.cpp new file mode 100644 index 00000000..55abfdc5 --- /dev/null +++ b/al/effects/effects.cpp @@ -0,0 +1,106 @@ +#if ALSOFT_EAX + + +#include <cassert> + +#include "AL/efx.h" + +#include "effects.h" + + +EaxEffectUPtr eax_create_eax_null_effect(); + +EaxEffectUPtr eax_create_eax_chorus_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_distortion_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_echo_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_flanger_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_frequency_shifter_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_vocal_morpher_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_pitch_shifter_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_ring_modulator_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_auto_wah_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_compressor_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_equalizer_effect( + EffectProps& al_effect_props); + +EaxEffectUPtr eax_create_eax_reverb_effect( + EffectProps& al_effect_props); + + +EaxEffectUPtr eax_create_eax_effect( + ALenum al_effect_type, + EffectProps& al_effect_props) +{ +#define EAX_PREFIX "[EAX_MAKE_EAX_EFFECT] " + + switch (al_effect_type) + { + case AL_EFFECT_NULL: + return eax_create_eax_null_effect(); + + case AL_EFFECT_CHORUS: + return eax_create_eax_chorus_effect(al_effect_props); + + case AL_EFFECT_DISTORTION: + return eax_create_eax_distortion_effect(al_effect_props); + + case AL_EFFECT_ECHO: + return eax_create_eax_echo_effect(al_effect_props); + + case AL_EFFECT_FLANGER: + return eax_create_eax_flanger_effect(al_effect_props); + + case AL_EFFECT_FREQUENCY_SHIFTER: + return eax_create_eax_frequency_shifter_effect(al_effect_props); + + case AL_EFFECT_VOCAL_MORPHER: + return eax_create_eax_vocal_morpher_effect(al_effect_props); + + case AL_EFFECT_PITCH_SHIFTER: + return eax_create_eax_pitch_shifter_effect(al_effect_props); + + case AL_EFFECT_RING_MODULATOR: + return eax_create_eax_ring_modulator_effect(al_effect_props); + + case AL_EFFECT_AUTOWAH: + return eax_create_eax_auto_wah_effect(al_effect_props); + + case AL_EFFECT_COMPRESSOR: + return eax_create_eax_compressor_effect(al_effect_props); + + case AL_EFFECT_EQUALIZER: + return eax_create_eax_equalizer_effect(al_effect_props); + + case AL_EFFECT_EAXREVERB: + return eax_create_eax_reverb_effect(al_effect_props); + + default: + assert(false && "Unsupported AL effect type."); + return nullptr; + } + +#undef EAX_PREFIX +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/effects.h b/al/effects/effects.h index 30b4bd75..6813beaa 100644 --- a/al/effects/effects.h +++ b/al/effects/effects.h @@ -5,6 +5,10 @@ #include "core/except.h" +#if ALSOFT_EAX +#include "al/eax_effect.h" +#endif // ALSOFT_EAX + union EffectProps; @@ -80,4 +84,11 @@ extern const EffectVtable VmorpherEffectVtable; extern const EffectVtable DedicatedEffectVtable; extern const EffectVtable ConvolutionEffectVtable; + +#if ALSOFT_EAX +EaxEffectUPtr eax_create_eax_effect( + ALenum al_effect_type, + EffectProps& al_effect_props); +#endif // ALSOFT_EAX + #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/al/effects/equalizer.cpp b/al/effects/equalizer.cpp index 3c039688..c052db3e 100644 --- a/al/effects/equalizer.cpp +++ b/al/effects/equalizer.cpp @@ -7,6 +7,13 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -167,3 +174,862 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Equalizer); const EffectProps EqualizerEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxEqualizerEffectDirtyFlagsValue = std::uint_least16_t; + +struct EaxEqualizerEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxEqualizerEffectDirtyFlagsValue lLowGain : 1; + EaxEqualizerEffectDirtyFlagsValue flLowCutOff : 1; + EaxEqualizerEffectDirtyFlagsValue lMid1Gain : 1; + EaxEqualizerEffectDirtyFlagsValue flMid1Center : 1; + EaxEqualizerEffectDirtyFlagsValue flMid1Width : 1; + EaxEqualizerEffectDirtyFlagsValue lMid2Gain : 1; + EaxEqualizerEffectDirtyFlagsValue flMid2Center : 1; + EaxEqualizerEffectDirtyFlagsValue flMid2Width : 1; + EaxEqualizerEffectDirtyFlagsValue lHighGain : 1; + EaxEqualizerEffectDirtyFlagsValue flHighCutOff : 1; +}; // EaxEqualizerEffectDirtyFlags + + +class EaxEqualizerEffect final : + public EaxEffect +{ +public: + EaxEqualizerEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXEQUALIZERPROPERTIES eax_{}; + EAXEQUALIZERPROPERTIES eax_d_{}; + EaxEqualizerEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_low_gain(); + + void set_efx_low_cutoff(); + + void set_efx_mid1_gain(); + + void set_efx_mid1_center(); + + void set_efx_mid1_width(); + + void set_efx_mid2_gain(); + + void set_efx_mid2_center(); + + void set_efx_mid2_width(); + + void set_efx_high_gain(); + + void set_efx_high_cutoff(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_low_gain( + long lLowGain); + + void validate_low_cutoff( + float flLowCutOff); + + void validate_mid1_gain( + long lMid1Gain); + + void validate_mid1_center( + float flMid1Center); + + void validate_mid1_width( + float flMid1Width); + + void validate_mid2_gain( + long lMid2Gain); + + void validate_mid2_center( + float flMid2Center); + + void validate_mid2_width( + float flMid2Width); + + void validate_high_gain( + long lHighGain); + + void validate_high_cutoff( + float flHighCutOff); + + void validate_all( + const EAXEQUALIZERPROPERTIES& all); + + + void defer_low_gain( + long lLowGain); + + void defer_low_cutoff( + float flLowCutOff); + + void defer_mid1_gain( + long lMid1Gain); + + void defer_mid1_center( + float flMid1Center); + + void defer_mid1_width( + float flMid1Width); + + void defer_mid2_gain( + long lMid2Gain); + + void defer_mid2_center( + float flMid2Center); + + void defer_mid2_width( + float flMid2Width); + + void defer_high_gain( + long lHighGain); + + void defer_high_cutoff( + float flHighCutOff); + + void defer_all( + const EAXEQUALIZERPROPERTIES& all); + + + void defer_low_gain( + const EaxEaxCall& eax_call); + + void defer_low_cutoff( + const EaxEaxCall& eax_call); + + void defer_mid1_gain( + const EaxEaxCall& eax_call); + + void defer_mid1_center( + const EaxEaxCall& eax_call); + + void defer_mid1_width( + const EaxEaxCall& eax_call); + + void defer_mid2_gain( + const EaxEaxCall& eax_call); + + void defer_mid2_center( + const EaxEaxCall& eax_call); + + void defer_mid2_width( + const EaxEaxCall& eax_call); + + void defer_high_gain( + const EaxEaxCall& eax_call); + + void defer_high_cutoff( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxEqualizerEffect + + +class EaxEqualizerEffectException : + public EaxException +{ +public: + explicit EaxEqualizerEffectException( + const char* message) + : + EaxException{"EAX_EQUALIZER_EFFECT", message} + { + } +}; // EaxEqualizerEffectException + + +EaxEqualizerEffect::EaxEqualizerEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxEqualizerEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxEqualizerEffect::set_eax_defaults() +{ + eax_.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; + eax_.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; + eax_.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; + eax_.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; + eax_.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; + eax_.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; + eax_.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; + eax_.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; + eax_.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; + eax_.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + + eax_d_ = eax_; +} + +void EaxEqualizerEffect::set_efx_low_gain() +{ + const auto low_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lLowGain)), + AL_EQUALIZER_MIN_LOW_GAIN, + AL_EQUALIZER_MAX_LOW_GAIN); + + al_effect_props_.Equalizer.LowGain = low_gain; +} + +void EaxEqualizerEffect::set_efx_low_cutoff() +{ + const auto low_cutoff = clamp( + eax_.flLowCutOff, + AL_EQUALIZER_MIN_LOW_CUTOFF, + AL_EQUALIZER_MAX_LOW_CUTOFF); + + al_effect_props_.Equalizer.LowCutoff = low_cutoff; +} + +void EaxEqualizerEffect::set_efx_mid1_gain() +{ + const auto mid1_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lMid1Gain)), + AL_EQUALIZER_MIN_MID1_GAIN, + AL_EQUALIZER_MAX_MID1_GAIN); + + al_effect_props_.Equalizer.Mid1Gain = mid1_gain; +} + +void EaxEqualizerEffect::set_efx_mid1_center() +{ + const auto mid1_center = clamp( + eax_.flMid1Center, + AL_EQUALIZER_MIN_MID1_CENTER, + AL_EQUALIZER_MAX_MID1_CENTER); + + al_effect_props_.Equalizer.Mid1Center = mid1_center; +} + +void EaxEqualizerEffect::set_efx_mid1_width() +{ + const auto mid1_width = clamp( + eax_.flMid1Width, + AL_EQUALIZER_MIN_MID1_WIDTH, + AL_EQUALIZER_MAX_MID1_WIDTH); + + al_effect_props_.Equalizer.Mid1Width = mid1_width; +} + +void EaxEqualizerEffect::set_efx_mid2_gain() +{ + const auto mid2_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lMid2Gain)), + AL_EQUALIZER_MIN_MID2_GAIN, + AL_EQUALIZER_MAX_MID2_GAIN); + + al_effect_props_.Equalizer.Mid2Gain = mid2_gain; +} + +void EaxEqualizerEffect::set_efx_mid2_center() +{ + const auto mid2_center = clamp( + eax_.flMid2Center, + AL_EQUALIZER_MIN_MID2_CENTER, + AL_EQUALIZER_MAX_MID2_CENTER); + + al_effect_props_.Equalizer.Mid2Center = mid2_center; +} + +void EaxEqualizerEffect::set_efx_mid2_width() +{ + const auto mid2_width = clamp( + eax_.flMid2Width, + AL_EQUALIZER_MIN_MID2_WIDTH, + AL_EQUALIZER_MAX_MID2_WIDTH); + + al_effect_props_.Equalizer.Mid2Width = mid2_width; +} + +void EaxEqualizerEffect::set_efx_high_gain() +{ + const auto high_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lHighGain)), + AL_EQUALIZER_MIN_HIGH_GAIN, + AL_EQUALIZER_MAX_HIGH_GAIN); + + al_effect_props_.Equalizer.HighGain = high_gain; +} + +void EaxEqualizerEffect::set_efx_high_cutoff() +{ + const auto high_cutoff = clamp( + eax_.flHighCutOff, + AL_EQUALIZER_MIN_HIGH_CUTOFF, + AL_EQUALIZER_MAX_HIGH_CUTOFF); + + al_effect_props_.Equalizer.HighCutoff = high_cutoff; +} + +void EaxEqualizerEffect::set_efx_defaults() +{ + set_efx_low_gain(); + set_efx_low_cutoff(); + set_efx_mid1_gain(); + set_efx_mid1_center(); + set_efx_mid1_width(); + set_efx_mid2_gain(); + set_efx_mid2_center(); + set_efx_mid2_width(); + set_efx_high_gain(); + set_efx_high_cutoff(); +} + +// [[nodiscard]] +bool EaxEqualizerEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXEQUALIZER_NONE: + break; + + case EAXEQUALIZER_ALLPARAMETERS: + eax_call.set_value<EaxEqualizerEffectException>(eax_); + break; + + case EAXEQUALIZER_LOWGAIN: + eax_call.set_value<EaxEqualizerEffectException>(eax_.lLowGain); + break; + + case EAXEQUALIZER_LOWCUTOFF: + eax_call.set_value<EaxEqualizerEffectException>(eax_.flLowCutOff); + break; + + case EAXEQUALIZER_MID1GAIN: + eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid1Gain); + break; + + case EAXEQUALIZER_MID1CENTER: + eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Center); + break; + + case EAXEQUALIZER_MID1WIDTH: + eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid1Width); + break; + + case EAXEQUALIZER_MID2GAIN: + eax_call.set_value<EaxEqualizerEffectException>(eax_.lMid2Gain); + break; + + case EAXEQUALIZER_MID2CENTER: + eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Center); + break; + + case EAXEQUALIZER_MID2WIDTH: + eax_call.set_value<EaxEqualizerEffectException>(eax_.flMid2Width); + break; + + case EAXEQUALIZER_HIGHGAIN: + eax_call.set_value<EaxEqualizerEffectException>(eax_.lHighGain); + break; + + case EAXEQUALIZER_HIGHCUTOFF: + eax_call.set_value<EaxEqualizerEffectException>(eax_.flHighCutOff); + break; + + default: + throw EaxEqualizerEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxEqualizerEffect::validate_low_gain( + long lLowGain) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Low Gain", + lLowGain, + EAXEQUALIZER_MINLOWGAIN, + EAXEQUALIZER_MAXLOWGAIN); +} + +void EaxEqualizerEffect::validate_low_cutoff( + float flLowCutOff) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Low Cutoff", + flLowCutOff, + EAXEQUALIZER_MINLOWCUTOFF, + EAXEQUALIZER_MAXLOWCUTOFF); +} + +void EaxEqualizerEffect::validate_mid1_gain( + long lMid1Gain) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Mid1 Gain", + lMid1Gain, + EAXEQUALIZER_MINMID1GAIN, + EAXEQUALIZER_MAXMID1GAIN); +} + +void EaxEqualizerEffect::validate_mid1_center( + float flMid1Center) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Mid1 Center", + flMid1Center, + EAXEQUALIZER_MINMID1CENTER, + EAXEQUALIZER_MAXMID1CENTER); +} + +void EaxEqualizerEffect::validate_mid1_width( + float flMid1Width) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Mid1 Width", + flMid1Width, + EAXEQUALIZER_MINMID1WIDTH, + EAXEQUALIZER_MAXMID1WIDTH); +} + +void EaxEqualizerEffect::validate_mid2_gain( + long lMid2Gain) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Mid2 Gain", + lMid2Gain, + EAXEQUALIZER_MINMID2GAIN, + EAXEQUALIZER_MAXMID2GAIN); +} + +void EaxEqualizerEffect::validate_mid2_center( + float flMid2Center) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Mid2 Center", + flMid2Center, + EAXEQUALIZER_MINMID2CENTER, + EAXEQUALIZER_MAXMID2CENTER); +} + +void EaxEqualizerEffect::validate_mid2_width( + float flMid2Width) +{ + eax_validate_range<EaxEqualizerEffectException>( + "Mid2 Width", + flMid2Width, + EAXEQUALIZER_MINMID2WIDTH, + EAXEQUALIZER_MAXMID2WIDTH); +} + +void EaxEqualizerEffect::validate_high_gain( + long lHighGain) +{ + eax_validate_range<EaxEqualizerEffectException>( + "High Gain", + lHighGain, + EAXEQUALIZER_MINHIGHGAIN, + EAXEQUALIZER_MAXHIGHGAIN); +} + +void EaxEqualizerEffect::validate_high_cutoff( + float flHighCutOff) +{ + eax_validate_range<EaxEqualizerEffectException>( + "High Cutoff", + flHighCutOff, + EAXEQUALIZER_MINHIGHCUTOFF, + EAXEQUALIZER_MAXHIGHCUTOFF); +} + +void EaxEqualizerEffect::validate_all( + const EAXEQUALIZERPROPERTIES& all) +{ + validate_low_gain(all.lLowGain); + validate_low_cutoff(all.flLowCutOff); + validate_mid1_gain(all.lMid1Gain); + validate_mid1_center(all.flMid1Center); + validate_mid1_width(all.flMid1Width); + validate_mid2_gain(all.lMid2Gain); + validate_mid2_center(all.flMid2Center); + validate_mid2_width(all.flMid2Width); + validate_high_gain(all.lHighGain); + validate_high_cutoff(all.flHighCutOff); +} + +void EaxEqualizerEffect::defer_low_gain( + long lLowGain) +{ + eax_d_.lLowGain = lLowGain; + eax_dirty_flags_.lLowGain = (eax_.lLowGain != eax_d_.lLowGain); +} + +void EaxEqualizerEffect::defer_low_cutoff( + float flLowCutOff) +{ + eax_d_.flLowCutOff = flLowCutOff; + eax_dirty_flags_.flLowCutOff = (eax_.flLowCutOff != eax_d_.flLowCutOff); +} + +void EaxEqualizerEffect::defer_mid1_gain( + long lMid1Gain) +{ + eax_d_.lMid1Gain = lMid1Gain; + eax_dirty_flags_.lMid1Gain = (eax_.lMid1Gain != eax_d_.lMid1Gain); +} + +void EaxEqualizerEffect::defer_mid1_center( + float flMid1Center) +{ + eax_d_.flMid1Center = flMid1Center; + eax_dirty_flags_.flMid1Center = (eax_.flMid1Center != eax_d_.flMid1Center); +} + +void EaxEqualizerEffect::defer_mid1_width( + float flMid1Width) +{ + eax_d_.flMid1Width = flMid1Width; + eax_dirty_flags_.flMid1Width = (eax_.flMid1Width != eax_d_.flMid1Width); +} + +void EaxEqualizerEffect::defer_mid2_gain( + long lMid2Gain) +{ + eax_d_.lMid2Gain = lMid2Gain; + eax_dirty_flags_.lMid2Gain = (eax_.lMid2Gain != eax_d_.lMid2Gain); +} + +void EaxEqualizerEffect::defer_mid2_center( + float flMid2Center) +{ + eax_d_.flMid2Center = flMid2Center; + eax_dirty_flags_.flMid2Center = (eax_.flMid2Center != eax_d_.flMid2Center); +} + +void EaxEqualizerEffect::defer_mid2_width( + float flMid2Width) +{ + eax_d_.flMid2Width = flMid2Width; + eax_dirty_flags_.flMid2Width = (eax_.flMid2Width != eax_d_.flMid2Width); +} + +void EaxEqualizerEffect::defer_high_gain( + long lHighGain) +{ + eax_d_.lHighGain = lHighGain; + eax_dirty_flags_.lHighGain = (eax_.lHighGain != eax_d_.lHighGain); +} + +void EaxEqualizerEffect::defer_high_cutoff( + float flHighCutOff) +{ + eax_d_.flHighCutOff = flHighCutOff; + eax_dirty_flags_.flHighCutOff = (eax_.flHighCutOff != eax_d_.flHighCutOff); +} + +void EaxEqualizerEffect::defer_all( + const EAXEQUALIZERPROPERTIES& all) +{ + defer_low_gain(all.lLowGain); + defer_low_cutoff(all.flLowCutOff); + defer_mid1_gain(all.lMid1Gain); + defer_mid1_center(all.flMid1Center); + defer_mid1_width(all.flMid1Width); + defer_mid2_gain(all.lMid2Gain); + defer_mid2_center(all.flMid2Center); + defer_mid2_width(all.flMid2Width); + defer_high_gain(all.lHighGain); + defer_high_cutoff(all.flHighCutOff); +} + +void EaxEqualizerEffect::defer_low_gain( + const EaxEaxCall& eax_call) +{ + const auto& low_gain = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lLowGain)>(); + + validate_low_gain(low_gain); + defer_low_gain(low_gain); +} + +void EaxEqualizerEffect::defer_low_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& low_cutoff = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flLowCutOff)>(); + + validate_low_cutoff(low_cutoff); + defer_low_cutoff(low_cutoff); +} + +void EaxEqualizerEffect::defer_mid1_gain( + const EaxEaxCall& eax_call) +{ + const auto& mid1_gain = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid1Gain)>(); + + validate_mid1_gain(mid1_gain); + defer_mid1_gain(mid1_gain); +} + +void EaxEqualizerEffect::defer_mid1_center( + const EaxEaxCall& eax_call) +{ + const auto& mid1_center = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Center)>(); + + validate_mid1_center(mid1_center); + defer_mid1_center(mid1_center); +} + +void EaxEqualizerEffect::defer_mid1_width( + const EaxEaxCall& eax_call) +{ + const auto& mid1_width = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid1Width)>(); + + validate_mid1_width(mid1_width); + defer_mid1_width(mid1_width); +} + +void EaxEqualizerEffect::defer_mid2_gain( + const EaxEaxCall& eax_call) +{ + const auto& mid2_gain = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lMid2Gain)>(); + + validate_mid2_gain(mid2_gain); + defer_mid2_gain(mid2_gain); +} + +void EaxEqualizerEffect::defer_mid2_center( + const EaxEaxCall& eax_call) +{ + const auto& mid2_center = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Center)>(); + + validate_mid2_center(mid2_center); + defer_mid2_center(mid2_center); +} + +void EaxEqualizerEffect::defer_mid2_width( + const EaxEaxCall& eax_call) +{ + const auto& mid2_width = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flMid2Width)>(); + + validate_mid2_width(mid2_width); + defer_mid2_width(mid2_width); +} + +void EaxEqualizerEffect::defer_high_gain( + const EaxEaxCall& eax_call) +{ + const auto& high_gain = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::lHighGain)>(); + + validate_high_gain(high_gain); + defer_high_gain(high_gain); +} + +void EaxEqualizerEffect::defer_high_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& high_cutoff = + eax_call.get_value<EaxEqualizerEffectException, const decltype(EAXEQUALIZERPROPERTIES::flHighCutOff)>(); + + validate_high_cutoff(high_cutoff); + defer_high_cutoff(high_cutoff); +} + +void EaxEqualizerEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxEqualizerEffectException, const EAXEQUALIZERPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxEqualizerEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxEqualizerEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.lLowGain) + { + set_efx_low_gain(); + } + + if (eax_dirty_flags_.flLowCutOff) + { + set_efx_low_cutoff(); + } + + if (eax_dirty_flags_.lMid1Gain) + { + set_efx_mid1_gain(); + } + + if (eax_dirty_flags_.flMid1Center) + { + set_efx_mid1_center(); + } + + if (eax_dirty_flags_.flMid1Width) + { + set_efx_mid1_width(); + } + + if (eax_dirty_flags_.lMid2Gain) + { + set_efx_mid2_gain(); + } + + if (eax_dirty_flags_.flMid2Center) + { + set_efx_mid2_center(); + } + + if (eax_dirty_flags_.flMid2Width) + { + set_efx_mid2_width(); + } + + if (eax_dirty_flags_.lHighGain) + { + set_efx_high_gain(); + } + + if (eax_dirty_flags_.flHighCutOff) + { + set_efx_high_cutoff(); + } + + eax_dirty_flags_ = EaxEqualizerEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxEqualizerEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXEQUALIZER_NONE: + break; + + case EAXEQUALIZER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXEQUALIZER_LOWGAIN: + defer_low_gain(eax_call); + break; + + case EAXEQUALIZER_LOWCUTOFF: + defer_low_cutoff(eax_call); + break; + + case EAXEQUALIZER_MID1GAIN: + defer_mid1_gain(eax_call); + break; + + case EAXEQUALIZER_MID1CENTER: + defer_mid1_center(eax_call); + break; + + case EAXEQUALIZER_MID1WIDTH: + defer_mid1_width(eax_call); + break; + + case EAXEQUALIZER_MID2GAIN: + defer_mid2_gain(eax_call); + break; + + case EAXEQUALIZER_MID2CENTER: + defer_mid2_center(eax_call); + break; + + case EAXEQUALIZER_MID2WIDTH: + defer_mid2_width(eax_call); + break; + + case EAXEQUALIZER_HIGHGAIN: + defer_high_gain(eax_call); + break; + + case EAXEQUALIZER_HIGHCUTOFF: + defer_high_cutoff(eax_call); + break; + + default: + throw EaxEqualizerEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_equalizer_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxEqualizerEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/fshifter.cpp b/al/effects/fshifter.cpp index 4aa860a2..aa4ddadb 100644 --- a/al/effects/fshifter.cpp +++ b/al/effects/fshifter.cpp @@ -10,6 +10,15 @@ #include "aloptional.h" #include "effects.h" +#if ALSOFT_EAX +#include <cassert> + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -128,3 +137,408 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Fshifter); const EffectProps FshifterEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxFrequencyShifterEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxFrequencyShifterEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxFrequencyShifterEffectDirtyFlagsValue flFrequency : 1; + EaxFrequencyShifterEffectDirtyFlagsValue ulLeftDirection : 1; + EaxFrequencyShifterEffectDirtyFlagsValue ulRightDirection : 1; +}; // EaxFrequencyShifterEffectDirtyFlags + + +class EaxFrequencyShifterEffect final : + public EaxEffect +{ +public: + EaxFrequencyShifterEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXFREQUENCYSHIFTERPROPERTIES eax_{}; + EAXFREQUENCYSHIFTERPROPERTIES eax_d_{}; + EaxFrequencyShifterEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_frequency(); + + void set_efx_left_direction(); + + void set_efx_right_direction(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_frequency( + float flFrequency); + + void validate_left_direction( + unsigned long ulLeftDirection); + + void validate_right_direction( + unsigned long ulRightDirection); + + void validate_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all); + + + void defer_frequency( + float flFrequency); + + void defer_left_direction( + unsigned long ulLeftDirection); + + void defer_right_direction( + unsigned long ulRightDirection); + + void defer_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all); + + + void defer_frequency( + const EaxEaxCall& eax_call); + + void defer_left_direction( + const EaxEaxCall& eax_call); + + void defer_right_direction( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxFrequencyShifterEffect + + +class EaxFrequencyShifterEffectException : + public EaxException +{ +public: + explicit EaxFrequencyShifterEffectException( + const char* message) + : + EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} + { + } +}; // EaxFrequencyShifterEffectException + + +EaxFrequencyShifterEffect::EaxFrequencyShifterEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxFrequencyShifterEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxFrequencyShifterEffect::set_eax_defaults() +{ + eax_.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + eax_.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + eax_.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + + eax_d_ = eax_; +} + +void EaxFrequencyShifterEffect::set_efx_frequency() +{ + const auto frequency = clamp( + eax_.flFrequency, + AL_FREQUENCY_SHIFTER_MIN_FREQUENCY, + AL_FREQUENCY_SHIFTER_MAX_FREQUENCY); + + al_effect_props_.Fshifter.Frequency = frequency; +} + +void EaxFrequencyShifterEffect::set_efx_left_direction() +{ + const auto left_direction = clamp( + static_cast<ALint>(eax_.ulLeftDirection), + AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION, + AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION); + + const auto efx_left_direction = DirectionFromEmum(left_direction); + assert(efx_left_direction.has_value()); + al_effect_props_.Fshifter.LeftDirection = *efx_left_direction; +} + +void EaxFrequencyShifterEffect::set_efx_right_direction() +{ + const auto right_direction = clamp( + static_cast<ALint>(eax_.ulRightDirection), + AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION, + AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION); + + const auto efx_right_direction = DirectionFromEmum(right_direction); + assert(efx_right_direction.has_value()); + al_effect_props_.Fshifter.RightDirection = *efx_right_direction; +} + +void EaxFrequencyShifterEffect::set_efx_defaults() +{ + set_efx_frequency(); + set_efx_left_direction(); + set_efx_right_direction(); +} + +// [[nodiscard]] +bool EaxFrequencyShifterEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: + break; + + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: + eax_call.set_value<EaxFrequencyShifterEffectException>(eax_); + break; + + case EAXFREQUENCYSHIFTER_FREQUENCY: + eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.flFrequency); + break; + + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: + eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulLeftDirection); + break; + + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: + eax_call.set_value<EaxFrequencyShifterEffectException>(eax_.ulRightDirection); + break; + + default: + throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxFrequencyShifterEffect::validate_frequency( + float flFrequency) +{ + eax_validate_range<EaxFrequencyShifterEffectException>( + "Frequency", + flFrequency, + EAXFREQUENCYSHIFTER_MINFREQUENCY, + EAXFREQUENCYSHIFTER_MAXFREQUENCY); +} + +void EaxFrequencyShifterEffect::validate_left_direction( + unsigned long ulLeftDirection) +{ + eax_validate_range<EaxFrequencyShifterEffectException>( + "Left Direction", + ulLeftDirection, + EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, + EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); +} + +void EaxFrequencyShifterEffect::validate_right_direction( + unsigned long ulRightDirection) +{ + eax_validate_range<EaxFrequencyShifterEffectException>( + "Right Direction", + ulRightDirection, + EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, + EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); +} + +void EaxFrequencyShifterEffect::validate_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all) +{ + validate_frequency(all.flFrequency); + validate_left_direction(all.ulLeftDirection); + validate_right_direction(all.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_frequency( + float flFrequency) +{ + eax_d_.flFrequency = flFrequency; + eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); +} + +void EaxFrequencyShifterEffect::defer_left_direction( + unsigned long ulLeftDirection) +{ + eax_d_.ulLeftDirection = ulLeftDirection; + eax_dirty_flags_.ulLeftDirection = (eax_.ulLeftDirection != eax_d_.ulLeftDirection); +} + +void EaxFrequencyShifterEffect::defer_right_direction( + unsigned long ulRightDirection) +{ + eax_d_.ulRightDirection = ulRightDirection; + eax_dirty_flags_.ulRightDirection = (eax_.ulRightDirection != eax_d_.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_all( + const EAXFREQUENCYSHIFTERPROPERTIES& all) +{ + defer_frequency(all.flFrequency); + defer_left_direction(all.ulLeftDirection); + defer_right_direction(all.ulRightDirection); +} + +void EaxFrequencyShifterEffect::defer_frequency( + const EaxEaxCall& eax_call) +{ + const auto& frequency = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::flFrequency)>(); + + validate_frequency(frequency); + defer_frequency(frequency); +} + +void EaxFrequencyShifterEffect::defer_left_direction( + const EaxEaxCall& eax_call) +{ + const auto& left_direction = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulLeftDirection)>(); + + validate_left_direction(left_direction); + defer_left_direction(left_direction); +} + +void EaxFrequencyShifterEffect::defer_right_direction( + const EaxEaxCall& eax_call) +{ + const auto& right_direction = + eax_call.get_value< + EaxFrequencyShifterEffectException, const decltype(EAXFREQUENCYSHIFTERPROPERTIES::ulRightDirection)>(); + + validate_right_direction(right_direction); + defer_right_direction(right_direction); +} + +void EaxFrequencyShifterEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value< + EaxFrequencyShifterEffectException, const EAXFREQUENCYSHIFTERPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxFrequencyShifterEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxFrequencyShifterEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flFrequency) + { + set_efx_frequency(); + } + + if (eax_dirty_flags_.ulLeftDirection) + { + set_efx_left_direction(); + } + + if (eax_dirty_flags_.ulRightDirection) + { + set_efx_right_direction(); + } + + eax_dirty_flags_ = EaxFrequencyShifterEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxFrequencyShifterEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: + break; + + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXFREQUENCYSHIFTER_FREQUENCY: + defer_frequency(eax_call); + break; + + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: + defer_left_direction(eax_call); + break; + + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: + defer_right_direction(eax_call); + break; + + default: + throw EaxFrequencyShifterEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_frequency_shifter_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxFrequencyShifterEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/modulator.cpp b/al/effects/modulator.cpp index d8a1bed6..6a30dc09 100644 --- a/al/effects/modulator.cpp +++ b/al/effects/modulator.cpp @@ -10,6 +10,15 @@ #include "aloptional.h" #include "effects.h" +#if ALSOFT_EAX +#include <cassert> + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -134,3 +143,405 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Modulator); const EffectProps ModulatorEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxRingModulatorEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxRingModulatorEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxRingModulatorEffectDirtyFlagsValue flFrequency : 1; + EaxRingModulatorEffectDirtyFlagsValue flHighPassCutOff : 1; + EaxRingModulatorEffectDirtyFlagsValue ulWaveform : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxRingModulatorEffect final : + public EaxEffect +{ +public: + EaxRingModulatorEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXRINGMODULATORPROPERTIES eax_{}; + EAXRINGMODULATORPROPERTIES eax_d_{}; + EaxRingModulatorEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_frequency(); + + void set_efx_high_pass_cutoff(); + + void set_efx_waveform(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_frequency( + float flFrequency); + + void validate_high_pass_cutoff( + float flHighPassCutOff); + + void validate_waveform( + unsigned long ulWaveform); + + void validate_all( + const EAXRINGMODULATORPROPERTIES& all); + + + void defer_frequency( + float flFrequency); + + void defer_high_pass_cutoff( + float flHighPassCutOff); + + void defer_waveform( + unsigned long ulWaveform); + + void defer_all( + const EAXRINGMODULATORPROPERTIES& all); + + + void defer_frequency( + const EaxEaxCall& eax_call); + + void defer_high_pass_cutoff( + const EaxEaxCall& eax_call); + + void defer_waveform( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxRingModulatorEffect + + +class EaxRingModulatorEffectException : + public EaxException +{ +public: + explicit EaxRingModulatorEffectException( + const char* message) + : + EaxException{"EAX_RING_MODULATOR_EFFECT", message} + { + } +}; // EaxRingModulatorEffectException + + +EaxRingModulatorEffect::EaxRingModulatorEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxRingModulatorEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxRingModulatorEffect::set_eax_defaults() +{ + eax_.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + eax_.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + eax_.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + + eax_d_ = eax_; +} + +void EaxRingModulatorEffect::set_efx_frequency() +{ + const auto frequency = clamp( + eax_.flFrequency, + AL_RING_MODULATOR_MIN_FREQUENCY, + AL_RING_MODULATOR_MAX_FREQUENCY); + + al_effect_props_.Modulator.Frequency = frequency; +} + +void EaxRingModulatorEffect::set_efx_high_pass_cutoff() +{ + const auto high_pass_cutoff = clamp( + eax_.flHighPassCutOff, + AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF, + AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF); + + al_effect_props_.Modulator.HighPassCutoff = high_pass_cutoff; +} + +void EaxRingModulatorEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast<ALint>(eax_.ulWaveform), + AL_RING_MODULATOR_MIN_WAVEFORM, + AL_RING_MODULATOR_MAX_WAVEFORM); + + const auto efx_waveform = WaveformFromEmum(waveform); + assert(efx_waveform.has_value()); + al_effect_props_.Modulator.Waveform = *efx_waveform; +} + +void EaxRingModulatorEffect::set_efx_defaults() +{ + set_efx_frequency(); + set_efx_high_pass_cutoff(); + set_efx_waveform(); +} + +// [[nodiscard]] +bool EaxRingModulatorEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: + break; + + case EAXRINGMODULATOR_ALLPARAMETERS: + eax_call.set_value<EaxRingModulatorEffectException>(eax_); + break; + + case EAXRINGMODULATOR_FREQUENCY: + eax_call.set_value<EaxRingModulatorEffectException>(eax_.flFrequency); + break; + + case EAXRINGMODULATOR_HIGHPASSCUTOFF: + eax_call.set_value<EaxRingModulatorEffectException>(eax_.flHighPassCutOff); + break; + + case EAXRINGMODULATOR_WAVEFORM: + eax_call.set_value<EaxRingModulatorEffectException>(eax_.ulWaveform); + break; + + default: + throw EaxRingModulatorEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxRingModulatorEffect::validate_frequency( + float flFrequency) +{ + eax_validate_range<EaxRingModulatorEffectException>( + "Frequency", + flFrequency, + EAXRINGMODULATOR_MINFREQUENCY, + EAXRINGMODULATOR_MAXFREQUENCY); +} + +void EaxRingModulatorEffect::validate_high_pass_cutoff( + float flHighPassCutOff) +{ + eax_validate_range<EaxRingModulatorEffectException>( + "High-Pass Cutoff", + flHighPassCutOff, + EAXRINGMODULATOR_MINHIGHPASSCUTOFF, + EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); +} + +void EaxRingModulatorEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range<EaxRingModulatorEffectException>( + "Waveform", + ulWaveform, + EAXRINGMODULATOR_MINWAVEFORM, + EAXRINGMODULATOR_MAXWAVEFORM); +} + +void EaxRingModulatorEffect::validate_all( + const EAXRINGMODULATORPROPERTIES& all) +{ + validate_frequency(all.flFrequency); + validate_high_pass_cutoff(all.flHighPassCutOff); + validate_waveform(all.ulWaveform); +} + +void EaxRingModulatorEffect::defer_frequency( + float flFrequency) +{ + eax_d_.flFrequency = flFrequency; + eax_dirty_flags_.flFrequency = (eax_.flFrequency != eax_d_.flFrequency); +} + +void EaxRingModulatorEffect::defer_high_pass_cutoff( + float flHighPassCutOff) +{ + eax_d_.flHighPassCutOff = flHighPassCutOff; + eax_dirty_flags_.flHighPassCutOff = (eax_.flHighPassCutOff != eax_d_.flHighPassCutOff); +} + +void EaxRingModulatorEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxRingModulatorEffect::defer_all( + const EAXRINGMODULATORPROPERTIES& all) +{ + defer_frequency(all.flFrequency); + defer_high_pass_cutoff(all.flHighPassCutOff); + defer_waveform(all.ulWaveform); +} + +void EaxRingModulatorEffect::defer_frequency( + const EaxEaxCall& eax_call) +{ + const auto& frequency = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flFrequency)>(); + + validate_frequency(frequency); + defer_frequency(frequency); +} + +void EaxRingModulatorEffect::defer_high_pass_cutoff( + const EaxEaxCall& eax_call) +{ + const auto& high_pass_cutoff = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::flHighPassCutOff)>(); + + validate_high_pass_cutoff(high_pass_cutoff); + defer_high_pass_cutoff(high_pass_cutoff); +} + +void EaxRingModulatorEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = + eax_call.get_value< + EaxRingModulatorEffectException, const decltype(EAXRINGMODULATORPROPERTIES::ulWaveform)>(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxRingModulatorEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxRingModulatorEffectException, const EAXRINGMODULATORPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxRingModulatorEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxRingModulatorEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.flFrequency) + { + set_efx_frequency(); + } + + if (eax_dirty_flags_.flHighPassCutOff) + { + set_efx_high_pass_cutoff(); + } + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + eax_dirty_flags_ = EaxRingModulatorEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxRingModulatorEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: + break; + + case EAXRINGMODULATOR_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXRINGMODULATOR_FREQUENCY: + defer_frequency(eax_call); + break; + + case EAXRINGMODULATOR_HIGHPASSCUTOFF: + defer_high_pass_cutoff(eax_call); + break; + + case EAXRINGMODULATOR_WAVEFORM: + defer_waveform(eax_call); + break; + + default: + throw EaxRingModulatorEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_ring_modulator_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxRingModulatorEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/null.cpp b/al/effects/null.cpp index 516446db..44595208 100644 --- a/al/effects/null.cpp +++ b/al/effects/null.cpp @@ -7,6 +7,10 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "al/eax_exception.h" +#endif // ALSOFT_EAX + namespace { @@ -91,3 +95,56 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Null); const EffectProps NullEffectProps{genDefaultProps()}; + + +#if ALSOFT_EAX +namespace +{ + + +class EaxNullEffect final : + public EaxEffect +{ +public: + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; +}; // EaxNullEffect + + +class EaxNullEffectException : + public EaxException +{ +public: + explicit EaxNullEffectException( + const char* message) + : + EaxException{"EAX_NULL_EFFECT", message} + { + } +}; // EaxNullEffectException + + +// [[nodiscard]] +bool EaxNullEffect::dispatch( + const EaxEaxCall& eax_call) +{ + if (eax_call.get_property_id() != 0) + { + throw EaxNullEffectException{"Unsupported property id."}; + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_null_effect() +{ + return std::make_unique<EaxNullEffect>(); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/pshifter.cpp b/al/effects/pshifter.cpp index 56059a3c..03f9a139 100644 --- a/al/effects/pshifter.cpp +++ b/al/effects/pshifter.cpp @@ -7,6 +7,13 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -82,3 +89,334 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Pshifter); const EffectProps PshifterEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxPitchShifterEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxPitchShifterEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxPitchShifterEffectDirtyFlagsValue lCoarseTune : 1; + EaxPitchShifterEffectDirtyFlagsValue lFineTune : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxPitchShifterEffect final : + public EaxEffect +{ +public: + EaxPitchShifterEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXPITCHSHIFTERPROPERTIES eax_{}; + EAXPITCHSHIFTERPROPERTIES eax_d_{}; + EaxPitchShifterEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_coarse_tune(); + + void set_efx_fine_tune(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_coarse_tune( + long lCoarseTune); + + void validate_fine_tune( + long lFineTune); + + void validate_all( + const EAXPITCHSHIFTERPROPERTIES& all); + + + void defer_coarse_tune( + long lCoarseTune); + + void defer_fine_tune( + long lFineTune); + + void defer_all( + const EAXPITCHSHIFTERPROPERTIES& all); + + + void defer_coarse_tune( + const EaxEaxCall& eax_call); + + void defer_fine_tune( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxPitchShifterEffect + + +class EaxPitchShifterEffectException : + public EaxException +{ +public: + explicit EaxPitchShifterEffectException( + const char* message) + : + EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} + { + } +}; // EaxPitchShifterEffectException + + +EaxPitchShifterEffect::EaxPitchShifterEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxPitchShifterEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxPitchShifterEffect::set_eax_defaults() +{ + eax_.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; + eax_.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; + + eax_d_ = eax_; +} + +void EaxPitchShifterEffect::set_efx_coarse_tune() +{ + const auto coarse_tune = clamp( + static_cast<ALint>(eax_.lCoarseTune), + AL_PITCH_SHIFTER_MIN_COARSE_TUNE, + AL_PITCH_SHIFTER_MAX_COARSE_TUNE); + + al_effect_props_.Pshifter.CoarseTune = coarse_tune; +} + +void EaxPitchShifterEffect::set_efx_fine_tune() +{ + const auto fine_tune = clamp( + static_cast<ALint>(eax_.lFineTune), + AL_PITCH_SHIFTER_MIN_FINE_TUNE, + AL_PITCH_SHIFTER_MAX_FINE_TUNE); + + al_effect_props_.Pshifter.FineTune = fine_tune; +} + +void EaxPitchShifterEffect::set_efx_defaults() +{ + set_efx_coarse_tune(); + set_efx_fine_tune(); +} + +// [[nodiscard]] +bool EaxPitchShifterEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: + break; + + case EAXPITCHSHIFTER_ALLPARAMETERS: + eax_call.set_value<EaxPitchShifterEffectException>(eax_); + break; + + case EAXPITCHSHIFTER_COARSETUNE: + eax_call.set_value<EaxPitchShifterEffectException>(eax_.lCoarseTune); + break; + + case EAXPITCHSHIFTER_FINETUNE: + eax_call.set_value<EaxPitchShifterEffectException>(eax_.lFineTune); + break; + + default: + throw EaxPitchShifterEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxPitchShifterEffect::validate_coarse_tune( + long lCoarseTune) +{ + eax_validate_range<EaxPitchShifterEffectException>( + "Coarse Tune", + lCoarseTune, + EAXPITCHSHIFTER_MINCOARSETUNE, + EAXPITCHSHIFTER_MAXCOARSETUNE); +} + +void EaxPitchShifterEffect::validate_fine_tune( + long lFineTune) +{ + eax_validate_range<EaxPitchShifterEffectException>( + "Fine Tune", + lFineTune, + EAXPITCHSHIFTER_MINFINETUNE, + EAXPITCHSHIFTER_MAXFINETUNE); +} + +void EaxPitchShifterEffect::validate_all( + const EAXPITCHSHIFTERPROPERTIES& all) +{ + validate_coarse_tune(all.lCoarseTune); + validate_fine_tune(all.lFineTune); +} + +void EaxPitchShifterEffect::defer_coarse_tune( + long lCoarseTune) +{ + eax_d_.lCoarseTune = lCoarseTune; + eax_dirty_flags_.lCoarseTune = (eax_.lCoarseTune != eax_d_.lCoarseTune); +} + +void EaxPitchShifterEffect::defer_fine_tune( + long lFineTune) +{ + eax_d_.lFineTune = lFineTune; + eax_dirty_flags_.lFineTune = (eax_.lFineTune != eax_d_.lFineTune); +} + +void EaxPitchShifterEffect::defer_all( + const EAXPITCHSHIFTERPROPERTIES& all) +{ + defer_coarse_tune(all.lCoarseTune); + defer_fine_tune(all.lFineTune); +} + +void EaxPitchShifterEffect::defer_coarse_tune( + const EaxEaxCall& eax_call) +{ + const auto& coarse_tune = + eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lCoarseTune)>(); + + validate_coarse_tune(coarse_tune); + defer_coarse_tune(coarse_tune); +} + +void EaxPitchShifterEffect::defer_fine_tune( + const EaxEaxCall& eax_call) +{ + const auto& fine_tune = + eax_call.get_value<EaxPitchShifterEffectException, const decltype(EAXPITCHSHIFTERPROPERTIES::lFineTune)>(); + + validate_fine_tune(fine_tune); + defer_fine_tune(fine_tune); +} + +void EaxPitchShifterEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = + eax_call.get_value<EaxPitchShifterEffectException, const EAXPITCHSHIFTERPROPERTIES>(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxPitchShifterEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxPitchShifterEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.lCoarseTune) + { + set_efx_coarse_tune(); + } + + if (eax_dirty_flags_.lFineTune) + { + set_efx_fine_tune(); + } + + eax_dirty_flags_ = EaxPitchShifterEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxPitchShifterEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: + break; + + case EAXPITCHSHIFTER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXPITCHSHIFTER_COARSETUNE: + defer_coarse_tune(eax_call); + break; + + case EAXPITCHSHIFTER_FINETUNE: + defer_fine_tune(eax_call); + break; + + default: + throw EaxPitchShifterEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_pitch_shifter_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxPitchShifterEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/reverb.cpp b/al/effects/reverb.cpp index 3f234b93..8012450d 100644 --- a/al/effects/reverb.cpp +++ b/al/effects/reverb.cpp @@ -9,6 +9,15 @@ #include "alc/effects/base.h" #include "effects.h" +#if ALSOFT_EAX +#include <tuple> + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -554,3 +563,1916 @@ const EffectProps ReverbEffectProps{genDefaultProps()}; DEFINE_ALEFFECT_VTABLE(StdReverb); const EffectProps StdReverbEffectProps{genDefaultStdProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxReverbEffectDirtyFlagsValue = std::uint_least32_t; + +struct EaxReverbEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxReverbEffectDirtyFlagsValue ulEnvironment : 1; + EaxReverbEffectDirtyFlagsValue flEnvironmentSize : 1; + EaxReverbEffectDirtyFlagsValue flEnvironmentDiffusion : 1; + EaxReverbEffectDirtyFlagsValue lRoom : 1; + EaxReverbEffectDirtyFlagsValue lRoomHF : 1; + EaxReverbEffectDirtyFlagsValue lRoomLF : 1; + EaxReverbEffectDirtyFlagsValue flDecayTime : 1; + EaxReverbEffectDirtyFlagsValue flDecayHFRatio : 1; + EaxReverbEffectDirtyFlagsValue flDecayLFRatio : 1; + EaxReverbEffectDirtyFlagsValue lReflections : 1; + EaxReverbEffectDirtyFlagsValue flReflectionsDelay : 1; + EaxReverbEffectDirtyFlagsValue vReflectionsPan : 1; + EaxReverbEffectDirtyFlagsValue lReverb : 1; + EaxReverbEffectDirtyFlagsValue flReverbDelay : 1; + EaxReverbEffectDirtyFlagsValue vReverbPan : 1; + EaxReverbEffectDirtyFlagsValue flEchoTime : 1; + EaxReverbEffectDirtyFlagsValue flEchoDepth : 1; + EaxReverbEffectDirtyFlagsValue flModulationTime : 1; + EaxReverbEffectDirtyFlagsValue flModulationDepth : 1; + EaxReverbEffectDirtyFlagsValue flAirAbsorptionHF : 1; + EaxReverbEffectDirtyFlagsValue flHFReference : 1; + EaxReverbEffectDirtyFlagsValue flLFReference : 1; + EaxReverbEffectDirtyFlagsValue flRoomRolloffFactor : 1; + EaxReverbEffectDirtyFlagsValue ulFlags : 1; +}; // EaxReverbEffectDirtyFlags + + +class EaxReverbEffect final : + public EaxEffect +{ +public: + EaxReverbEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXREVERBPROPERTIES eax_{}; + EAXREVERBPROPERTIES eax_d_{}; + EaxReverbEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_density(); + + void set_efx_diffusion(); + + void set_efx_gain(); + + void set_efx_gain_hf(); + + void set_efx_gain_lf(); + + void set_efx_decay_time(); + + void set_efx_decay_hf_ratio(); + + void set_efx_decay_lf_ratio(); + + void set_efx_reflections_gain(); + + void set_efx_reflections_delay(); + + void set_efx_reflections_pan(); + + void set_efx_late_reverb_gain(); + + void set_efx_late_reverb_delay(); + + void set_efx_late_reverb_pan(); + + void set_efx_echo_time(); + + void set_efx_echo_depth(); + + void set_efx_modulation_time(); + + void set_efx_modulation_depth(); + + void set_efx_air_absorption_gain_hf(); + + void set_efx_hf_reference(); + + void set_efx_lf_reference(); + + void set_efx_room_rolloff_factor(); + + void set_efx_flags(); + + void set_efx_defaults(); + + + void get_all( + const EaxEaxCall& eax_call) const; + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call) const; + + + void validate_environment( + unsigned long ulEnvironment, + int version, + bool is_standalone); + + void validate_environment_size( + float flEnvironmentSize); + + void validate_environment_diffusion( + float flEnvironmentDiffusion); + + void validate_room( + long lRoom); + + void validate_room_hf( + long lRoomHF); + + void validate_room_lf( + long lRoomLF); + + void validate_decay_time( + float flDecayTime); + + void validate_decay_hf_ratio( + float flDecayHFRatio); + + void validate_decay_lf_ratio( + float flDecayLFRatio); + + void validate_reflections( + long lReflections); + + void validate_reflections_delay( + float flReflectionsDelay); + + void validate_reflections_pan( + const EAXVECTOR& vReflectionsPan); + + void validate_reverb( + long lReverb); + + void validate_reverb_delay( + float flReverbDelay); + + void validate_reverb_pan( + const EAXVECTOR& vReverbPan); + + void validate_echo_time( + float flEchoTime); + + void validate_echo_depth( + float flEchoDepth); + + void validate_modulation_time( + float flModulationTime); + + void validate_modulation_depth( + float flModulationDepth); + + void validate_air_absorbtion_hf( + float air_absorbtion_hf); + + void validate_hf_reference( + float flHFReference); + + void validate_lf_reference( + float flLFReference); + + void validate_room_rolloff_factor( + float flRoomRolloffFactor); + + void validate_flags( + unsigned long ulFlags); + + void validate_all( + const EAX20LISTENERPROPERTIES& all, + int version); + + void validate_all( + const EAXREVERBPROPERTIES& all, + int version); + + + void defer_environment( + unsigned long ulEnvironment); + + void defer_environment_size( + float flEnvironmentSize); + + void defer_environment_diffusion( + float flEnvironmentDiffusion); + + void defer_room( + long lRoom); + + void defer_room_hf( + long lRoomHF); + + void defer_room_lf( + long lRoomLF); + + void defer_decay_time( + float flDecayTime); + + void defer_decay_hf_ratio( + float flDecayHFRatio); + + void defer_decay_lf_ratio( + float flDecayLFRatio); + + void defer_reflections( + long lReflections); + + void defer_reflections_delay( + float flReflectionsDelay); + + void defer_reflections_pan( + const EAXVECTOR& vReflectionsPan); + + void defer_reverb( + long lReverb); + + void defer_reverb_delay( + float flReverbDelay); + + void defer_reverb_pan( + const EAXVECTOR& vReverbPan); + + void defer_echo_time( + float flEchoTime); + + void defer_echo_depth( + float flEchoDepth); + + void defer_modulation_time( + float flModulationTime); + + void defer_modulation_depth( + float flModulationDepth); + + void defer_air_absorbtion_hf( + float flAirAbsorptionHF); + + void defer_hf_reference( + float flHFReference); + + void defer_lf_reference( + float flLFReference); + + void defer_room_rolloff_factor( + float flRoomRolloffFactor); + + void defer_flags( + unsigned long ulFlags); + + void defer_all( + const EAX20LISTENERPROPERTIES& all); + + void defer_all( + const EAXREVERBPROPERTIES& all); + + + void defer_environment( + const EaxEaxCall& eax_call); + + void defer_environment_size( + const EaxEaxCall& eax_call); + + void defer_environment_diffusion( + const EaxEaxCall& eax_call); + + void defer_room( + const EaxEaxCall& eax_call); + + void defer_room_hf( + const EaxEaxCall& eax_call); + + void defer_room_lf( + const EaxEaxCall& eax_call); + + void defer_decay_time( + const EaxEaxCall& eax_call); + + void defer_decay_hf_ratio( + const EaxEaxCall& eax_call); + + void defer_decay_lf_ratio( + const EaxEaxCall& eax_call); + + void defer_reflections( + const EaxEaxCall& eax_call); + + void defer_reflections_delay( + const EaxEaxCall& eax_call); + + void defer_reflections_pan( + const EaxEaxCall& eax_call); + + void defer_reverb( + const EaxEaxCall& eax_call); + + void defer_reverb_delay( + const EaxEaxCall& eax_call); + + void defer_reverb_pan( + const EaxEaxCall& eax_call); + + void defer_echo_time( + const EaxEaxCall& eax_call); + + void defer_echo_depth( + const EaxEaxCall& eax_call); + + void defer_modulation_time( + const EaxEaxCall& eax_call); + + void defer_modulation_depth( + const EaxEaxCall& eax_call); + + void defer_air_absorbtion_hf( + const EaxEaxCall& eax_call); + + void defer_hf_reference( + const EaxEaxCall& eax_call); + + void defer_lf_reference( + const EaxEaxCall& eax_call); + + void defer_room_rolloff_factor( + const EaxEaxCall& eax_call); + + void defer_flags( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxReverbEffect + + +class EaxReverbEffectException : + public EaxException +{ +public: + explicit EaxReverbEffectException( + const char* message) + : + EaxException{"EAX_REVERB_EFFECT", message} + { + } +}; // EaxReverbEffectException + + +EaxReverbEffect::EaxReverbEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxReverbEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxReverbEffect::set_eax_defaults() +{ + eax_ = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; + + eax_d_ = eax_; +} + +void EaxReverbEffect::set_efx_density() +{ + const auto eax_environment_size = eax_.flEnvironmentSize; + + const auto efx_density = clamp( + (eax_environment_size * eax_environment_size * eax_environment_size) / 16.0F, + AL_EAXREVERB_MIN_DENSITY, + AL_EAXREVERB_MAX_DENSITY); + + al_effect_props_.Reverb.Density = efx_density; +} + +void EaxReverbEffect::set_efx_diffusion() +{ + const auto efx_diffusion = clamp( + eax_.flEnvironmentDiffusion, + AL_EAXREVERB_MIN_DIFFUSION, + AL_EAXREVERB_MAX_DIFFUSION); + + al_effect_props_.Reverb.Diffusion = efx_diffusion; +} + +void EaxReverbEffect::set_efx_gain() +{ + const auto efx_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lRoom)), + AL_EAXREVERB_MIN_GAIN, + AL_EAXREVERB_MAX_GAIN); + + al_effect_props_.Reverb.Gain = efx_gain; +} + +void EaxReverbEffect::set_efx_gain_hf() +{ + const auto efx_gain_hf = clamp( + level_mb_to_gain(static_cast<float>(eax_.lRoomHF)), + AL_EAXREVERB_MIN_GAINHF, + AL_EAXREVERB_MAX_GAINHF); + + al_effect_props_.Reverb.GainHF = efx_gain_hf; +} + +void EaxReverbEffect::set_efx_gain_lf() +{ + const auto efx_gain_lf = clamp( + level_mb_to_gain(static_cast<float>(eax_.lRoomLF)), + AL_EAXREVERB_MIN_GAINLF, + AL_EAXREVERB_MAX_GAINLF); + + al_effect_props_.Reverb.GainLF = efx_gain_lf; +} + +void EaxReverbEffect::set_efx_decay_time() +{ + const auto efx_decay_time = clamp( + eax_.flDecayTime, + AL_EAXREVERB_MIN_DECAY_TIME, + AL_EAXREVERB_MAX_DECAY_TIME); + + al_effect_props_.Reverb.DecayTime = efx_decay_time; +} + +void EaxReverbEffect::set_efx_decay_hf_ratio() +{ + const auto efx_decay_hf_ratio = clamp( + eax_.flDecayHFRatio, + AL_EAXREVERB_MIN_DECAY_HFRATIO, + AL_EAXREVERB_MAX_DECAY_HFRATIO); + + al_effect_props_.Reverb.DecayHFRatio = efx_decay_hf_ratio; +} + +void EaxReverbEffect::set_efx_decay_lf_ratio() +{ + const auto efx_decay_lf_ratio = clamp( + eax_.flDecayLFRatio, + AL_EAXREVERB_MIN_DECAY_LFRATIO, + AL_EAXREVERB_MAX_DECAY_LFRATIO); + + al_effect_props_.Reverb.DecayLFRatio = efx_decay_lf_ratio; +} + +void EaxReverbEffect::set_efx_reflections_gain() +{ + const auto efx_reflections_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lReflections)), + AL_EAXREVERB_MIN_REFLECTIONS_GAIN, + AL_EAXREVERB_MAX_REFLECTIONS_GAIN); + + al_effect_props_.Reverb.ReflectionsGain = efx_reflections_gain; +} + +void EaxReverbEffect::set_efx_reflections_delay() +{ + const auto efx_reflections_delay = clamp( + eax_.flReflectionsDelay, + AL_EAXREVERB_MIN_REFLECTIONS_DELAY, + AL_EAXREVERB_MAX_REFLECTIONS_DELAY); + + al_effect_props_.Reverb.ReflectionsDelay = efx_reflections_delay; +} + +void EaxReverbEffect::set_efx_reflections_pan() +{ + al_effect_props_.Reverb.ReflectionsPan[0] = eax_.vReflectionsPan.x; + al_effect_props_.Reverb.ReflectionsPan[1] = eax_.vReflectionsPan.y; + al_effect_props_.Reverb.ReflectionsPan[2] = eax_.vReflectionsPan.z; +} + +void EaxReverbEffect::set_efx_late_reverb_gain() +{ + const auto efx_late_reverb_gain = clamp( + level_mb_to_gain(static_cast<float>(eax_.lReverb)), + AL_EAXREVERB_MIN_LATE_REVERB_GAIN, + AL_EAXREVERB_MAX_LATE_REVERB_GAIN); + + al_effect_props_.Reverb.LateReverbGain = efx_late_reverb_gain; +} + +void EaxReverbEffect::set_efx_late_reverb_delay() +{ + const auto efx_late_reverb_delay = clamp( + eax_.flReverbDelay, + AL_EAXREVERB_MIN_LATE_REVERB_DELAY, + AL_EAXREVERB_MAX_LATE_REVERB_DELAY); + + al_effect_props_.Reverb.LateReverbDelay = efx_late_reverb_delay; +} + +void EaxReverbEffect::set_efx_late_reverb_pan() +{ + al_effect_props_.Reverb.LateReverbPan[0] = eax_.vReverbPan.x; + al_effect_props_.Reverb.LateReverbPan[1] = eax_.vReverbPan.y; + al_effect_props_.Reverb.LateReverbPan[2] = eax_.vReverbPan.z; +} + +void EaxReverbEffect::set_efx_echo_time() +{ + const auto efx_echo_time = clamp( + eax_.flEchoTime, + AL_EAXREVERB_MIN_ECHO_TIME, + AL_EAXREVERB_MAX_ECHO_TIME); + + al_effect_props_.Reverb.EchoTime = efx_echo_time; +} + +void EaxReverbEffect::set_efx_echo_depth() +{ + const auto efx_echo_depth = clamp( + eax_.flEchoDepth, + AL_EAXREVERB_MIN_ECHO_DEPTH, + AL_EAXREVERB_MAX_ECHO_DEPTH); + + al_effect_props_.Reverb.EchoDepth = efx_echo_depth; +} + +void EaxReverbEffect::set_efx_modulation_time() +{ + const auto efx_modulation_time = clamp( + eax_.flModulationTime, + AL_EAXREVERB_MIN_MODULATION_TIME, + AL_EAXREVERB_MAX_MODULATION_TIME); + + al_effect_props_.Reverb.ModulationTime = efx_modulation_time; +} + +void EaxReverbEffect::set_efx_modulation_depth() +{ + const auto efx_modulation_depth = clamp( + eax_.flModulationDepth, + AL_EAXREVERB_MIN_MODULATION_DEPTH, + AL_EAXREVERB_MAX_MODULATION_DEPTH); + + al_effect_props_.Reverb.ModulationDepth = efx_modulation_depth; +} + +void EaxReverbEffect::set_efx_air_absorption_gain_hf() +{ + const auto efx_air_absorption_hf = clamp( + level_mb_to_gain(eax_.flAirAbsorptionHF), + AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF, + AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF); + + al_effect_props_.Reverb.AirAbsorptionGainHF = efx_air_absorption_hf; +} + +void EaxReverbEffect::set_efx_hf_reference() +{ + const auto efx_hf_reference = clamp( + eax_.flHFReference, + AL_EAXREVERB_MIN_HFREFERENCE, + AL_EAXREVERB_MAX_HFREFERENCE); + + al_effect_props_.Reverb.HFReference = efx_hf_reference; +} + +void EaxReverbEffect::set_efx_lf_reference() +{ + const auto efx_lf_reference = clamp( + eax_.flLFReference, + AL_EAXREVERB_MIN_LFREFERENCE, + AL_EAXREVERB_MAX_LFREFERENCE); + + al_effect_props_.Reverb.LFReference = efx_lf_reference; +} + +void EaxReverbEffect::set_efx_room_rolloff_factor() +{ + const auto efx_room_rolloff_factor = clamp( + eax_.flRoomRolloffFactor, + AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR, + AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR); + + al_effect_props_.Reverb.RoomRolloffFactor = efx_room_rolloff_factor; +} + +void EaxReverbEffect::set_efx_flags() +{ + al_effect_props_.Reverb.DecayHFLimit = ((eax_.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); +} + +void EaxReverbEffect::set_efx_defaults() +{ + set_efx_density(); + set_efx_diffusion(); + set_efx_gain(); + set_efx_gain_hf(); + set_efx_gain_lf(); + set_efx_decay_time(); + set_efx_decay_hf_ratio(); + set_efx_decay_lf_ratio(); + set_efx_reflections_gain(); + set_efx_reflections_delay(); + set_efx_reflections_pan(); + set_efx_late_reverb_gain(); + set_efx_late_reverb_delay(); + set_efx_late_reverb_pan(); + set_efx_echo_time(); + set_efx_echo_depth(); + set_efx_modulation_time(); + set_efx_modulation_depth(); + set_efx_air_absorption_gain_hf(); + set_efx_hf_reference(); + set_efx_lf_reference(); + set_efx_room_rolloff_factor(); + set_efx_flags(); +} + +void EaxReverbEffect::get_all( + const EaxEaxCall& eax_call) const +{ + if (eax_call.get_version() == 2) + { + auto& eax_reverb = eax_call.get_value<EaxReverbEffectException, EAX20LISTENERPROPERTIES>(); + eax_reverb.lRoom = eax_.lRoom; + eax_reverb.lRoomHF = eax_.lRoomHF; + eax_reverb.flRoomRolloffFactor = eax_.flRoomRolloffFactor; + eax_reverb.flDecayTime = eax_.flDecayTime; + eax_reverb.flDecayHFRatio = eax_.flDecayHFRatio; + eax_reverb.lReflections = eax_.lReflections; + eax_reverb.flReflectionsDelay = eax_.flReflectionsDelay; + eax_reverb.lReverb = eax_.lReverb; + eax_reverb.flReverbDelay = eax_.flReverbDelay; + eax_reverb.dwEnvironment = eax_.ulEnvironment; + eax_reverb.flEnvironmentSize = eax_.flEnvironmentSize; + eax_reverb.flEnvironmentDiffusion = eax_.flEnvironmentDiffusion; + eax_reverb.flAirAbsorptionHF = eax_.flAirAbsorptionHF; + eax_reverb.dwFlags = eax_.ulFlags; + } + else + { + eax_call.set_value<EaxReverbEffectException>(eax_); + } +} + +// [[nodiscard]] +bool EaxReverbEffect::get( + const EaxEaxCall& eax_call) const +{ + switch (eax_call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + get_all(eax_call); + break; + + case EAXREVERB_ENVIRONMENT: + eax_call.set_value<EaxReverbEffectException>(eax_.ulEnvironment); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + eax_call.set_value<EaxReverbEffectException>(eax_.flEnvironmentSize); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + eax_call.set_value<EaxReverbEffectException>(eax_.flEnvironmentDiffusion); + break; + + case EAXREVERB_ROOM: + eax_call.set_value<EaxReverbEffectException>(eax_.lRoom); + break; + + case EAXREVERB_ROOMHF: + eax_call.set_value<EaxReverbEffectException>(eax_.lRoomHF); + break; + + case EAXREVERB_ROOMLF: + eax_call.set_value<EaxReverbEffectException>(eax_.lRoomLF); + break; + + case EAXREVERB_DECAYTIME: + eax_call.set_value<EaxReverbEffectException>(eax_.flDecayTime); + break; + + case EAXREVERB_DECAYHFRATIO: + eax_call.set_value<EaxReverbEffectException>(eax_.flDecayHFRatio); + break; + + case EAXREVERB_DECAYLFRATIO: + eax_call.set_value<EaxReverbEffectException>(eax_.flDecayLFRatio); + break; + + case EAXREVERB_REFLECTIONS: + eax_call.set_value<EaxReverbEffectException>(eax_.lReflections); + break; + + case EAXREVERB_REFLECTIONSDELAY: + eax_call.set_value<EaxReverbEffectException>(eax_.flReflectionsDelay); + break; + + case EAXREVERB_REFLECTIONSPAN: + eax_call.set_value<EaxReverbEffectException>(eax_.vReflectionsPan); + break; + + case EAXREVERB_REVERB: + eax_call.set_value<EaxReverbEffectException>(eax_.lReverb); + break; + + case EAXREVERB_REVERBDELAY: + eax_call.set_value<EaxReverbEffectException>(eax_.flReverbDelay); + break; + + case EAXREVERB_REVERBPAN: + eax_call.set_value<EaxReverbEffectException>(eax_.vReverbPan); + break; + + case EAXREVERB_ECHOTIME: + eax_call.set_value<EaxReverbEffectException>(eax_.flEchoTime); + break; + + case EAXREVERB_ECHODEPTH: + eax_call.set_value<EaxReverbEffectException>(eax_.flEchoDepth); + break; + + case EAXREVERB_MODULATIONTIME: + eax_call.set_value<EaxReverbEffectException>(eax_.flModulationTime); + break; + + case EAXREVERB_MODULATIONDEPTH: + eax_call.set_value<EaxReverbEffectException>(eax_.flModulationDepth); + break; + + case EAXREVERB_AIRABSORPTIONHF: + eax_call.set_value<EaxReverbEffectException>(eax_.flAirAbsorptionHF); + break; + + case EAXREVERB_HFREFERENCE: + eax_call.set_value<EaxReverbEffectException>(eax_.flHFReference); + break; + + case EAXREVERB_LFREFERENCE: + eax_call.set_value<EaxReverbEffectException>(eax_.flLFReference); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + eax_call.set_value<EaxReverbEffectException>(eax_.flRoomRolloffFactor); + break; + + case EAXREVERB_FLAGS: + eax_call.set_value<EaxReverbEffectException>(eax_.ulFlags); + break; + + default: + throw EaxReverbEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxReverbEffect::validate_environment( + unsigned long ulEnvironment, + int version, + bool is_standalone) +{ + eax_validate_range<EaxReverbEffectException>( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + (version == 2 || is_standalone) ? EAX20REVERB_MAXENVIRONMENT : EAX30REVERB_MAXENVIRONMENT); +} + +void EaxReverbEffect::validate_environment_size( + float flEnvironmentSize) +{ + eax_validate_range<EaxReverbEffectException>( + "Environment Size", + flEnvironmentSize, + EAXREVERB_MINENVIRONMENTSIZE, + EAXREVERB_MAXENVIRONMENTSIZE); +} + +void EaxReverbEffect::validate_environment_diffusion( + float flEnvironmentDiffusion) +{ + eax_validate_range<EaxReverbEffectException>( + "Environment Diffusion", + flEnvironmentDiffusion, + EAXREVERB_MINENVIRONMENTDIFFUSION, + EAXREVERB_MAXENVIRONMENTDIFFUSION); +} + +void EaxReverbEffect::validate_room( + long lRoom) +{ + eax_validate_range<EaxReverbEffectException>( + "Room", + lRoom, + EAXREVERB_MINROOM, + EAXREVERB_MAXROOM); +} + +void EaxReverbEffect::validate_room_hf( + long lRoomHF) +{ + eax_validate_range<EaxReverbEffectException>( + "Room HF", + lRoomHF, + EAXREVERB_MINROOMHF, + EAXREVERB_MAXROOMHF); +} + +void EaxReverbEffect::validate_room_lf( + long lRoomLF) +{ + eax_validate_range<EaxReverbEffectException>( + "Room LF", + lRoomLF, + EAXREVERB_MINROOMLF, + EAXREVERB_MAXROOMLF); +} + +void EaxReverbEffect::validate_decay_time( + float flDecayTime) +{ + eax_validate_range<EaxReverbEffectException>( + "Decay Time", + flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); +} + +void EaxReverbEffect::validate_decay_hf_ratio( + float flDecayHFRatio) +{ + eax_validate_range<EaxReverbEffectException>( + "Decay HF Ratio", + flDecayHFRatio, + EAXREVERB_MINDECAYHFRATIO, + EAXREVERB_MAXDECAYHFRATIO); +} + +void EaxReverbEffect::validate_decay_lf_ratio( + float flDecayLFRatio) +{ + eax_validate_range<EaxReverbEffectException>( + "Decay LF Ratio", + flDecayLFRatio, + EAXREVERB_MINDECAYLFRATIO, + EAXREVERB_MAXDECAYLFRATIO); +} + +void EaxReverbEffect::validate_reflections( + long lReflections) +{ + eax_validate_range<EaxReverbEffectException>( + "Reflections", + lReflections, + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); +} + +void EaxReverbEffect::validate_reflections_delay( + float flReflectionsDelay) +{ + eax_validate_range<EaxReverbEffectException>( + "Reflections Delay", + flReflectionsDelay, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); +} + +void EaxReverbEffect::validate_reflections_pan( + const EAXVECTOR& vReflectionsPan) +{ + std::ignore = vReflectionsPan; +} + +void EaxReverbEffect::validate_reverb( + long lReverb) +{ + eax_validate_range<EaxReverbEffectException>( + "Reverb", + lReverb, + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); +} + +void EaxReverbEffect::validate_reverb_delay( + float flReverbDelay) +{ + eax_validate_range<EaxReverbEffectException>( + "Reverb Delay", + flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); +} + +void EaxReverbEffect::validate_reverb_pan( + const EAXVECTOR& vReverbPan) +{ + std::ignore = vReverbPan; +} + +void EaxReverbEffect::validate_echo_time( + float flEchoTime) +{ + eax_validate_range<EaxReverbEffectException>( + "Echo Time", + flEchoTime, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); +} + +void EaxReverbEffect::validate_echo_depth( + float flEchoDepth) +{ + eax_validate_range<EaxReverbEffectException>( + "Echo Depth", + flEchoDepth, + EAXREVERB_MINECHODEPTH, + EAXREVERB_MAXECHODEPTH); +} + +void EaxReverbEffect::validate_modulation_time( + float flModulationTime) +{ + eax_validate_range<EaxReverbEffectException>( + "Modulation Time", + flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); +} + +void EaxReverbEffect::validate_modulation_depth( + float flModulationDepth) +{ + eax_validate_range<EaxReverbEffectException>( + "Modulation Depth", + flModulationDepth, + EAXREVERB_MINMODULATIONDEPTH, + EAXREVERB_MAXMODULATIONDEPTH); +} + +void EaxReverbEffect::validate_air_absorbtion_hf( + float air_absorbtion_hf) +{ + eax_validate_range<EaxReverbEffectException>( + "Air Absorbtion HF", + air_absorbtion_hf, + EAXREVERB_MINAIRABSORPTIONHF, + EAXREVERB_MAXAIRABSORPTIONHF); +} + +void EaxReverbEffect::validate_hf_reference( + float flHFReference) +{ + eax_validate_range<EaxReverbEffectException>( + "HF Reference", + flHFReference, + EAXREVERB_MINHFREFERENCE, + EAXREVERB_MAXHFREFERENCE); +} + +void EaxReverbEffect::validate_lf_reference( + float flLFReference) +{ + eax_validate_range<EaxReverbEffectException>( + "LF Reference", + flLFReference, + EAXREVERB_MINLFREFERENCE, + EAXREVERB_MAXLFREFERENCE); +} + +void EaxReverbEffect::validate_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_validate_range<EaxReverbEffectException>( + "Room Rolloff Factor", + flRoomRolloffFactor, + EAXREVERB_MINROOMROLLOFFFACTOR, + EAXREVERB_MAXROOMROLLOFFFACTOR); +} + +void EaxReverbEffect::validate_flags( + unsigned long ulFlags) +{ + eax_validate_range<EaxReverbEffectException>( + "Flags", + ulFlags, + 0UL, + ~EAXREVERBFLAGS_RESERVED); +} + +void EaxReverbEffect::validate_all( + const EAX20LISTENERPROPERTIES& listener, + int version) +{ + validate_room(listener.lRoom); + validate_room_hf(listener.lRoomHF); + validate_room_rolloff_factor(listener.flRoomRolloffFactor); + validate_decay_time(listener.flDecayTime); + validate_decay_hf_ratio(listener.flDecayHFRatio); + validate_reflections(listener.lReflections); + validate_reflections_delay(listener.flReflectionsDelay); + validate_reverb(listener.lReverb); + validate_reverb_delay(listener.flReverbDelay); + validate_environment(listener.dwEnvironment, version, false); + validate_environment_size(listener.flEnvironmentSize); + validate_environment_diffusion(listener.flEnvironmentDiffusion); + validate_air_absorbtion_hf(listener.flAirAbsorptionHF); + validate_flags(listener.dwFlags); +} + +void EaxReverbEffect::validate_all( + const EAXREVERBPROPERTIES& lReverb, + int version) +{ + validate_environment(lReverb.ulEnvironment, version, false); + validate_environment_size(lReverb.flEnvironmentSize); + validate_environment_diffusion(lReverb.flEnvironmentDiffusion); + validate_room(lReverb.lRoom); + validate_room_hf(lReverb.lRoomHF); + validate_room_lf(lReverb.lRoomLF); + validate_decay_time(lReverb.flDecayTime); + validate_decay_hf_ratio(lReverb.flDecayHFRatio); + validate_decay_lf_ratio(lReverb.flDecayLFRatio); + validate_reflections(lReverb.lReflections); + validate_reflections_delay(lReverb.flReflectionsDelay); + validate_reverb(lReverb.lReverb); + validate_reverb_delay(lReverb.flReverbDelay); + validate_echo_time(lReverb.flEchoTime); + validate_echo_depth(lReverb.flEchoDepth); + validate_modulation_time(lReverb.flModulationTime); + validate_modulation_depth(lReverb.flModulationDepth); + validate_air_absorbtion_hf(lReverb.flAirAbsorptionHF); + validate_hf_reference(lReverb.flHFReference); + validate_lf_reference(lReverb.flLFReference); + validate_room_rolloff_factor(lReverb.flRoomRolloffFactor); + validate_flags(lReverb.ulFlags); +} + +void EaxReverbEffect::defer_environment( + unsigned long ulEnvironment) +{ + eax_d_.ulEnvironment = ulEnvironment; + eax_dirty_flags_.ulEnvironment = (eax_.ulEnvironment != eax_d_.ulEnvironment); +} + +void EaxReverbEffect::defer_environment_size( + float flEnvironmentSize) +{ + eax_d_.flEnvironmentSize = flEnvironmentSize; + eax_dirty_flags_.flEnvironmentSize = (eax_.flEnvironmentSize != eax_d_.flEnvironmentSize); +} + +void EaxReverbEffect::defer_environment_diffusion( + float flEnvironmentDiffusion) +{ + eax_d_.flEnvironmentDiffusion = flEnvironmentDiffusion; + eax_dirty_flags_.flEnvironmentDiffusion = (eax_.flEnvironmentDiffusion != eax_d_.flEnvironmentDiffusion); +} + +void EaxReverbEffect::defer_room( + long lRoom) +{ + eax_d_.lRoom = lRoom; + eax_dirty_flags_.lRoom = (eax_.lRoom != eax_d_.lRoom); +} + +void EaxReverbEffect::defer_room_hf( + long lRoomHF) +{ + eax_d_.lRoomHF = lRoomHF; + eax_dirty_flags_.lRoomHF = (eax_.lRoomHF != eax_d_.lRoomHF); +} + +void EaxReverbEffect::defer_room_lf( + long lRoomLF) +{ + eax_d_.lRoomLF = lRoomLF; + eax_dirty_flags_.lRoomLF = (eax_.lRoomLF != eax_d_.lRoomLF); +} + +void EaxReverbEffect::defer_decay_time( + float flDecayTime) +{ + eax_d_.flDecayTime = flDecayTime; + eax_dirty_flags_.flDecayTime = (eax_.flDecayTime != eax_d_.flDecayTime); +} + +void EaxReverbEffect::defer_decay_hf_ratio( + float flDecayHFRatio) +{ + eax_d_.flDecayHFRatio = flDecayHFRatio; + eax_dirty_flags_.flDecayHFRatio = (eax_.flDecayHFRatio != eax_d_.flDecayHFRatio); +} + +void EaxReverbEffect::defer_decay_lf_ratio( + float flDecayLFRatio) +{ + eax_d_.flDecayLFRatio = flDecayLFRatio; + eax_dirty_flags_.flDecayLFRatio = (eax_.flDecayLFRatio != eax_d_.flDecayLFRatio); +} + +void EaxReverbEffect::defer_reflections( + long lReflections) +{ + eax_d_.lReflections = lReflections; + eax_dirty_flags_.lReflections = (eax_.lReflections != eax_d_.lReflections); +} + +void EaxReverbEffect::defer_reflections_delay( + float flReflectionsDelay) +{ + eax_d_.flReflectionsDelay = flReflectionsDelay; + eax_dirty_flags_.flReflectionsDelay = (eax_.flReflectionsDelay != eax_d_.flReflectionsDelay); +} + +void EaxReverbEffect::defer_reflections_pan( + const EAXVECTOR& vReflectionsPan) +{ + eax_d_.vReflectionsPan = vReflectionsPan; + eax_dirty_flags_.vReflectionsPan = (eax_.vReflectionsPan != eax_d_.vReflectionsPan); +} + +void EaxReverbEffect::defer_reverb( + long lReverb) +{ + eax_d_.lReverb = lReverb; + eax_dirty_flags_.lReverb = (eax_.lReverb != eax_d_.lReverb); +} + +void EaxReverbEffect::defer_reverb_delay( + float flReverbDelay) +{ + eax_d_.flReverbDelay = flReverbDelay; + eax_dirty_flags_.flReverbDelay = (eax_.flReverbDelay != eax_d_.flReverbDelay); +} + +void EaxReverbEffect::defer_reverb_pan( + const EAXVECTOR& vReverbPan) +{ + eax_d_.vReverbPan = vReverbPan; + eax_dirty_flags_.vReverbPan = (eax_.vReverbPan != eax_d_.vReverbPan); +} + +void EaxReverbEffect::defer_echo_time( + float flEchoTime) +{ + eax_d_.flEchoTime = flEchoTime; + eax_dirty_flags_.flEchoTime = (eax_.flEchoTime != eax_d_.flEchoTime); +} + +void EaxReverbEffect::defer_echo_depth( + float flEchoDepth) +{ + eax_d_.flEchoDepth = flEchoDepth; + eax_dirty_flags_.flEchoDepth = (eax_.flEchoDepth != eax_d_.flEchoDepth); +} + +void EaxReverbEffect::defer_modulation_time( + float flModulationTime) +{ + eax_d_.flModulationTime = flModulationTime; + eax_dirty_flags_.flModulationTime = (eax_.flModulationTime != eax_d_.flModulationTime); +} + +void EaxReverbEffect::defer_modulation_depth( + float flModulationDepth) +{ + eax_d_.flModulationDepth = flModulationDepth; + eax_dirty_flags_.flModulationDepth = (eax_.flModulationDepth != eax_d_.flModulationDepth); +} + +void EaxReverbEffect::defer_air_absorbtion_hf( + float flAirAbsorptionHF) +{ + eax_d_.flAirAbsorptionHF = flAirAbsorptionHF; + eax_dirty_flags_.flAirAbsorptionHF = (eax_.flAirAbsorptionHF != eax_d_.flAirAbsorptionHF); +} + +void EaxReverbEffect::defer_hf_reference( + float flHFReference) +{ + eax_d_.flHFReference = flHFReference; + eax_dirty_flags_.flHFReference = (eax_.flHFReference != eax_d_.flHFReference); +} + +void EaxReverbEffect::defer_lf_reference( + float flLFReference) +{ + eax_d_.flLFReference = flLFReference; + eax_dirty_flags_.flLFReference = (eax_.flLFReference != eax_d_.flLFReference); +} + +void EaxReverbEffect::defer_room_rolloff_factor( + float flRoomRolloffFactor) +{ + eax_d_.flRoomRolloffFactor = flRoomRolloffFactor; + eax_dirty_flags_.flRoomRolloffFactor = (eax_.flRoomRolloffFactor != eax_d_.flRoomRolloffFactor); +} + +void EaxReverbEffect::defer_flags( + unsigned long ulFlags) +{ + eax_d_.ulFlags = ulFlags; + eax_dirty_flags_.ulFlags = (eax_.ulFlags != eax_d_.ulFlags); +} + +void EaxReverbEffect::defer_all( + const EAX20LISTENERPROPERTIES& listener) +{ + defer_room(listener.lRoom); + defer_room_hf(listener.lRoomHF); + defer_room_rolloff_factor(listener.flRoomRolloffFactor); + defer_decay_time(listener.flDecayTime); + defer_decay_hf_ratio(listener.flDecayHFRatio); + defer_reflections(listener.lReflections); + defer_reflections_delay(listener.flReflectionsDelay); + defer_reverb(listener.lReverb); + defer_reverb_delay(listener.flReverbDelay); + defer_environment(listener.dwEnvironment); + defer_environment_size(listener.flEnvironmentSize); + defer_environment_diffusion(listener.flEnvironmentDiffusion); + defer_air_absorbtion_hf(listener.flAirAbsorptionHF); + defer_flags(listener.dwFlags); +} + +void EaxReverbEffect::defer_all( + const EAXREVERBPROPERTIES& lReverb) +{ + defer_environment(lReverb.ulEnvironment); + defer_environment_size(lReverb.flEnvironmentSize); + defer_environment_diffusion(lReverb.flEnvironmentDiffusion); + defer_room(lReverb.lRoom); + defer_room_hf(lReverb.lRoomHF); + defer_room_lf(lReverb.lRoomLF); + defer_decay_time(lReverb.flDecayTime); + defer_decay_hf_ratio(lReverb.flDecayHFRatio); + defer_decay_lf_ratio(lReverb.flDecayLFRatio); + defer_reflections(lReverb.lReflections); + defer_reflections_delay(lReverb.flReflectionsDelay); + defer_reflections_pan(lReverb.vReflectionsPan); + defer_reverb(lReverb.lReverb); + defer_reverb_delay(lReverb.flReverbDelay); + defer_reverb_pan(lReverb.vReverbPan); + defer_echo_time(lReverb.flEchoTime); + defer_echo_depth(lReverb.flEchoDepth); + defer_modulation_time(lReverb.flModulationTime); + defer_modulation_depth(lReverb.flModulationDepth); + defer_air_absorbtion_hf(lReverb.flAirAbsorptionHF); + defer_hf_reference(lReverb.flHFReference); + defer_lf_reference(lReverb.flLFReference); + defer_room_rolloff_factor(lReverb.flRoomRolloffFactor); + defer_flags(lReverb.ulFlags); +} + +void EaxReverbEffect::defer_environment( + const EaxEaxCall& eax_call) +{ + const auto& ulEnvironment = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::ulEnvironment)>(); + + validate_environment(ulEnvironment, eax_call.get_version(), true); + + if (eax_d_.ulEnvironment == ulEnvironment) + { + return; + } + + const auto& reverb_preset = EAXREVERB_PRESETS[ulEnvironment]; + + defer_all(reverb_preset); +} + +void EaxReverbEffect::defer_environment_size( + const EaxEaxCall& eax_call) +{ + const auto& flEnvironmentSize = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEnvironmentSize)>(); + + validate_environment_size(flEnvironmentSize); + + if (eax_d_.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / eax_d_.flEnvironmentSize; + + defer_environment_size(flEnvironmentSize); + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) + { + const auto flDecayTime = clamp( + scale * eax_d_.flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + + defer_decay_time(flDecayTime); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0) + { + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + const auto lReflections = clamp( + eax_d_.lReflections - static_cast<long>(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + + defer_reflections(lReflections); + } + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + const auto flReflectionsDelay = clamp( + eax_d_.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + + defer_reflections_delay(flReflectionsDelay); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((eax_d_.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + + const auto lReverb = clamp( + eax_d_.lReverb - static_cast<long>(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + + defer_reverb(lReverb); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) + { + const auto flReverbDelay = clamp( + scale * eax_d_.flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + + defer_reverb_delay(flReverbDelay); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) + { + const auto flEchoTime = clamp( + eax_d_.flEchoTime * scale, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); + + defer_echo_time(flEchoTime); + } + + if ((eax_d_.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) + { + const auto flModulationTime = clamp( + scale * eax_d_.flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); + + defer_modulation_time(flModulationTime); + } +} + +void EaxReverbEffect::defer_environment_diffusion( + const EaxEaxCall& eax_call) +{ + const auto& flEnvironmentDiffusion = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEnvironmentDiffusion)>(); + + validate_environment_diffusion(flEnvironmentDiffusion); + defer_environment_diffusion(flEnvironmentDiffusion); +} + +void EaxReverbEffect::defer_room( + const EaxEaxCall& eax_call) +{ + const auto& lRoom = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoom)>(); + + validate_room(lRoom); + defer_room(lRoom); +} + +void EaxReverbEffect::defer_room_hf( + const EaxEaxCall& eax_call) +{ + const auto& lRoomHF = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoomHF)>(); + + validate_room_hf(lRoomHF); + defer_room_hf(lRoomHF); +} + +void EaxReverbEffect::defer_room_lf( + const EaxEaxCall& eax_call) +{ + const auto& lRoomLF = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lRoomLF)>(); + + validate_room_lf(lRoomLF); + defer_room_lf(lRoomLF); +} + +void EaxReverbEffect::defer_decay_time( + const EaxEaxCall& eax_call) +{ + const auto& flDecayTime = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayTime)>(); + + validate_decay_time(flDecayTime); + defer_decay_time(flDecayTime); +} + +void EaxReverbEffect::defer_decay_hf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& flDecayHFRatio = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayHFRatio)>(); + + validate_decay_hf_ratio(flDecayHFRatio); + defer_decay_hf_ratio(flDecayHFRatio); +} + +void EaxReverbEffect::defer_decay_lf_ratio( + const EaxEaxCall& eax_call) +{ + const auto& flDecayLFRatio = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flDecayLFRatio)>(); + + validate_decay_lf_ratio(flDecayLFRatio); + defer_decay_lf_ratio(flDecayLFRatio); +} + +void EaxReverbEffect::defer_reflections( + const EaxEaxCall& eax_call) +{ + const auto& lReflections = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lReflections)>(); + + validate_reflections(lReflections); + defer_reflections(lReflections); +} + +void EaxReverbEffect::defer_reflections_delay( + const EaxEaxCall& eax_call) +{ + const auto& flReflectionsDelay = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flReflectionsDelay)>(); + + validate_reflections_delay(flReflectionsDelay); + defer_reflections_delay(flReflectionsDelay); +} + +void EaxReverbEffect::defer_reflections_pan( + const EaxEaxCall& eax_call) +{ + const auto& vReflectionsPan = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::vReflectionsPan)>(); + + validate_reflections_pan(vReflectionsPan); + defer_reflections_pan(vReflectionsPan); +} + +void EaxReverbEffect::defer_reverb( + const EaxEaxCall& eax_call) +{ + const auto& lReverb = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::lReverb)>(); + + validate_reverb(lReverb); + defer_reverb(lReverb); +} + +void EaxReverbEffect::defer_reverb_delay( + const EaxEaxCall& eax_call) +{ + const auto& flReverbDelay = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flReverbDelay)>(); + + validate_reverb_delay(flReverbDelay); + defer_reverb_delay(flReverbDelay); +} + +void EaxReverbEffect::defer_reverb_pan( + const EaxEaxCall& eax_call) +{ + const auto& vReverbPan = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::vReverbPan)>(); + + validate_reverb_pan(vReverbPan); + defer_reverb_pan(vReverbPan); +} + +void EaxReverbEffect::defer_echo_time( + const EaxEaxCall& eax_call) +{ + const auto& flEchoTime = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEchoTime)>(); + + validate_echo_time(flEchoTime); + defer_echo_time(flEchoTime); +} + +void EaxReverbEffect::defer_echo_depth( + const EaxEaxCall& eax_call) +{ + const auto& flEchoDepth = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flEchoDepth)>(); + + validate_echo_depth(flEchoDepth); + defer_echo_depth(flEchoDepth); +} + +void EaxReverbEffect::defer_modulation_time( + const EaxEaxCall& eax_call) +{ + const auto& flModulationTime = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flModulationTime)>(); + + validate_modulation_time(flModulationTime); + defer_modulation_time(flModulationTime); +} + +void EaxReverbEffect::defer_modulation_depth( + const EaxEaxCall& eax_call) +{ + const auto& flModulationDepth = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flModulationDepth)>(); + + validate_modulation_depth(flModulationDepth); + defer_modulation_depth(flModulationDepth); +} + +void EaxReverbEffect::defer_air_absorbtion_hf( + const EaxEaxCall& eax_call) +{ + const auto& air_absorbtion_hf = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flAirAbsorptionHF)>(); + + validate_air_absorbtion_hf(air_absorbtion_hf); + defer_air_absorbtion_hf(air_absorbtion_hf); +} + +void EaxReverbEffect::defer_hf_reference( + const EaxEaxCall& eax_call) +{ + const auto& flHFReference = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flHFReference)>(); + + validate_hf_reference(flHFReference); + defer_hf_reference(flHFReference); +} + +void EaxReverbEffect::defer_lf_reference( + const EaxEaxCall& eax_call) +{ + const auto& flLFReference = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flLFReference)>(); + + validate_lf_reference(flLFReference); + defer_lf_reference(flLFReference); +} + +void EaxReverbEffect::defer_room_rolloff_factor( + const EaxEaxCall& eax_call) +{ + const auto& flRoomRolloffFactor = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::flRoomRolloffFactor)>(); + + validate_room_rolloff_factor(flRoomRolloffFactor); + defer_room_rolloff_factor(flRoomRolloffFactor); +} + +void EaxReverbEffect::defer_flags( + const EaxEaxCall& eax_call) +{ + const auto& ulFlags = + eax_call.get_value<EaxReverbEffectException, const decltype(EAXREVERBPROPERTIES::ulFlags)>(); + + validate_flags(ulFlags); + defer_flags(ulFlags); +} + +void EaxReverbEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto eax_version = eax_call.get_version(); + + if (eax_version == 2) + { + const auto& listener = + eax_call.get_value<EaxReverbEffectException, const EAX20LISTENERPROPERTIES>(); + + validate_all(listener, eax_version); + defer_all(listener); + } + else + { + const auto& reverb_all = + eax_call.get_value<EaxReverbEffectException, const EAXREVERBPROPERTIES>(); + + validate_all(reverb_all, eax_version); + defer_all(reverb_all); + } +} + +// [[nodiscard]] +bool EaxReverbEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxReverbEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulEnvironment) + { + } + + if (eax_dirty_flags_.flEnvironmentSize) + { + set_efx_density(); + } + + if (eax_dirty_flags_.flEnvironmentDiffusion) + { + set_efx_diffusion(); + } + + if (eax_dirty_flags_.lRoom) + { + set_efx_gain(); + } + + if (eax_dirty_flags_.lRoomHF) + { + set_efx_gain_hf(); + } + + if (eax_dirty_flags_.lRoomLF) + { + set_efx_gain_lf(); + } + + if (eax_dirty_flags_.flDecayTime) + { + set_efx_decay_time(); + } + + if (eax_dirty_flags_.flDecayHFRatio) + { + set_efx_decay_hf_ratio(); + } + + if (eax_dirty_flags_.flDecayLFRatio) + { + set_efx_decay_lf_ratio(); + } + + if (eax_dirty_flags_.lReflections) + { + set_efx_reflections_gain(); + } + + if (eax_dirty_flags_.flReflectionsDelay) + { + set_efx_reflections_delay(); + } + + if (eax_dirty_flags_.vReflectionsPan) + { + set_efx_reflections_pan(); + } + + if (eax_dirty_flags_.lReverb) + { + set_efx_late_reverb_gain(); + } + + if (eax_dirty_flags_.flReverbDelay) + { + set_efx_late_reverb_delay(); + } + + if (eax_dirty_flags_.vReverbPan) + { + set_efx_late_reverb_pan(); + } + + if (eax_dirty_flags_.flEchoTime) + { + set_efx_echo_time(); + } + + if (eax_dirty_flags_.flEchoDepth) + { + set_efx_echo_depth(); + } + + if (eax_dirty_flags_.flModulationTime) + { + set_efx_modulation_time(); + } + + if (eax_dirty_flags_.flModulationDepth) + { + set_efx_modulation_depth(); + } + + if (eax_dirty_flags_.flAirAbsorptionHF) + { + set_efx_air_absorption_gain_hf(); + } + + if (eax_dirty_flags_.flHFReference) + { + set_efx_hf_reference(); + } + + if (eax_dirty_flags_.flLFReference) + { + set_efx_lf_reference(); + } + + if (eax_dirty_flags_.flRoomRolloffFactor) + { + set_efx_room_rolloff_factor(); + } + + if (eax_dirty_flags_.ulFlags) + { + set_efx_flags(); + } + + eax_dirty_flags_ = EaxReverbEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxReverbEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXREVERB_ENVIRONMENT: + defer_environment(eax_call); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + defer_environment_size(eax_call); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + defer_environment_diffusion(eax_call); + break; + + case EAXREVERB_ROOM: + defer_room(eax_call); + break; + + case EAXREVERB_ROOMHF: + defer_room_hf(eax_call); + break; + + case EAXREVERB_ROOMLF: + defer_room_lf(eax_call); + break; + + case EAXREVERB_DECAYTIME: + defer_decay_time(eax_call); + break; + + case EAXREVERB_DECAYHFRATIO: + defer_decay_hf_ratio(eax_call); + break; + + case EAXREVERB_DECAYLFRATIO: + defer_decay_lf_ratio(eax_call); + break; + + case EAXREVERB_REFLECTIONS: + defer_reflections(eax_call); + break; + + case EAXREVERB_REFLECTIONSDELAY: + defer_reflections_delay(eax_call); + break; + + case EAXREVERB_REFLECTIONSPAN: + defer_reflections_pan(eax_call); + break; + + case EAXREVERB_REVERB: + defer_reverb(eax_call); + break; + + case EAXREVERB_REVERBDELAY: + defer_reverb_delay(eax_call); + break; + + case EAXREVERB_REVERBPAN: + defer_reverb_pan(eax_call); + break; + + case EAXREVERB_ECHOTIME: + defer_echo_time(eax_call); + break; + + case EAXREVERB_ECHODEPTH: + defer_echo_depth(eax_call); + break; + + case EAXREVERB_MODULATIONTIME: + defer_modulation_time(eax_call); + break; + + case EAXREVERB_MODULATIONDEPTH: + defer_modulation_depth(eax_call); + break; + + case EAXREVERB_AIRABSORPTIONHF: + defer_air_absorbtion_hf(eax_call); + break; + + case EAXREVERB_HFREFERENCE: + defer_hf_reference(eax_call); + break; + + case EAXREVERB_LFREFERENCE: + defer_lf_reference(eax_call); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + defer_room_rolloff_factor(eax_call); + break; + + case EAXREVERB_FLAGS: + defer_flags(eax_call); + break; + + default: + throw EaxReverbEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_reverb_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxReverbEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX diff --git a/al/effects/vmorpher.cpp b/al/effects/vmorpher.cpp index 1b4710ff..2a9e0702 100644 --- a/al/effects/vmorpher.cpp +++ b/al/effects/vmorpher.cpp @@ -10,6 +10,15 @@ #include "aloptional.h" #include "effects.h" +#if ALSOFT_EAX +#include <cassert> + +#include "alnumeric.h" + +#include "al/eax_exception.h" +#include "al/eax_utils.h" +#endif // ALSOFT_EAX + namespace { @@ -247,3 +256,618 @@ EffectProps genDefaultProps() noexcept DEFINE_ALEFFECT_VTABLE(Vmorpher); const EffectProps VmorpherEffectProps{genDefaultProps()}; + +#if ALSOFT_EAX +namespace +{ + + +using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t; + +struct EaxVocalMorpherEffectDirtyFlags +{ + using EaxIsBitFieldStruct = bool; + + EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1; + EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1; + EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1; + EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1; + EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1; + EaxVocalMorpherEffectDirtyFlagsValue flRate : 1; +}; // EaxPitchShifterEffectDirtyFlags + + +class EaxVocalMorpherEffect final : + public EaxEffect +{ +public: + EaxVocalMorpherEffect( + EffectProps& al_effect_props); + + + // [[nodiscard]] + bool dispatch( + const EaxEaxCall& eax_call) override; + + +private: + EffectProps& al_effect_props_; + + EAXVOCALMORPHERPROPERTIES eax_{}; + EAXVOCALMORPHERPROPERTIES eax_d_{}; + EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{}; + + + void set_eax_defaults(); + + + void set_efx_phoneme_a(); + + void set_efx_phoneme_a_coarse_tuning(); + + void set_efx_phoneme_b(); + + void set_efx_phoneme_b_coarse_tuning(); + + void set_efx_waveform(); + + void set_efx_rate(); + + void set_efx_defaults(); + + + // [[nodiscard]] + bool get( + const EaxEaxCall& eax_call); + + + void validate_phoneme_a( + unsigned long ulPhonemeA); + + void validate_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning); + + void validate_phoneme_b( + unsigned long ulPhonemeB); + + void validate_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning); + + void validate_waveform( + unsigned long ulWaveform); + + void validate_rate( + float flRate); + + void validate_all( + const EAXVOCALMORPHERPROPERTIES& all); + + + void defer_phoneme_a( + unsigned long ulPhonemeA); + + void defer_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning); + + void defer_phoneme_b( + unsigned long ulPhonemeB); + + void defer_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning); + + void defer_waveform( + unsigned long ulWaveform); + + void defer_rate( + float flRate); + + void defer_all( + const EAXVOCALMORPHERPROPERTIES& all); + + + void defer_phoneme_a( + const EaxEaxCall& eax_call); + + void defer_phoneme_a_coarse_tuning( + const EaxEaxCall& eax_call); + + void defer_phoneme_b( + const EaxEaxCall& eax_call); + + void defer_phoneme_b_coarse_tuning( + const EaxEaxCall& eax_call); + + void defer_waveform( + const EaxEaxCall& eax_call); + + void defer_rate( + const EaxEaxCall& eax_call); + + void defer_all( + const EaxEaxCall& eax_call); + + + // [[nodiscard]] + bool apply_deferred(); + + // [[nodiscard]] + bool set( + const EaxEaxCall& eax_call); +}; // EaxVocalMorpherEffect + + +class EaxVocalMorpherEffectException : + public EaxException +{ +public: + explicit EaxVocalMorpherEffectException( + const char* message) + : + EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} + { + } +}; // EaxVocalMorpherEffectException + + +EaxVocalMorpherEffect::EaxVocalMorpherEffect( + EffectProps& al_effect_props) + : + al_effect_props_{al_effect_props} +{ + set_eax_defaults(); + set_efx_defaults(); +} + +// [[nodiscard]] +bool EaxVocalMorpherEffect::dispatch( + const EaxEaxCall& eax_call) +{ + return eax_call.is_get() ? get(eax_call) : set(eax_call); +} + +void EaxVocalMorpherEffect::set_eax_defaults() +{ + eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; + eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; + eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; + eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; + eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; + eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE; + + eax_d_ = eax_; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_a() +{ + const auto phoneme_a = clamp( + static_cast<ALint>(eax_.ulPhonemeA), + AL_VOCAL_MORPHER_MIN_PHONEMEA, + AL_VOCAL_MORPHER_MAX_PHONEMEA); + + const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a); + assert(efx_phoneme_a.has_value()); + al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning() +{ + const auto phoneme_a_coarse_tuning = clamp( + static_cast<ALint>(eax_.lPhonemeACoarseTuning), + AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING, + AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING); + + al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_b() +{ + const auto phoneme_b = clamp( + static_cast<ALint>(eax_.ulPhonemeB), + AL_VOCAL_MORPHER_MIN_PHONEMEB, + AL_VOCAL_MORPHER_MAX_PHONEMEB); + + const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b); + assert(efx_phoneme_b.has_value()); + al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b; +} + +void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning() +{ + const auto phoneme_b_coarse_tuning = clamp( + static_cast<ALint>(eax_.lPhonemeBCoarseTuning), + AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING, + AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING); + + al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning; +} + +void EaxVocalMorpherEffect::set_efx_waveform() +{ + const auto waveform = clamp( + static_cast<ALint>(eax_.ulWaveform), + AL_VOCAL_MORPHER_MIN_WAVEFORM, + AL_VOCAL_MORPHER_MAX_WAVEFORM); + + const auto wfx_waveform = WaveformFromEmum(waveform); + assert(wfx_waveform.has_value()); + al_effect_props_.Vmorpher.Waveform = *wfx_waveform; +} + +void EaxVocalMorpherEffect::set_efx_rate() +{ + const auto rate = clamp( + eax_.flRate, + AL_VOCAL_MORPHER_MIN_RATE, + AL_VOCAL_MORPHER_MAX_RATE); + + al_effect_props_.Vmorpher.Rate = rate; +} + +void EaxVocalMorpherEffect::set_efx_defaults() +{ + set_efx_phoneme_a(); + set_efx_phoneme_a_coarse_tuning(); + set_efx_phoneme_b(); + set_efx_phoneme_b_coarse_tuning(); + set_efx_waveform(); + set_efx_rate(); +} + +// [[nodiscard]] +bool EaxVocalMorpherEffect::get( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_); + break; + + case EAXVOCALMORPHER_PHONEMEA: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeA); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeACoarseTuning); + break; + + case EAXVOCALMORPHER_PHONEMEB: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeB); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeBCoarseTuning); + break; + + case EAXVOCALMORPHER_WAVEFORM: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulWaveform); + break; + + case EAXVOCALMORPHER_RATE: + eax_call.set_value<EaxVocalMorpherEffectException>(eax_.flRate); + break; + + default: + throw EaxVocalMorpherEffectException{"Unsupported property id."}; + } + + return false; +} + +void EaxVocalMorpherEffect::validate_phoneme_a( + unsigned long ulPhonemeA) +{ + eax_validate_range<EaxVocalMorpherEffectException>( + "Phoneme A", + ulPhonemeA, + EAXVOCALMORPHER_MINPHONEMEA, + EAXVOCALMORPHER_MAXPHONEMEA); +} + +void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning) +{ + eax_validate_range<EaxVocalMorpherEffectException>( + "Phoneme A Coarse Tuning", + lPhonemeACoarseTuning, + EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); +} + +void EaxVocalMorpherEffect::validate_phoneme_b( + unsigned long ulPhonemeB) +{ + eax_validate_range<EaxVocalMorpherEffectException>( + "Phoneme B", + ulPhonemeB, + EAXVOCALMORPHER_MINPHONEMEB, + EAXVOCALMORPHER_MAXPHONEMEB); +} + +void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning) +{ + eax_validate_range<EaxVocalMorpherEffectException>( + "Phoneme B Coarse Tuning", + lPhonemeBCoarseTuning, + EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); +} + +void EaxVocalMorpherEffect::validate_waveform( + unsigned long ulWaveform) +{ + eax_validate_range<EaxVocalMorpherEffectException>( + "Waveform", + ulWaveform, + EAXVOCALMORPHER_MINWAVEFORM, + EAXVOCALMORPHER_MAXWAVEFORM); +} + +void EaxVocalMorpherEffect::validate_rate( + float flRate) +{ + eax_validate_range<EaxVocalMorpherEffectException>( + "Rate", + flRate, + EAXVOCALMORPHER_MINRATE, + EAXVOCALMORPHER_MAXRATE); +} + +void EaxVocalMorpherEffect::validate_all( + const EAXVOCALMORPHERPROPERTIES& all) +{ + validate_phoneme_a(all.ulPhonemeA); + validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); + validate_phoneme_b(all.ulPhonemeB); + validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); + validate_waveform(all.ulWaveform); + validate_rate(all.flRate); +} + +void EaxVocalMorpherEffect::defer_phoneme_a( + unsigned long ulPhonemeA) +{ + eax_d_.ulPhonemeA = ulPhonemeA; + eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA); +} + +void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( + long lPhonemeACoarseTuning) +{ + eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning; + eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning); +} + +void EaxVocalMorpherEffect::defer_phoneme_b( + unsigned long ulPhonemeB) +{ + eax_d_.ulPhonemeB = ulPhonemeB; + eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB); +} + +void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( + long lPhonemeBCoarseTuning) +{ + eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning; + eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning); +} + +void EaxVocalMorpherEffect::defer_waveform( + unsigned long ulWaveform) +{ + eax_d_.ulWaveform = ulWaveform; + eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform); +} + +void EaxVocalMorpherEffect::defer_rate( + float flRate) +{ + eax_d_.flRate = flRate; + eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate); +} + +void EaxVocalMorpherEffect::defer_all( + const EAXVOCALMORPHERPROPERTIES& all) +{ + defer_phoneme_a(all.ulPhonemeA); + defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning); + defer_phoneme_b(all.ulPhonemeB); + defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning); + defer_waveform(all.ulWaveform); + defer_rate(all.flRate); +} + +void EaxVocalMorpherEffect::defer_phoneme_a( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_a = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeA) + >(); + + validate_phoneme_a(phoneme_a); + defer_phoneme_a(phoneme_a); +} + +void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_a_coarse_tuning = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning) + >(); + + validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); + defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning); +} + +void EaxVocalMorpherEffect::defer_phoneme_b( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_b = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB) + >(); + + validate_phoneme_b(phoneme_b); + defer_phoneme_b(phoneme_b); +} + +void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning( + const EaxEaxCall& eax_call) +{ + const auto& phoneme_b_coarse_tuning = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning) + >(); + + validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); + defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning); +} + +void EaxVocalMorpherEffect::defer_waveform( + const EaxEaxCall& eax_call) +{ + const auto& waveform = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform) + >(); + + validate_waveform(waveform); + defer_waveform(waveform); +} + +void EaxVocalMorpherEffect::defer_rate( + const EaxEaxCall& eax_call) +{ + const auto& rate = eax_call.get_value< + EaxVocalMorpherEffectException, + const decltype(EAXVOCALMORPHERPROPERTIES::flRate) + >(); + + validate_rate(rate); + defer_rate(rate); +} + +void EaxVocalMorpherEffect::defer_all( + const EaxEaxCall& eax_call) +{ + const auto& all = eax_call.get_value< + EaxVocalMorpherEffectException, + const EAXVOCALMORPHERPROPERTIES + >(); + + validate_all(all); + defer_all(all); +} + +// [[nodiscard]] +bool EaxVocalMorpherEffect::apply_deferred() +{ + if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{}) + { + return false; + } + + eax_ = eax_d_; + + if (eax_dirty_flags_.ulPhonemeA) + { + set_efx_phoneme_a(); + } + + if (eax_dirty_flags_.lPhonemeACoarseTuning) + { + set_efx_phoneme_a_coarse_tuning(); + } + + if (eax_dirty_flags_.ulPhonemeB) + { + set_efx_phoneme_b(); + } + + if (eax_dirty_flags_.lPhonemeBCoarseTuning) + { + set_efx_phoneme_b_coarse_tuning(); + } + + if (eax_dirty_flags_.ulWaveform) + { + set_efx_waveform(); + } + + if (eax_dirty_flags_.flRate) + { + set_efx_rate(); + } + + eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{}; + + return true; +} + +// [[nodiscard]] +bool EaxVocalMorpherEffect::set( + const EaxEaxCall& eax_call) +{ + switch (eax_call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + defer_all(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEA: + defer_phoneme_a(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + defer_phoneme_a_coarse_tuning(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEB: + defer_phoneme_b(eax_call); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + defer_phoneme_b_coarse_tuning(eax_call); + break; + + case EAXVOCALMORPHER_WAVEFORM: + defer_waveform(eax_call); + break; + + case EAXVOCALMORPHER_RATE: + defer_rate(eax_call); + break; + + default: + throw EaxVocalMorpherEffectException{"Unsupported property id."}; + } + + if (!eax_call.is_deferred()) + { + return apply_deferred(); + } + + return false; +} + + +} // namespace + + +EaxEffectUPtr eax_create_eax_vocal_morpher_effect( + EffectProps& al_effect_props) +{ + return std::make_unique<EaxVocalMorpherEffect>(al_effect_props); +} + + +#endif // ALSOFT_EAX |