diff options
author | Sven Gothel <[email protected]> | 2023-05-03 16:17:49 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-05-03 16:17:49 +0200 |
commit | ec167fd05661a5b02dd406c87081f84a0f8dd77d (patch) | |
tree | 9c4669e471c9969bda59265381b18d2d416db060 /al/effects | |
parent | 0d14d30808cfe7b9e3413353e3eef8a0f201399a (diff) | |
parent | d3875f333fb6abe2f39d82caca329414871ae53b (diff) |
Merge branch 'v1.23.1'
Resolved Conflicts:
CMakeLists.txt
Diffstat (limited to 'al/effects')
-rw-r--r-- | al/effects/autowah.cpp | 252 | ||||
-rw-r--r-- | al/effects/chorus.cpp | 724 | ||||
-rw-r--r-- | al/effects/compressor.cpp | 162 | ||||
-rw-r--r-- | al/effects/convolution.cpp | 93 | ||||
-rw-r--r-- | al/effects/dedicated.cpp | 72 | ||||
-rw-r--r-- | al/effects/distortion.cpp | 271 | ||||
-rw-r--r-- | al/effects/echo.cpp | 268 | ||||
-rw-r--r-- | al/effects/effects.cpp | 9 | ||||
-rw-r--r-- | al/effects/effects.h | 88 | ||||
-rw-r--r-- | al/effects/equalizer.cpp | 411 | ||||
-rw-r--r-- | al/effects/fshifter.cpp | 264 | ||||
-rw-r--r-- | al/effects/modulator.cpp | 272 | ||||
-rw-r--r-- | al/effects/null.cpp | 149 | ||||
-rw-r--r-- | al/effects/pshifter.cpp | 191 | ||||
-rw-r--r-- | al/effects/reverb.cpp | 1499 | ||||
-rw-r--r-- | al/effects/vmorpher.cpp | 520 |
16 files changed, 5245 insertions, 0 deletions
diff --git a/al/effects/autowah.cpp b/al/effects/autowah.cpp new file mode 100644 index 00000000..129318f4 --- /dev/null +++ b/al/effects/autowah.cpp @@ -0,0 +1,252 @@ + +#include "config.h" + +#include <cmath> +#include <cstdlib> + +#include <algorithm> + +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef 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) +{ + switch(param) + { + case AL_AUTOWAH_ATTACK_TIME: + if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) + throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; + props->Autowah.AttackTime = val; + break; + + case AL_AUTOWAH_RELEASE_TIME: + if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) + throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; + props->Autowah.ReleaseTime = val; + break; + + case AL_AUTOWAH_RESONANCE: + if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) + throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; + props->Autowah.Resonance = val; + break; + + case AL_AUTOWAH_PEAK_GAIN: + if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; + props->Autowah.PeakGain = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + } +} +void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Autowah_setParamf(props, param, vals[0]); } + +void Autowah_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void Autowah_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +void Autowah_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_AUTOWAH_ATTACK_TIME: + *val = props->Autowah.AttackTime; + break; + + case AL_AUTOWAH_RELEASE_TIME: + *val = props->Autowah.ReleaseTime; + break; + + case AL_AUTOWAH_RESONANCE: + *val = props->Autowah.Resonance; + break; + + case AL_AUTOWAH_PEAK_GAIN: + *val = props->Autowah.PeakGain; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; + } + +} +void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Autowah_getParamf(props, param, vals); } + +void Autowah_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void Autowah_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; + props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; + props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; + props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Autowah); + +const EffectProps AutowahEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using AutowahCommitter = EaxCommitter<EaxAutowahCommitter>; + +struct AttackTimeValidator { + void operator()(float flAttackTime) const + { + eax_validate_range<AutowahCommitter::Exception>( + "Attack Time", + flAttackTime, + EAXAUTOWAH_MINATTACKTIME, + EAXAUTOWAH_MAXATTACKTIME); + } +}; // AttackTimeValidator + +struct ReleaseTimeValidator { + void operator()(float flReleaseTime) const + { + eax_validate_range<AutowahCommitter::Exception>( + "Release Time", + flReleaseTime, + EAXAUTOWAH_MINRELEASETIME, + EAXAUTOWAH_MAXRELEASETIME); + } +}; // ReleaseTimeValidator + +struct ResonanceValidator { + void operator()(long lResonance) const + { + eax_validate_range<AutowahCommitter::Exception>( + "Resonance", + lResonance, + EAXAUTOWAH_MINRESONANCE, + EAXAUTOWAH_MAXRESONANCE); + } +}; // ResonanceValidator + +struct PeakLevelValidator { + void operator()(long lPeakLevel) const + { + eax_validate_range<AutowahCommitter::Exception>( + "Peak Level", + lPeakLevel, + EAXAUTOWAH_MINPEAKLEVEL, + EAXAUTOWAH_MAXPEAKLEVEL); + } +}; // PeakLevelValidator + +struct AllValidator { + void operator()(const EAXAUTOWAHPROPERTIES& all) const + { + AttackTimeValidator{}(all.flAttackTime); + ReleaseTimeValidator{}(all.flReleaseTime); + ResonanceValidator{}(all.lResonance); + PeakLevelValidator{}(all.lPeakLevel); + } +}; // AllValidator + +} // namespace + +template<> +struct AutowahCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_AUTOWAH_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void AutowahCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool AutowahCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && mEaxProps.mAutowah.flAttackTime == props.mAutowah.flAttackTime + && mEaxProps.mAutowah.flReleaseTime == props.mAutowah.flReleaseTime + && mEaxProps.mAutowah.lResonance == props.mAutowah.lResonance + && mEaxProps.mAutowah.lPeakLevel == props.mAutowah.lPeakLevel) + return false; + + mEaxProps = props; + + mAlProps.Autowah.AttackTime = props.mAutowah.flAttackTime; + mAlProps.Autowah.ReleaseTime = props.mAutowah.flReleaseTime; + mAlProps.Autowah.Resonance = level_mb_to_gain(static_cast<float>(props.mAutowah.lResonance)); + mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast<float>(props.mAutowah.lPeakLevel)); + + return true; +} + +template<> +void AutowahCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Autowah; + props.mAutowah.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + props.mAutowah.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + props.mAutowah.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + props.mAutowah.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; +} + +template<> +void AutowahCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXAUTOWAH_NONE: break; + case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props.mAutowah); break; + case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.mAutowah.flAttackTime); break; + case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.mAutowah.flReleaseTime); break; + case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.mAutowah.lResonance); break; + case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.mAutowah.lPeakLevel); break; + default: fail_unknown_property_id(); + } +} + +template<> +void AutowahCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXAUTOWAH_NONE: break; + case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props.mAutowah); break; + case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.mAutowah.flAttackTime); break; + case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.mAutowah.flReleaseTime); break; + case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.mAutowah.lResonance); break; + case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.mAutowah.lPeakLevel); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/chorus.cpp b/al/effects/chorus.cpp new file mode 100644 index 00000000..305259a4 --- /dev/null +++ b/al/effects/chorus.cpp @@ -0,0 +1,724 @@ + +#include "config.h" + +#include <stdexcept> + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "core/logging.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include <cassert> +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +static_assert(ChorusMaxDelay >= AL_CHORUS_MAX_DELAY, "Chorus max delay too small"); +static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too small"); + +static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); +static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); + +inline al::optional<ChorusWaveform> WaveformFromEnum(ALenum type) +{ + switch(type) + { + case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid; + case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle; + } + return al::nullopt; +} +inline ALenum EnumFromWaveform(ChorusWaveform type) +{ + switch(type) + { + case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID; + case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE; + } + throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast<int>(type))}; +} + +void Chorus_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_CHORUS_WAVEFORM: + if(auto formopt = WaveformFromEnum(val)) + props->Chorus.Waveform = *formopt; + else + throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; + break; + + case AL_CHORUS_PHASE: + if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) + throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; + props->Chorus.Phase = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + } +} +void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Chorus_setParami(props, param, vals[0]); } +void Chorus_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_CHORUS_RATE: + if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) + throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; + props->Chorus.Rate = val; + break; + + case AL_CHORUS_DEPTH: + if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; + props->Chorus.Depth = val; + break; + + case AL_CHORUS_FEEDBACK: + if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) + throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; + props->Chorus.Feedback = val; + break; + + case AL_CHORUS_DELAY: + if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; + props->Chorus.Delay = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + } +} +void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Chorus_setParamf(props, param, vals[0]); } + +void Chorus_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_CHORUS_WAVEFORM: + *val = EnumFromWaveform(props->Chorus.Waveform); + break; + + case AL_CHORUS_PHASE: + *val = props->Chorus.Phase; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; + } +} +void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Chorus_getParami(props, param, vals); } +void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_CHORUS_RATE: + *val = props->Chorus.Rate; + break; + + case AL_CHORUS_DEPTH: + *val = props->Chorus.Depth; + break; + + case AL_CHORUS_FEEDBACK: + *val = props->Chorus.Feedback; + break; + + case AL_CHORUS_DELAY: + *val = props->Chorus.Delay; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; + } +} +void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Chorus_getParamf(props, param, vals); } + +EffectProps genDefaultChorusProps() noexcept +{ + EffectProps props{}; + props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM); + props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; + props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; + props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; + props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; + props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; + return props; +} + + +void Flanger_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_FLANGER_WAVEFORM: + if(auto formopt = WaveformFromEnum(val)) + props->Chorus.Waveform = *formopt; + else + throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; + break; + + case AL_FLANGER_PHASE: + if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) + throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; + props->Chorus.Phase = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + } +} +void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Flanger_setParami(props, param, vals[0]); } +void Flanger_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_FLANGER_RATE: + if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) + throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; + props->Chorus.Rate = val; + break; + + case AL_FLANGER_DEPTH: + if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; + props->Chorus.Depth = val; + break; + + case AL_FLANGER_FEEDBACK: + if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) + throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; + props->Chorus.Feedback = val; + break; + + case AL_FLANGER_DELAY: + if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; + props->Chorus.Delay = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + } +} +void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Flanger_setParamf(props, param, vals[0]); } + +void Flanger_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_FLANGER_WAVEFORM: + *val = EnumFromWaveform(props->Chorus.Waveform); + break; + + case AL_FLANGER_PHASE: + *val = props->Chorus.Phase; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; + } +} +void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Flanger_getParami(props, param, vals); } +void Flanger_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_FLANGER_RATE: + *val = props->Chorus.Rate; + break; + + case AL_FLANGER_DEPTH: + *val = props->Chorus.Depth; + break; + + case AL_FLANGER_FEEDBACK: + *val = props->Chorus.Feedback; + break; + + case AL_FLANGER_DELAY: + *val = props->Chorus.Delay; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; + } +} +void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Flanger_getParamf(props, param, vals); } + +EffectProps genDefaultFlangerProps() noexcept +{ + EffectProps props{}; + props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM); + props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; + props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; + props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; + props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; + props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Chorus); + +const EffectProps ChorusEffectProps{genDefaultChorusProps()}; + +DEFINE_ALEFFECT_VTABLE(Flanger); + +const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; + + +#ifdef ALSOFT_EAX +namespace { + +struct EaxChorusTraits { + using Props = EAXCHORUSPROPERTIES; + using Committer = EaxChorusCommitter; + static constexpr auto Field = &EaxEffectProps::mChorus; + + static constexpr auto eax_effect_type() { return EaxEffectType::Chorus; } + static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; } + + static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; } + static constexpr auto eax_allparameters_param_id() { return EAXCHORUS_ALLPARAMETERS; } + static constexpr auto eax_waveform_param_id() { return EAXCHORUS_WAVEFORM; } + static constexpr auto eax_phase_param_id() { return EAXCHORUS_PHASE; } + static constexpr auto eax_rate_param_id() { return EAXCHORUS_RATE; } + static constexpr auto eax_depth_param_id() { return EAXCHORUS_DEPTH; } + static constexpr auto eax_feedback_param_id() { return EAXCHORUS_FEEDBACK; } + static constexpr auto eax_delay_param_id() { return EAXCHORUS_DELAY; } + + static constexpr auto eax_min_waveform() { return EAXCHORUS_MINWAVEFORM; } + static constexpr auto eax_min_phase() { return EAXCHORUS_MINPHASE; } + static constexpr auto eax_min_rate() { return EAXCHORUS_MINRATE; } + static constexpr auto eax_min_depth() { return EAXCHORUS_MINDEPTH; } + static constexpr auto eax_min_feedback() { return EAXCHORUS_MINFEEDBACK; } + static constexpr auto eax_min_delay() { return EAXCHORUS_MINDELAY; } + + static constexpr auto eax_max_waveform() { return EAXCHORUS_MAXWAVEFORM; } + static constexpr auto eax_max_phase() { return EAXCHORUS_MAXPHASE; } + static constexpr auto eax_max_rate() { return EAXCHORUS_MAXRATE; } + static constexpr auto eax_max_depth() { return EAXCHORUS_MAXDEPTH; } + static constexpr auto eax_max_feedback() { return EAXCHORUS_MAXFEEDBACK; } + static constexpr auto eax_max_delay() { return EAXCHORUS_MAXDELAY; } + + static constexpr auto eax_default_waveform() { return EAXCHORUS_DEFAULTWAVEFORM; } + static constexpr auto eax_default_phase() { return EAXCHORUS_DEFAULTPHASE; } + static constexpr auto eax_default_rate() { return EAXCHORUS_DEFAULTRATE; } + static constexpr auto eax_default_depth() { return EAXCHORUS_DEFAULTDEPTH; } + static constexpr auto eax_default_feedback() { return EAXCHORUS_DEFAULTFEEDBACK; } + static constexpr auto eax_default_delay() { return EAXCHORUS_DEFAULTDELAY; } + + static constexpr auto efx_min_waveform() { return AL_CHORUS_MIN_WAVEFORM; } + static constexpr auto efx_min_phase() { return AL_CHORUS_MIN_PHASE; } + static constexpr auto efx_min_rate() { return AL_CHORUS_MIN_RATE; } + static constexpr auto efx_min_depth() { return AL_CHORUS_MIN_DEPTH; } + static constexpr auto efx_min_feedback() { return AL_CHORUS_MIN_FEEDBACK; } + static constexpr auto efx_min_delay() { return AL_CHORUS_MIN_DELAY; } + + static constexpr auto efx_max_waveform() { return AL_CHORUS_MAX_WAVEFORM; } + static constexpr auto efx_max_phase() { return AL_CHORUS_MAX_PHASE; } + static constexpr auto efx_max_rate() { return AL_CHORUS_MAX_RATE; } + static constexpr auto efx_max_depth() { return AL_CHORUS_MAX_DEPTH; } + static constexpr auto efx_max_feedback() { return AL_CHORUS_MAX_FEEDBACK; } + static constexpr auto efx_max_delay() { return AL_CHORUS_MAX_DELAY; } + + static constexpr auto efx_default_waveform() { return AL_CHORUS_DEFAULT_WAVEFORM; } + static constexpr auto efx_default_phase() { return AL_CHORUS_DEFAULT_PHASE; } + static constexpr auto efx_default_rate() { return AL_CHORUS_DEFAULT_RATE; } + static constexpr auto efx_default_depth() { return AL_CHORUS_DEFAULT_DEPTH; } + static constexpr auto efx_default_feedback() { return AL_CHORUS_DEFAULT_FEEDBACK; } + static constexpr auto efx_default_delay() { return AL_CHORUS_DEFAULT_DELAY; } + + static ChorusWaveform eax_waveform(unsigned long type) + { + if(type == EAX_CHORUS_SINUSOID) return ChorusWaveform::Sinusoid; + if(type == EAX_CHORUS_TRIANGLE) return ChorusWaveform::Triangle; + return ChorusWaveform::Sinusoid; + } +}; // EaxChorusTraits + +struct EaxFlangerTraits { + using Props = EAXFLANGERPROPERTIES; + using Committer = EaxFlangerCommitter; + static constexpr auto Field = &EaxEffectProps::mFlanger; + + static constexpr auto eax_effect_type() { return EaxEffectType::Flanger; } + static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; } + + static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; } + static constexpr auto eax_allparameters_param_id() { return EAXFLANGER_ALLPARAMETERS; } + static constexpr auto eax_waveform_param_id() { return EAXFLANGER_WAVEFORM; } + static constexpr auto eax_phase_param_id() { return EAXFLANGER_PHASE; } + static constexpr auto eax_rate_param_id() { return EAXFLANGER_RATE; } + static constexpr auto eax_depth_param_id() { return EAXFLANGER_DEPTH; } + static constexpr auto eax_feedback_param_id() { return EAXFLANGER_FEEDBACK; } + static constexpr auto eax_delay_param_id() { return EAXFLANGER_DELAY; } + + static constexpr auto eax_min_waveform() { return EAXFLANGER_MINWAVEFORM; } + static constexpr auto eax_min_phase() { return EAXFLANGER_MINPHASE; } + static constexpr auto eax_min_rate() { return EAXFLANGER_MINRATE; } + static constexpr auto eax_min_depth() { return EAXFLANGER_MINDEPTH; } + static constexpr auto eax_min_feedback() { return EAXFLANGER_MINFEEDBACK; } + static constexpr auto eax_min_delay() { return EAXFLANGER_MINDELAY; } + + static constexpr auto eax_max_waveform() { return EAXFLANGER_MAXWAVEFORM; } + static constexpr auto eax_max_phase() { return EAXFLANGER_MAXPHASE; } + static constexpr auto eax_max_rate() { return EAXFLANGER_MAXRATE; } + static constexpr auto eax_max_depth() { return EAXFLANGER_MAXDEPTH; } + static constexpr auto eax_max_feedback() { return EAXFLANGER_MAXFEEDBACK; } + static constexpr auto eax_max_delay() { return EAXFLANGER_MAXDELAY; } + + static constexpr auto eax_default_waveform() { return EAXFLANGER_DEFAULTWAVEFORM; } + static constexpr auto eax_default_phase() { return EAXFLANGER_DEFAULTPHASE; } + static constexpr auto eax_default_rate() { return EAXFLANGER_DEFAULTRATE; } + static constexpr auto eax_default_depth() { return EAXFLANGER_DEFAULTDEPTH; } + static constexpr auto eax_default_feedback() { return EAXFLANGER_DEFAULTFEEDBACK; } + static constexpr auto eax_default_delay() { return EAXFLANGER_DEFAULTDELAY; } + + static constexpr auto efx_min_waveform() { return AL_FLANGER_MIN_WAVEFORM; } + static constexpr auto efx_min_phase() { return AL_FLANGER_MIN_PHASE; } + static constexpr auto efx_min_rate() { return AL_FLANGER_MIN_RATE; } + static constexpr auto efx_min_depth() { return AL_FLANGER_MIN_DEPTH; } + static constexpr auto efx_min_feedback() { return AL_FLANGER_MIN_FEEDBACK; } + static constexpr auto efx_min_delay() { return AL_FLANGER_MIN_DELAY; } + + static constexpr auto efx_max_waveform() { return AL_FLANGER_MAX_WAVEFORM; } + static constexpr auto efx_max_phase() { return AL_FLANGER_MAX_PHASE; } + static constexpr auto efx_max_rate() { return AL_FLANGER_MAX_RATE; } + static constexpr auto efx_max_depth() { return AL_FLANGER_MAX_DEPTH; } + static constexpr auto efx_max_feedback() { return AL_FLANGER_MAX_FEEDBACK; } + static constexpr auto efx_max_delay() { return AL_FLANGER_MAX_DELAY; } + + static constexpr auto efx_default_waveform() { return AL_FLANGER_DEFAULT_WAVEFORM; } + static constexpr auto efx_default_phase() { return AL_FLANGER_DEFAULT_PHASE; } + static constexpr auto efx_default_rate() { return AL_FLANGER_DEFAULT_RATE; } + static constexpr auto efx_default_depth() { return AL_FLANGER_DEFAULT_DEPTH; } + static constexpr auto efx_default_feedback() { return AL_FLANGER_DEFAULT_FEEDBACK; } + static constexpr auto efx_default_delay() { return AL_FLANGER_DEFAULT_DELAY; } + + static ChorusWaveform eax_waveform(unsigned long type) + { + if(type == EAX_FLANGER_SINUSOID) return ChorusWaveform::Sinusoid; + if(type == EAX_FLANGER_TRIANGLE) return ChorusWaveform::Triangle; + return ChorusWaveform::Sinusoid; + } +}; // EaxFlangerTraits + +template<typename TTraits> +struct ChorusFlangerEffect { + using Traits = TTraits; + using Committer = typename Traits::Committer; + using Exception = typename Committer::Exception; + + static constexpr auto Field = Traits::Field; + + struct WaveformValidator { + void operator()(unsigned long ulWaveform) const + { + eax_validate_range<Exception>( + "Waveform", + ulWaveform, + Traits::eax_min_waveform(), + Traits::eax_max_waveform()); + } + }; // WaveformValidator + + struct PhaseValidator { + void operator()(long lPhase) const + { + eax_validate_range<Exception>( + "Phase", + lPhase, + Traits::eax_min_phase(), + Traits::eax_max_phase()); + } + }; // PhaseValidator + + struct RateValidator { + void operator()(float flRate) const + { + eax_validate_range<Exception>( + "Rate", + flRate, + Traits::eax_min_rate(), + Traits::eax_max_rate()); + } + }; // RateValidator + + struct DepthValidator { + void operator()(float flDepth) const + { + eax_validate_range<Exception>( + "Depth", + flDepth, + Traits::eax_min_depth(), + Traits::eax_max_depth()); + } + }; // DepthValidator + + struct FeedbackValidator { + void operator()(float flFeedback) const + { + eax_validate_range<Exception>( + "Feedback", + flFeedback, + Traits::eax_min_feedback(), + Traits::eax_max_feedback()); + } + }; // FeedbackValidator + + struct DelayValidator { + void operator()(float flDelay) const + { + eax_validate_range<Exception>( + "Delay", + flDelay, + Traits::eax_min_delay(), + Traits::eax_max_delay()); + } + }; // DelayValidator + + struct AllValidator { + void operator()(const typename Traits::Props& all) const + { + WaveformValidator{}(all.ulWaveform); + PhaseValidator{}(all.lPhase); + RateValidator{}(all.flRate); + DepthValidator{}(all.flDepth); + FeedbackValidator{}(all.flFeedback); + DelayValidator{}(all.flDelay); + } + }; // AllValidator + +public: + static void SetDefaults(EaxEffectProps &props) + { + auto&& all = props.*Field; + props.mType = Traits::eax_effect_type(); + all.ulWaveform = Traits::eax_default_waveform(); + all.lPhase = Traits::eax_default_phase(); + all.flRate = Traits::eax_default_rate(); + all.flDepth = Traits::eax_default_depth(); + all.flFeedback = Traits::eax_default_feedback(); + all.flDelay = Traits::eax_default_delay(); + } + + + static void Get(const EaxCall &call, const EaxEffectProps &props) + { + auto&& all = props.*Field; + switch(call.get_property_id()) + { + case Traits::eax_none_param_id(): + break; + + case Traits::eax_allparameters_param_id(): + call.template set_value<Exception>(all); + break; + + case Traits::eax_waveform_param_id(): + call.template set_value<Exception>(all.ulWaveform); + break; + + case Traits::eax_phase_param_id(): + call.template set_value<Exception>(all.lPhase); + break; + + case Traits::eax_rate_param_id(): + call.template set_value<Exception>(all.flRate); + break; + + case Traits::eax_depth_param_id(): + call.template set_value<Exception>(all.flDepth); + break; + + case Traits::eax_feedback_param_id(): + call.template set_value<Exception>(all.flFeedback); + break; + + case Traits::eax_delay_param_id(): + call.template set_value<Exception>(all.flDelay); + break; + + default: + Committer::fail_unknown_property_id(); + } + } + + static void Set(const EaxCall &call, EaxEffectProps &props) + { + auto&& all = props.*Field; + switch(call.get_property_id()) + { + case Traits::eax_none_param_id(): + break; + + case Traits::eax_allparameters_param_id(): + Committer::template defer<AllValidator>(call, all); + break; + + case Traits::eax_waveform_param_id(): + Committer::template defer<WaveformValidator>(call, all.ulWaveform); + break; + + case Traits::eax_phase_param_id(): + Committer::template defer<PhaseValidator>(call, all.lPhase); + break; + + case Traits::eax_rate_param_id(): + Committer::template defer<RateValidator>(call, all.flRate); + break; + + case Traits::eax_depth_param_id(): + Committer::template defer<DepthValidator>(call, all.flDepth); + break; + + case Traits::eax_feedback_param_id(): + Committer::template defer<FeedbackValidator>(call, all.flFeedback); + break; + + case Traits::eax_delay_param_id(): + Committer::template defer<DelayValidator>(call, all.flDelay); + break; + + default: + Committer::fail_unknown_property_id(); + } + } + + static bool Commit(const EaxEffectProps &props, EaxEffectProps &props_, EffectProps &al_props_) + { + if(props.mType == props_.mType) + { + auto&& src = props_.*Field; + auto&& dst = props.*Field; + if(dst.ulWaveform == src.ulWaveform && dst.lPhase == src.lPhase + && dst.flRate == src.flRate && dst.flDepth == src.flDepth + && dst.flFeedback == src.flFeedback && dst.flDelay == src.flDelay) + return false; + } + + props_ = props; + auto&& dst = props.*Field; + + al_props_.Chorus.Waveform = Traits::eax_waveform(dst.ulWaveform); + al_props_.Chorus.Phase = static_cast<int>(dst.lPhase); + al_props_.Chorus.Rate = dst.flRate; + al_props_.Chorus.Depth = dst.flDepth; + al_props_.Chorus.Feedback = dst.flFeedback; + al_props_.Chorus.Delay = dst.flDelay; + + return true; + } +}; // EaxChorusFlangerEffect + + +using ChorusCommitter = EaxCommitter<EaxChorusCommitter>; +using FlangerCommitter = EaxCommitter<EaxFlangerCommitter>; + +} // namespace + +template<> +struct ChorusCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void ChorusCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool ChorusCommitter::commit(const EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxChorusTraits>; + return Committer::Commit(props, mEaxProps, mAlProps); +} + +template<> +void ChorusCommitter::SetDefaults(EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxChorusTraits>; + Committer::SetDefaults(props); +} + +template<> +void ChorusCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxChorusTraits>; + Committer::Get(call, props); +} + +template<> +void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxChorusTraits>; + Committer::Set(call, props); +} + +template<> +struct FlangerCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_FLANGER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void FlangerCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool FlangerCommitter::commit(const EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxFlangerTraits>; + return Committer::Commit(props, mEaxProps, mAlProps); +} + +template<> +void FlangerCommitter::SetDefaults(EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxFlangerTraits>; + Committer::SetDefaults(props); +} + +template<> +void FlangerCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxFlangerTraits>; + Committer::Get(call, props); +} + +template<> +void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + using Committer = ChorusFlangerEffect<EaxFlangerTraits>; + Committer::Set(call, props); +} + +#endif // ALSOFT_EAX diff --git a/al/effects/compressor.cpp b/al/effects/compressor.cpp new file mode 100644 index 00000000..a4aa8e77 --- /dev/null +++ b/al/effects/compressor.cpp @@ -0,0 +1,162 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Compressor_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_COMPRESSOR_ONOFF: + if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) + throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; + props->Compressor.OnOff = (val != AL_FALSE); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", + param}; + } +} +void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Compressor_setParami(props, param, vals[0]); } +void Compressor_setParamf(EffectProps*, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } +void Compressor_setParamfv(EffectProps*, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", + param}; +} + +void Compressor_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_COMPRESSOR_ONOFF: + *val = props->Compressor.OnOff; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", + param}; + } +} +void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Compressor_getParami(props, param, vals); } +void Compressor_getParamf(const EffectProps*, ALenum param, float*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } +void Compressor_getParamfv(const EffectProps*, ALenum param, float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", + param}; +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Compressor); + +const EffectProps CompressorEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using CompressorCommitter = EaxCommitter<EaxCompressorCommitter>; + +struct OnOffValidator { + void operator()(unsigned long ulOnOff) const + { + eax_validate_range<CompressorCommitter::Exception>( + "On-Off", + ulOnOff, + EAXAGCCOMPRESSOR_MINONOFF, + EAXAGCCOMPRESSOR_MAXONOFF); + } +}; // OnOffValidator + +struct AllValidator { + void operator()(const EAXAGCCOMPRESSORPROPERTIES& all) const + { + OnOffValidator{}(all.ulOnOff); + } +}; // AllValidator + +} // namespace + +template<> +struct CompressorCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void CompressorCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool CompressorCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && props.mCompressor.ulOnOff == mEaxProps.mCompressor.ulOnOff) + return false; + + mEaxProps = props; + + mAlProps.Compressor.OnOff = (props.mCompressor.ulOnOff != 0); + return true; +} + +template<> +void CompressorCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Compressor; + props.mCompressor.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; +} + +template<> +void CompressorCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value<Exception>(props.mCompressor); break; + case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(props.mCompressor.ulOnOff); break; + default: fail_unknown_property_id(); + } +} + +template<> +void CompressorCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXAGCCOMPRESSOR_NONE: break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer<AllValidator>(call, props.mCompressor); break; + case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.mCompressor.ulOnOff); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/convolution.cpp b/al/effects/convolution.cpp new file mode 100644 index 00000000..8e850fd3 --- /dev/null +++ b/al/effects/convolution.cpp @@ -0,0 +1,93 @@ + +#include "config.h" + +#include "AL/al.h" +#include "alc/inprogext.h" + +#include "alc/effects/base.h" +#include "effects.h" + + +namespace { + +void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ + switch(param) + { + default: + Convolution_setParami(props, param, vals[0]); + } +} +void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ + switch(param) + { + default: + Convolution_setParamf(props, param, vals[0]); + } +} + +void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ + switch(param) + { + default: + Convolution_getParami(props, param, vals); + } +} +void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ + switch(param) + { + default: + Convolution_getParamf(props, param, vals); + } +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Convolution); + +const EffectProps ConvolutionEffectProps{genDefaultProps()}; diff --git a/al/effects/dedicated.cpp b/al/effects/dedicated.cpp new file mode 100644 index 00000000..db57003c --- /dev/null +++ b/al/effects/dedicated.cpp @@ -0,0 +1,72 @@ + +#include "config.h" + +#include <cmath> + +#include "AL/al.h" +#include "AL/alext.h" + +#include "alc/effects/base.h" +#include "effects.h" + + +namespace { + +void Dedicated_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void Dedicated_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void Dedicated_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: + if(!(val >= 0.0f && std::isfinite(val))) + throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + props->Dedicated.Gain = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Dedicated_setParamf(props, param, vals[0]); } + +void Dedicated_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void Dedicated_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: + *val = props->Dedicated.Gain; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Dedicated_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Dedicated.Gain = 1.0f; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Dedicated); + +const EffectProps DedicatedEffectProps{genDefaultProps()}; diff --git a/al/effects/distortion.cpp b/al/effects/distortion.cpp new file mode 100644 index 00000000..ee298ddf --- /dev/null +++ b/al/effects/distortion.cpp @@ -0,0 +1,271 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Distortion_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } +void Distortion_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", + param}; +} +void Distortion_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_DISTORTION_EDGE: + if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) + throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; + props->Distortion.Edge = val; + break; + + case AL_DISTORTION_GAIN: + if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; + props->Distortion.Gain = val; + break; + + case AL_DISTORTION_LOWPASS_CUTOFF: + if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; + props->Distortion.LowpassCutoff = val; + break; + + case AL_DISTORTION_EQCENTER: + if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) + throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; + props->Distortion.EQCenter = val; + break; + + case AL_DISTORTION_EQBANDWIDTH: + if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) + throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; + props->Distortion.EQBandwidth = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + } +} +void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Distortion_setParamf(props, param, vals[0]); } + +void Distortion_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } +void Distortion_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", + param}; +} +void Distortion_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_DISTORTION_EDGE: + *val = props->Distortion.Edge; + break; + + case AL_DISTORTION_GAIN: + *val = props->Distortion.Gain; + break; + + case AL_DISTORTION_LOWPASS_CUTOFF: + *val = props->Distortion.LowpassCutoff; + break; + + case AL_DISTORTION_EQCENTER: + *val = props->Distortion.EQCenter; + break; + + case AL_DISTORTION_EQBANDWIDTH: + *val = props->Distortion.EQBandwidth; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; + } +} +void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Distortion_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; + props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; + props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; + props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; + props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Distortion); + +const EffectProps DistortionEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using DistortionCommitter = EaxCommitter<EaxDistortionCommitter>; + +struct EdgeValidator { + void operator()(float flEdge) const + { + eax_validate_range<DistortionCommitter::Exception>( + "Edge", + flEdge, + EAXDISTORTION_MINEDGE, + EAXDISTORTION_MAXEDGE); + } +}; // EdgeValidator + +struct GainValidator { + void operator()(long lGain) const + { + eax_validate_range<DistortionCommitter::Exception>( + "Gain", + lGain, + EAXDISTORTION_MINGAIN, + EAXDISTORTION_MAXGAIN); + } +}; // GainValidator + +struct LowPassCutOffValidator { + void operator()(float flLowPassCutOff) const + { + eax_validate_range<DistortionCommitter::Exception>( + "Low-pass Cut-off", + flLowPassCutOff, + EAXDISTORTION_MINLOWPASSCUTOFF, + EAXDISTORTION_MAXLOWPASSCUTOFF); + } +}; // LowPassCutOffValidator + +struct EqCenterValidator { + void operator()(float flEQCenter) const + { + eax_validate_range<DistortionCommitter::Exception>( + "EQ Center", + flEQCenter, + EAXDISTORTION_MINEQCENTER, + EAXDISTORTION_MAXEQCENTER); + } +}; // EqCenterValidator + +struct EqBandwidthValidator { + void operator()(float flEQBandwidth) const + { + eax_validate_range<DistortionCommitter::Exception>( + "EQ Bandwidth", + flEQBandwidth, + EAXDISTORTION_MINEQBANDWIDTH, + EAXDISTORTION_MAXEQBANDWIDTH); + } +}; // EqBandwidthValidator + +struct AllValidator { + void operator()(const EAXDISTORTIONPROPERTIES& all) const + { + EdgeValidator{}(all.flEdge); + GainValidator{}(all.lGain); + LowPassCutOffValidator{}(all.flLowPassCutOff); + EqCenterValidator{}(all.flEQCenter); + EqBandwidthValidator{}(all.flEQBandwidth); + } +}; // AllValidator + +} // namespace + +template<> +struct DistortionCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_DISTORTION_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void DistortionCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool DistortionCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType && mEaxProps.mDistortion.flEdge == props.mDistortion.flEdge + && mEaxProps.mDistortion.lGain == props.mDistortion.lGain + && mEaxProps.mDistortion.flLowPassCutOff == props.mDistortion.flLowPassCutOff + && mEaxProps.mDistortion.flEQCenter == props.mDistortion.flEQCenter + && mEaxProps.mDistortion.flEQBandwidth == props.mDistortion.flEQBandwidth) + return false; + + mEaxProps = props; + + mAlProps.Distortion.Edge = props.mDistortion.flEdge; + mAlProps.Distortion.Gain = level_mb_to_gain(static_cast<float>(props.mDistortion.lGain)); + mAlProps.Distortion.LowpassCutoff = props.mDistortion.flLowPassCutOff; + mAlProps.Distortion.EQCenter = props.mDistortion.flEQCenter; + mAlProps.Distortion.EQBandwidth = props.mDistortion.flEdge; + + return true; +} + +template<> +void DistortionCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Distortion; + props.mDistortion.flEdge = EAXDISTORTION_DEFAULTEDGE; + props.mDistortion.lGain = EAXDISTORTION_DEFAULTGAIN; + props.mDistortion.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; + props.mDistortion.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; + props.mDistortion.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; +} + +template<> +void DistortionCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXDISTORTION_NONE: break; + case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props.mDistortion); break; + case EAXDISTORTION_EDGE: call.set_value<Exception>(props.mDistortion.flEdge); break; + case EAXDISTORTION_GAIN: call.set_value<Exception>(props.mDistortion.lGain); break; + case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.mDistortion.flLowPassCutOff); break; + case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.mDistortion.flEQCenter); break; + case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.mDistortion.flEQBandwidth); break; + default: fail_unknown_property_id(); + } +} + +template<> +void DistortionCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXDISTORTION_NONE: break; + case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props.mDistortion); break; + case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.mDistortion.flEdge); break; + case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.mDistortion.lGain); break; + case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.mDistortion.flLowPassCutOff); break; + case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.mDistortion.flEQCenter); break; + case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.mDistortion.flEQBandwidth); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/echo.cpp b/al/effects/echo.cpp new file mode 100644 index 00000000..2eb37603 --- /dev/null +++ b/al/effects/echo.cpp @@ -0,0 +1,268 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short"); +static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short"); + +void Echo_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } +void Echo_setParamiv(EffectProps*, ALenum param, const int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } +void Echo_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_ECHO_DELAY: + if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; + props->Echo.Delay = val; + break; + + case AL_ECHO_LRDELAY: + if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) + throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; + props->Echo.LRDelay = val; + break; + + case AL_ECHO_DAMPING: + if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) + throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; + props->Echo.Damping = val; + break; + + case AL_ECHO_FEEDBACK: + if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) + throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; + props->Echo.Feedback = val; + break; + + case AL_ECHO_SPREAD: + if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) + throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; + props->Echo.Spread = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + } +} +void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Echo_setParamf(props, param, vals[0]); } + +void Echo_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } +void Echo_getParamiv(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } +void Echo_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_ECHO_DELAY: + *val = props->Echo.Delay; + break; + + case AL_ECHO_LRDELAY: + *val = props->Echo.LRDelay; + break; + + case AL_ECHO_DAMPING: + *val = props->Echo.Damping; + break; + + case AL_ECHO_FEEDBACK: + *val = props->Echo.Feedback; + break; + + case AL_ECHO_SPREAD: + *val = props->Echo.Spread; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; + } +} +void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Echo_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; + props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; + props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; + props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; + props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Echo); + +const EffectProps EchoEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EchoCommitter = EaxCommitter<EaxEchoCommitter>; + +struct DelayValidator { + void operator()(float flDelay) const + { + eax_validate_range<EchoCommitter::Exception>( + "Delay", + flDelay, + EAXECHO_MINDELAY, + EAXECHO_MAXDELAY); + } +}; // DelayValidator + +struct LrDelayValidator { + void operator()(float flLRDelay) const + { + eax_validate_range<EchoCommitter::Exception>( + "LR Delay", + flLRDelay, + EAXECHO_MINLRDELAY, + EAXECHO_MAXLRDELAY); + } +}; // LrDelayValidator + +struct DampingValidator { + void operator()(float flDamping) const + { + eax_validate_range<EchoCommitter::Exception>( + "Damping", + flDamping, + EAXECHO_MINDAMPING, + EAXECHO_MAXDAMPING); + } +}; // DampingValidator + +struct FeedbackValidator { + void operator()(float flFeedback) const + { + eax_validate_range<EchoCommitter::Exception>( + "Feedback", + flFeedback, + EAXECHO_MINFEEDBACK, + EAXECHO_MAXFEEDBACK); + } +}; // FeedbackValidator + +struct SpreadValidator { + void operator()(float flSpread) const + { + eax_validate_range<EchoCommitter::Exception>( + "Spread", + flSpread, + EAXECHO_MINSPREAD, + EAXECHO_MAXSPREAD); + } +}; // SpreadValidator + +struct AllValidator { + void operator()(const EAXECHOPROPERTIES& all) const + { + DelayValidator{}(all.flDelay); + LrDelayValidator{}(all.flLRDelay); + DampingValidator{}(all.flDamping); + FeedbackValidator{}(all.flFeedback); + SpreadValidator{}(all.flSpread); + } +}; // AllValidator + +} // namespace + +template<> +struct EchoCommitter::Exception : public EaxException { + explicit Exception(const char* message) : EaxException{"EAX_ECHO_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void EchoCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool EchoCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType && mEaxProps.mEcho.flDelay == props.mEcho.flDelay + && mEaxProps.mEcho.flLRDelay == props.mEcho.flLRDelay + && mEaxProps.mEcho.flDamping == props.mEcho.flDamping + && mEaxProps.mEcho.flFeedback == props.mEcho.flFeedback + && mEaxProps.mEcho.flSpread == props.mEcho.flSpread) + return false; + + mEaxProps = props; + + mAlProps.Echo.Delay = props.mEcho.flDelay; + mAlProps.Echo.LRDelay = props.mEcho.flLRDelay; + mAlProps.Echo.Damping = props.mEcho.flDamping; + mAlProps.Echo.Feedback = props.mEcho.flFeedback; + mAlProps.Echo.Spread = props.mEcho.flSpread; + + return true; +} + +template<> +void EchoCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Echo; + props.mEcho.flDelay = EAXECHO_DEFAULTDELAY; + props.mEcho.flLRDelay = EAXECHO_DEFAULTLRDELAY; + props.mEcho.flDamping = EAXECHO_DEFAULTDAMPING; + props.mEcho.flFeedback = EAXECHO_DEFAULTFEEDBACK; + props.mEcho.flSpread = EAXECHO_DEFAULTSPREAD; +} + +template<> +void EchoCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXECHO_NONE: break; + case EAXECHO_ALLPARAMETERS: call.set_value<Exception>(props.mEcho); break; + case EAXECHO_DELAY: call.set_value<Exception>(props.mEcho.flDelay); break; + case EAXECHO_LRDELAY: call.set_value<Exception>(props.mEcho.flLRDelay); break; + case EAXECHO_DAMPING: call.set_value<Exception>(props.mEcho.flDamping); break; + case EAXECHO_FEEDBACK: call.set_value<Exception>(props.mEcho.flFeedback); break; + case EAXECHO_SPREAD: call.set_value<Exception>(props.mEcho.flSpread); break; + default: fail_unknown_property_id(); + } +} + +template<> +void EchoCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXECHO_NONE: break; + case EAXECHO_ALLPARAMETERS: defer<AllValidator>(call, props.mEcho); break; + case EAXECHO_DELAY: defer<DelayValidator>(call, props.mEcho.flDelay); break; + case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.mEcho.flLRDelay); break; + case EAXECHO_DAMPING: defer<DampingValidator>(call, props.mEcho.flDamping); break; + case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.mEcho.flFeedback); break; + case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.mEcho.flSpread); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/effects.cpp b/al/effects/effects.cpp new file mode 100644 index 00000000..4a67b5ff --- /dev/null +++ b/al/effects/effects.cpp @@ -0,0 +1,9 @@ +#include "config.h" + +#ifdef ALSOFT_EAX + +#include <cassert> +#include "AL/efx.h" +#include "effects.h" + +#endif // ALSOFT_EAX diff --git a/al/effects/effects.h b/al/effects/effects.h new file mode 100644 index 00000000..9d57dd82 --- /dev/null +++ b/al/effects/effects.h @@ -0,0 +1,88 @@ +#ifndef AL_EFFECTS_EFFECTS_H +#define AL_EFFECTS_EFFECTS_H + +#include "AL/al.h" + +#include "core/except.h" + +#ifdef ALSOFT_EAX +#include "al/eax/effect.h" +#endif // ALSOFT_EAX + +union EffectProps; + + +class effect_exception final : public al::base_exception { + ALenum mErrorCode; + +public: +#ifdef __USE_MINGW_ANSI_STDIO + [[gnu::format(gnu_printf, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + effect_exception(ALenum code, const char *msg, ...); + ~effect_exception() override; + + ALenum errorCode() const noexcept { return mErrorCode; } +}; + + +struct EffectVtable { + void (*const setParami)(EffectProps *props, ALenum param, int val); + void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals); + void (*const setParamf)(EffectProps *props, ALenum param, float val); + void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals); + + void (*const getParami)(const EffectProps *props, ALenum param, int *val); + void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals); + void (*const getParamf)(const EffectProps *props, ALenum param, float *val); + void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals); +}; + +#define DEFINE_ALEFFECT_VTABLE(T) \ +const EffectVtable T##EffectVtable = { \ + T##_setParami, T##_setParamiv, \ + T##_setParamf, T##_setParamfv, \ + T##_getParami, T##_getParamiv, \ + T##_getParamf, T##_getParamfv, \ +} + + +/* Default properties for the given effect types. */ +extern const EffectProps NullEffectProps; +extern const EffectProps ReverbEffectProps; +extern const EffectProps StdReverbEffectProps; +extern const EffectProps AutowahEffectProps; +extern const EffectProps ChorusEffectProps; +extern const EffectProps CompressorEffectProps; +extern const EffectProps DistortionEffectProps; +extern const EffectProps EchoEffectProps; +extern const EffectProps EqualizerEffectProps; +extern const EffectProps FlangerEffectProps; +extern const EffectProps FshifterEffectProps; +extern const EffectProps ModulatorEffectProps; +extern const EffectProps PshifterEffectProps; +extern const EffectProps VmorpherEffectProps; +extern const EffectProps DedicatedEffectProps; +extern const EffectProps ConvolutionEffectProps; + +/* Vtables to get/set properties for the given effect types. */ +extern const EffectVtable NullEffectVtable; +extern const EffectVtable ReverbEffectVtable; +extern const EffectVtable StdReverbEffectVtable; +extern const EffectVtable AutowahEffectVtable; +extern const EffectVtable ChorusEffectVtable; +extern const EffectVtable CompressorEffectVtable; +extern const EffectVtable DistortionEffectVtable; +extern const EffectVtable EchoEffectVtable; +extern const EffectVtable EqualizerEffectVtable; +extern const EffectVtable FlangerEffectVtable; +extern const EffectVtable FshifterEffectVtable; +extern const EffectVtable ModulatorEffectVtable; +extern const EffectVtable PshifterEffectVtable; +extern const EffectVtable VmorpherEffectVtable; +extern const EffectVtable DedicatedEffectVtable; +extern const EffectVtable ConvolutionEffectVtable; + +#endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/al/effects/equalizer.cpp b/al/effects/equalizer.cpp new file mode 100644 index 00000000..7dc703db --- /dev/null +++ b/al/effects/equalizer.cpp @@ -0,0 +1,411 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Equalizer_setParami(EffectProps*, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } +void Equalizer_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", + param}; +} +void Equalizer_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_EQUALIZER_LOW_GAIN: + if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; + props->Equalizer.LowGain = val; + break; + + case AL_EQUALIZER_LOW_CUTOFF: + if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; + props->Equalizer.LowCutoff = val; + break; + + case AL_EQUALIZER_MID1_GAIN: + if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; + props->Equalizer.Mid1Gain = val; + break; + + case AL_EQUALIZER_MID1_CENTER: + if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; + props->Equalizer.Mid1Center = val; + break; + + case AL_EQUALIZER_MID1_WIDTH: + if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; + props->Equalizer.Mid1Width = val; + break; + + case AL_EQUALIZER_MID2_GAIN: + if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; + props->Equalizer.Mid2Gain = val; + break; + + case AL_EQUALIZER_MID2_CENTER: + if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; + props->Equalizer.Mid2Center = val; + break; + + case AL_EQUALIZER_MID2_WIDTH: + if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; + props->Equalizer.Mid2Width = val; + break; + + case AL_EQUALIZER_HIGH_GAIN: + if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; + props->Equalizer.HighGain = val; + break; + + case AL_EQUALIZER_HIGH_CUTOFF: + if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; + props->Equalizer.HighCutoff = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + } +} +void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Equalizer_setParamf(props, param, vals[0]); } + +void Equalizer_getParami(const EffectProps*, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } +void Equalizer_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", + param}; +} +void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_EQUALIZER_LOW_GAIN: + *val = props->Equalizer.LowGain; + break; + + case AL_EQUALIZER_LOW_CUTOFF: + *val = props->Equalizer.LowCutoff; + break; + + case AL_EQUALIZER_MID1_GAIN: + *val = props->Equalizer.Mid1Gain; + break; + + case AL_EQUALIZER_MID1_CENTER: + *val = props->Equalizer.Mid1Center; + break; + + case AL_EQUALIZER_MID1_WIDTH: + *val = props->Equalizer.Mid1Width; + break; + + case AL_EQUALIZER_MID2_GAIN: + *val = props->Equalizer.Mid2Gain; + break; + + case AL_EQUALIZER_MID2_CENTER: + *val = props->Equalizer.Mid2Center; + break; + + case AL_EQUALIZER_MID2_WIDTH: + *val = props->Equalizer.Mid2Width; + break; + + case AL_EQUALIZER_HIGH_GAIN: + *val = props->Equalizer.HighGain; + break; + + case AL_EQUALIZER_HIGH_CUTOFF: + *val = props->Equalizer.HighCutoff; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; + } +} +void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Equalizer_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; + props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; + props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; + props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; + props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; + props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; + props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; + props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; + props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; + props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Equalizer); + +const EffectProps EqualizerEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using EqualizerCommitter = EaxCommitter<EaxEqualizerCommitter>; + +struct LowGainValidator { + void operator()(long lLowGain) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Low Gain", + lLowGain, + EAXEQUALIZER_MINLOWGAIN, + EAXEQUALIZER_MAXLOWGAIN); + } +}; // LowGainValidator + +struct LowCutOffValidator { + void operator()(float flLowCutOff) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Low Cutoff", + flLowCutOff, + EAXEQUALIZER_MINLOWCUTOFF, + EAXEQUALIZER_MAXLOWCUTOFF); + } +}; // LowCutOffValidator + +struct Mid1GainValidator { + void operator()(long lMid1Gain) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Mid1 Gain", + lMid1Gain, + EAXEQUALIZER_MINMID1GAIN, + EAXEQUALIZER_MAXMID1GAIN); + } +}; // Mid1GainValidator + +struct Mid1CenterValidator { + void operator()(float flMid1Center) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Mid1 Center", + flMid1Center, + EAXEQUALIZER_MINMID1CENTER, + EAXEQUALIZER_MAXMID1CENTER); + } +}; // Mid1CenterValidator + +struct Mid1WidthValidator { + void operator()(float flMid1Width) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Mid1 Width", + flMid1Width, + EAXEQUALIZER_MINMID1WIDTH, + EAXEQUALIZER_MAXMID1WIDTH); + } +}; // Mid1WidthValidator + +struct Mid2GainValidator { + void operator()(long lMid2Gain) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Mid2 Gain", + lMid2Gain, + EAXEQUALIZER_MINMID2GAIN, + EAXEQUALIZER_MAXMID2GAIN); + } +}; // Mid2GainValidator + +struct Mid2CenterValidator { + void operator()(float flMid2Center) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Mid2 Center", + flMid2Center, + EAXEQUALIZER_MINMID2CENTER, + EAXEQUALIZER_MAXMID2CENTER); + } +}; // Mid2CenterValidator + +struct Mid2WidthValidator { + void operator()(float flMid2Width) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "Mid2 Width", + flMid2Width, + EAXEQUALIZER_MINMID2WIDTH, + EAXEQUALIZER_MAXMID2WIDTH); + } +}; // Mid2WidthValidator + +struct HighGainValidator { + void operator()(long lHighGain) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "High Gain", + lHighGain, + EAXEQUALIZER_MINHIGHGAIN, + EAXEQUALIZER_MAXHIGHGAIN); + } +}; // HighGainValidator + +struct HighCutOffValidator { + void operator()(float flHighCutOff) const + { + eax_validate_range<EqualizerCommitter::Exception>( + "High Cutoff", + flHighCutOff, + EAXEQUALIZER_MINHIGHCUTOFF, + EAXEQUALIZER_MAXHIGHCUTOFF); + } +}; // HighCutOffValidator + +struct AllValidator { + void operator()(const EAXEQUALIZERPROPERTIES& all) const + { + LowGainValidator{}(all.lLowGain); + LowCutOffValidator{}(all.flLowCutOff); + Mid1GainValidator{}(all.lMid1Gain); + Mid1CenterValidator{}(all.flMid1Center); + Mid1WidthValidator{}(all.flMid1Width); + Mid2GainValidator{}(all.lMid2Gain); + Mid2CenterValidator{}(all.flMid2Center); + Mid2WidthValidator{}(all.flMid2Width); + HighGainValidator{}(all.lHighGain); + HighCutOffValidator{}(all.flHighCutOff); + } +}; // AllValidator + +} // namespace + +template<> +struct EqualizerCommitter::Exception : public EaxException { + explicit Exception(const char* message) : EaxException{"EAX_EQUALIZER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void EqualizerCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool EqualizerCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType && mEaxProps.mEqualizer.lLowGain == props.mEqualizer.lLowGain + && mEaxProps.mEqualizer.flLowCutOff == props.mEqualizer.flLowCutOff + && mEaxProps.mEqualizer.lMid1Gain == props.mEqualizer.lMid1Gain + && mEaxProps.mEqualizer.flMid1Center == props.mEqualizer.flMid1Center + && mEaxProps.mEqualizer.flMid1Width == props.mEqualizer.flMid1Width + && mEaxProps.mEqualizer.lMid2Gain == props.mEqualizer.lMid2Gain + && mEaxProps.mEqualizer.flMid2Center == props.mEqualizer.flMid2Center + && mEaxProps.mEqualizer.flMid2Width == props.mEqualizer.flMid2Width + && mEaxProps.mEqualizer.lHighGain == props.mEqualizer.lHighGain + && mEaxProps.mEqualizer.flHighCutOff == props.mEqualizer.flHighCutOff) + return false; + + mEaxProps = props; + + mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lLowGain)); + mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff; + mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid1Gain)); + mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center; + mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width; + mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid2Gain)); + mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center; + mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width; + mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lHighGain)); + mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff; + + return true; +} + +template<> +void EqualizerCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Equalizer; + props.mEqualizer.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; + props.mEqualizer.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; + props.mEqualizer.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; + props.mEqualizer.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; + props.mEqualizer.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; + props.mEqualizer.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; + props.mEqualizer.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; + props.mEqualizer.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; + props.mEqualizer.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; + props.mEqualizer.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; +} + +template<> +void EqualizerCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXEQUALIZER_NONE: break; + case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props.mEqualizer); break; + case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.mEqualizer.lLowGain); break; + case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.mEqualizer.flLowCutOff); break; + case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.mEqualizer.lMid1Gain); break; + case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.mEqualizer.flMid1Center); break; + case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.mEqualizer.flMid1Width); break; + case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.mEqualizer.lMid2Gain); break; + case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.mEqualizer.flMid2Center); break; + case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.mEqualizer.flMid2Width); break; + case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.mEqualizer.lHighGain); break; + case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.mEqualizer.flHighCutOff); break; + default: fail_unknown_property_id(); + } +} + +template<> +void EqualizerCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXEQUALIZER_NONE: break; + case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props.mEqualizer); break; + case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.mEqualizer.lLowGain); break; + case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.mEqualizer.flLowCutOff); break; + case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.mEqualizer.lMid1Gain); break; + case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.mEqualizer.flMid1Center); break; + case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.mEqualizer.flMid1Width); break; + case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.mEqualizer.lMid2Gain); break; + case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.mEqualizer.flMid2Center); break; + case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.mEqualizer.flMid2Width); break; + case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.mEqualizer.lHighGain); break; + case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.mEqualizer.flHighCutOff); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/fshifter.cpp b/al/effects/fshifter.cpp new file mode 100644 index 00000000..949db203 --- /dev/null +++ b/al/effects/fshifter.cpp @@ -0,0 +1,264 @@ + +#include "config.h" + +#include <stdexcept> + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include <cassert> +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +al::optional<FShifterDirection> DirectionFromEmum(ALenum value) +{ + switch(value) + { + case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return FShifterDirection::Down; + case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up; + case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off; + } + return al::nullopt; +} +ALenum EnumFromDirection(FShifterDirection dir) +{ + switch(dir) + { + case FShifterDirection::Down: return AL_FREQUENCY_SHIFTER_DIRECTION_DOWN; + case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP; + case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF; + } + throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast<int>(dir))}; +} + +void Fshifter_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_FREQUENCY: + if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) + throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; + props->Fshifter.Frequency = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", + param}; + } +} +void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Fshifter_setParamf(props, param, vals[0]); } + +void Fshifter_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: + if(auto diropt = DirectionFromEmum(val)) + props->Fshifter.LeftDirection = *diropt; + else + throw effect_exception{AL_INVALID_VALUE, + "Unsupported frequency shifter left direction: 0x%04x", val}; + break; + + case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: + if(auto diropt = DirectionFromEmum(val)) + props->Fshifter.RightDirection = *diropt; + else + throw effect_exception{AL_INVALID_VALUE, + "Unsupported frequency shifter right direction: 0x%04x", val}; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, + "Invalid frequency shifter integer property 0x%04x", param}; + } +} +void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Fshifter_setParami(props, param, vals[0]); } + +void Fshifter_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: + *val = EnumFromDirection(props->Fshifter.LeftDirection); + break; + case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: + *val = EnumFromDirection(props->Fshifter.RightDirection); + break; + default: + throw effect_exception{AL_INVALID_ENUM, + "Invalid frequency shifter integer property 0x%04x", param}; + } +} +void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Fshifter_getParami(props, param, vals); } + +void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_FREQUENCY: + *val = props->Fshifter.Frequency; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", + param}; + } +} +void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Fshifter_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; + props.Fshifter.LeftDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION); + props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION); + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Fshifter); + +const EffectProps FshifterEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using FrequencyShifterCommitter = EaxCommitter<EaxFrequencyShifterCommitter>; + +struct FrequencyValidator { + void operator()(float flFrequency) const + { + eax_validate_range<FrequencyShifterCommitter::Exception>( + "Frequency", + flFrequency, + EAXFREQUENCYSHIFTER_MINFREQUENCY, + EAXFREQUENCYSHIFTER_MAXFREQUENCY); + } +}; // FrequencyValidator + +struct LeftDirectionValidator { + void operator()(unsigned long ulLeftDirection) const + { + eax_validate_range<FrequencyShifterCommitter::Exception>( + "Left Direction", + ulLeftDirection, + EAXFREQUENCYSHIFTER_MINLEFTDIRECTION, + EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION); + } +}; // LeftDirectionValidator + +struct RightDirectionValidator { + void operator()(unsigned long ulRightDirection) const + { + eax_validate_range<FrequencyShifterCommitter::Exception>( + "Right Direction", + ulRightDirection, + EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION, + EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION); + } +}; // RightDirectionValidator + +struct AllValidator { + void operator()(const EAXFREQUENCYSHIFTERPROPERTIES& all) const + { + FrequencyValidator{}(all.flFrequency); + LeftDirectionValidator{}(all.ulLeftDirection); + RightDirectionValidator{}(all.ulRightDirection); + } +}; // AllValidator + +} // namespace + +template<> +struct FrequencyShifterCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void FrequencyShifterCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool FrequencyShifterCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && mEaxProps.mFrequencyShifter.flFrequency == props.mFrequencyShifter.flFrequency + && mEaxProps.mFrequencyShifter.ulLeftDirection == props.mFrequencyShifter.ulLeftDirection + && mEaxProps.mFrequencyShifter.ulRightDirection == props.mFrequencyShifter.ulRightDirection) + return false; + + mEaxProps = props; + + auto get_direction = [](unsigned long dir) noexcept + { + if(dir == EAX_FREQUENCYSHIFTER_DOWN) + return FShifterDirection::Down; + if(dir == EAX_FREQUENCYSHIFTER_UP) + return FShifterDirection::Up; + return FShifterDirection::Off; + }; + + mAlProps.Fshifter.Frequency = props.mFrequencyShifter.flFrequency; + mAlProps.Fshifter.LeftDirection = get_direction(props.mFrequencyShifter.ulLeftDirection); + mAlProps.Fshifter.RightDirection = get_direction(props.mFrequencyShifter.ulRightDirection); + + return true; +} + +template<> +void FrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::FrequencyShifter; + props.mFrequencyShifter.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + props.mFrequencyShifter.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + props.mFrequencyShifter.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; +} + +template<> +void FrequencyShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: break; + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mFrequencyShifter); break; + case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.mFrequencyShifter.flFrequency); break; + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulLeftDirection); break; + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulRightDirection); break; + default: fail_unknown_property_id(); + } +} + +template<> +void FrequencyShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXFREQUENCYSHIFTER_NONE: break; + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mFrequencyShifter); break; + case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.mFrequencyShifter.flFrequency); break; + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.mFrequencyShifter.ulLeftDirection); break; + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.mFrequencyShifter.ulRightDirection); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/modulator.cpp b/al/effects/modulator.cpp new file mode 100644 index 00000000..5f37d08f --- /dev/null +++ b/al/effects/modulator.cpp @@ -0,0 +1,272 @@ + +#include "config.h" + +#include <stdexcept> + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include <cassert> +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +al::optional<ModulatorWaveform> WaveformFromEmum(ALenum value) +{ + switch(value) + { + case AL_RING_MODULATOR_SINUSOID: return ModulatorWaveform::Sinusoid; + case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth; + case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square; + } + return al::nullopt; +} +ALenum EnumFromWaveform(ModulatorWaveform type) +{ + switch(type) + { + case ModulatorWaveform::Sinusoid: return AL_RING_MODULATOR_SINUSOID; + case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH; + case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE; + } + throw std::runtime_error{"Invalid modulator waveform: " + + std::to_string(static_cast<int>(type))}; +} + +void Modulator_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) + throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; + props->Modulator.Frequency = val; + break; + + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; + props->Modulator.HighPassCutoff = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + } +} +void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Modulator_setParamf(props, param, vals[0]); } +void Modulator_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + Modulator_setParamf(props, param, static_cast<float>(val)); + break; + + case AL_RING_MODULATOR_WAVEFORM: + if(auto formopt = WaveformFromEmum(val)) + props->Modulator.Waveform = *formopt; + else + throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val}; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", + param}; + } +} +void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Modulator_setParami(props, param, vals[0]); } + +void Modulator_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + *val = static_cast<int>(props->Modulator.Frequency); + break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + *val = static_cast<int>(props->Modulator.HighPassCutoff); + break; + case AL_RING_MODULATOR_WAVEFORM: + *val = EnumFromWaveform(props->Modulator.Waveform); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", + param}; + } +} +void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Modulator_getParami(props, param, vals); } +void Modulator_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: + *val = props->Modulator.Frequency; + break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: + *val = props->Modulator.HighPassCutoff; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + } +} +void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Modulator_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; + props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; + props.Modulator.Waveform = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM); + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Modulator); + +const EffectProps ModulatorEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using ModulatorCommitter = EaxCommitter<EaxModulatorCommitter>; + +struct FrequencyValidator { + void operator()(float flFrequency) const + { + eax_validate_range<ModulatorCommitter::Exception>( + "Frequency", + flFrequency, + EAXRINGMODULATOR_MINFREQUENCY, + EAXRINGMODULATOR_MAXFREQUENCY); + } +}; // FrequencyValidator + +struct HighPassCutOffValidator { + void operator()(float flHighPassCutOff) const + { + eax_validate_range<ModulatorCommitter::Exception>( + "High-Pass Cutoff", + flHighPassCutOff, + EAXRINGMODULATOR_MINHIGHPASSCUTOFF, + EAXRINGMODULATOR_MAXHIGHPASSCUTOFF); + } +}; // HighPassCutOffValidator + +struct WaveformValidator { + void operator()(unsigned long ulWaveform) const + { + eax_validate_range<ModulatorCommitter::Exception>( + "Waveform", + ulWaveform, + EAXRINGMODULATOR_MINWAVEFORM, + EAXRINGMODULATOR_MAXWAVEFORM); + } +}; // WaveformValidator + +struct AllValidator { + void operator()(const EAXRINGMODULATORPROPERTIES& all) const + { + FrequencyValidator{}(all.flFrequency); + HighPassCutOffValidator{}(all.flHighPassCutOff); + WaveformValidator{}(all.ulWaveform); + } +}; // AllValidator + +} // namespace + +template<> +struct ModulatorCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_RING_MODULATOR_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void ModulatorCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool ModulatorCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && mEaxProps.mModulator.flFrequency == props.mModulator.flFrequency + && mEaxProps.mModulator.flHighPassCutOff == props.mModulator.flHighPassCutOff + && mEaxProps.mModulator.ulWaveform == props.mModulator.ulWaveform) + return false; + + mEaxProps = props; + + auto get_waveform = [](unsigned long form) + { + if(form == EAX_RINGMODULATOR_SINUSOID) + return ModulatorWaveform::Sinusoid; + if(form == EAX_RINGMODULATOR_SAWTOOTH) + return ModulatorWaveform::Sawtooth; + if(form == EAX_RINGMODULATOR_SQUARE) + return ModulatorWaveform::Square; + return ModulatorWaveform::Sinusoid; + }; + + mAlProps.Modulator.Frequency = props.mModulator.flFrequency; + mAlProps.Modulator.HighPassCutoff = props.mModulator.flHighPassCutOff; + mAlProps.Modulator.Waveform = get_waveform(props.mModulator.ulWaveform); + + return true; +} + +template<> +void ModulatorCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Modulator; + props.mModulator.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + props.mModulator.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + props.mModulator.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; +} + +template<> +void ModulatorCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: break; + case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value<Exception>(props.mModulator); break; + case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.mModulator.flFrequency); break; + case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.mModulator.flHighPassCutOff); break; + case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(props.mModulator.ulWaveform); break; + default: fail_unknown_property_id(); + } +} + +template<> +void ModulatorCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch (call.get_property_id()) + { + case EAXRINGMODULATOR_NONE: break; + case EAXRINGMODULATOR_ALLPARAMETERS: defer<AllValidator>(call, props.mModulator); break; + case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.mModulator.flFrequency); break; + case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.mModulator.flHighPassCutOff); break; + case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.mModulator.ulWaveform); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/null.cpp b/al/effects/null.cpp new file mode 100644 index 00000000..0bbc183a --- /dev/null +++ b/al/effects/null.cpp @@ -0,0 +1,149 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "al/eax/exception.h" +#endif // ALSOFT_EAX + + +namespace { + +void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Null_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ + switch(param) + { + default: + Null_setParami(props, param, vals[0]); + } +} +void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Null_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ + switch(param) + { + default: + Null_setParamf(props, param, vals[0]); + } +} + +void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void Null_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ + switch(param) + { + default: + Null_getParami(props, param, vals); + } +} +void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void Null_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ + switch(param) + { + default: + Null_getParamf(props, param, vals); + } +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Null); + +const EffectProps NullEffectProps{genDefaultProps()}; + + +#ifdef ALSOFT_EAX +namespace { + +using NullCommitter = EaxCommitter<EaxNullCommitter>; + +} // namespace + +template<> +struct NullCommitter::Exception : public EaxException +{ + explicit Exception(const char *message) : EaxException{"EAX_NULL_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void NullCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool NullCommitter::commit(const EaxEffectProps &props) +{ + const bool ret{props.mType != mEaxProps.mType}; + mEaxProps = props; + return ret; +} + +template<> +void NullCommitter::SetDefaults(EaxEffectProps &props) +{ + props = EaxEffectProps{}; + props.mType = EaxEffectType::None; +} + +template<> +void NullCommitter::Get(const EaxCall &call, const EaxEffectProps&) +{ + if(call.get_property_id() != 0) + fail_unknown_property_id(); +} + +template<> +void NullCommitter::Set(const EaxCall &call, EaxEffectProps&) +{ + if(call.get_property_id() != 0) + fail_unknown_property_id(); +} + +#endif // ALSOFT_EAX diff --git a/al/effects/pshifter.cpp b/al/effects/pshifter.cpp new file mode 100644 index 00000000..634eb186 --- /dev/null +++ b/al/effects/pshifter.cpp @@ -0,0 +1,191 @@ + +#include "config.h" + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Pshifter_setParamf(EffectProps*, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void Pshifter_setParamfv(EffectProps*, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", + param}; +} + +void Pshifter_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_PITCH_SHIFTER_COARSE_TUNE: + if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) + throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; + props->Pshifter.CoarseTune = val; + break; + + case AL_PITCH_SHIFTER_FINE_TUNE: + if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) + throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; + props->Pshifter.FineTune = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", + param}; + } +} +void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Pshifter_setParami(props, param, vals[0]); } + +void Pshifter_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_PITCH_SHIFTER_COARSE_TUNE: + *val = props->Pshifter.CoarseTune; + break; + case AL_PITCH_SHIFTER_FINE_TUNE: + *val = props->Pshifter.FineTune; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", + param}; + } +} +void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Pshifter_getParami(props, param, vals); } + +void Pshifter_getParamf(const EffectProps*, ALenum param, float*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void Pshifter_getParamfv(const EffectProps*, ALenum param, float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", + param}; +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; + props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Pshifter); + +const EffectProps PshifterEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using PitchShifterCommitter = EaxCommitter<EaxPitchShifterCommitter>; + +struct CoarseTuneValidator { + void operator()(long lCoarseTune) const + { + eax_validate_range<PitchShifterCommitter::Exception>( + "Coarse Tune", + lCoarseTune, + EAXPITCHSHIFTER_MINCOARSETUNE, + EAXPITCHSHIFTER_MAXCOARSETUNE); + } +}; // CoarseTuneValidator + +struct FineTuneValidator { + void operator()(long lFineTune) const + { + eax_validate_range<PitchShifterCommitter::Exception>( + "Fine Tune", + lFineTune, + EAXPITCHSHIFTER_MINFINETUNE, + EAXPITCHSHIFTER_MAXFINETUNE); + } +}; // FineTuneValidator + +struct AllValidator { + void operator()(const EAXPITCHSHIFTERPROPERTIES& all) const + { + CoarseTuneValidator{}(all.lCoarseTune); + FineTuneValidator{}(all.lFineTune); + } +}; // AllValidator + +} // namespace + +template<> +struct PitchShifterCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_PITCH_SHIFTER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void PitchShifterCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool PitchShifterCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && mEaxProps.mPitchShifter.lCoarseTune == props.mPitchShifter.lCoarseTune + && mEaxProps.mPitchShifter.lFineTune == props.mPitchShifter.lFineTune) + return false; + + mEaxProps = props; + + mAlProps.Pshifter.CoarseTune = static_cast<int>(mEaxProps.mPitchShifter.lCoarseTune); + mAlProps.Pshifter.FineTune = static_cast<int>(mEaxProps.mPitchShifter.lFineTune); + + return true; +} + +template<> +void PitchShifterCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::PitchShifter; + props.mPitchShifter.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; + props.mPitchShifter.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; +} + +template<> +void PitchShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: break; + case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mPitchShifter); break; + case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.mPitchShifter.lCoarseTune); break; + case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.mPitchShifter.lFineTune); break; + default: fail_unknown_property_id(); + } +} + +template<> +void PitchShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXPITCHSHIFTER_NONE: break; + case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mPitchShifter); break; + case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.mPitchShifter.lCoarseTune); break; + case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.mPitchShifter.lFineTune); break; + default: fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX diff --git a/al/effects/reverb.cpp b/al/effects/reverb.cpp new file mode 100644 index 00000000..440d7b4e --- /dev/null +++ b/al/effects/reverb.cpp @@ -0,0 +1,1499 @@ + +#include "config.h" + +#include <cmath> + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include <cassert> +#include "alnumeric.h" +#include "AL/efx-presets.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +void Reverb_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + props->Reverb.DecayHFLimit = val != AL_FALSE; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; + } +} +void Reverb_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ Reverb_setParami(props, param, vals[0]); } +void Reverb_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: + if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + props->Reverb.Density = val; + break; + + case AL_EAXREVERB_DIFFUSION: + if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + props->Reverb.Diffusion = val; + break; + + case AL_EAXREVERB_GAIN: + if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + props->Reverb.Gain = val; + break; + + case AL_EAXREVERB_GAINHF: + if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + props->Reverb.GainHF = val; + break; + + case AL_EAXREVERB_GAINLF: + if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; + props->Reverb.GainLF = val; + break; + + case AL_EAXREVERB_DECAY_TIME: + if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + props->Reverb.DecayTime = val; + break; + + case AL_EAXREVERB_DECAY_HFRATIO: + if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + props->Reverb.DecayHFRatio = val; + break; + + case AL_EAXREVERB_DECAY_LFRATIO: + if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; + props->Reverb.DecayLFRatio = val; + break; + + case AL_EAXREVERB_REFLECTIONS_GAIN: + if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + props->Reverb.ReflectionsGain = val; + break; + + case AL_EAXREVERB_REFLECTIONS_DELAY: + if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + props->Reverb.ReflectionsDelay = val; + break; + + case AL_EAXREVERB_LATE_REVERB_GAIN: + if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + props->Reverb.LateReverbGain = val; + break; + + case AL_EAXREVERB_LATE_REVERB_DELAY: + if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + props->Reverb.LateReverbDelay = val; + break; + + case AL_EAXREVERB_ECHO_TIME: + if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; + props->Reverb.EchoTime = val; + break; + + case AL_EAXREVERB_ECHO_DEPTH: + if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; + props->Reverb.EchoDepth = val; + break; + + case AL_EAXREVERB_MODULATION_TIME: + if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; + props->Reverb.ModulationTime = val; + break; + + case AL_EAXREVERB_MODULATION_DEPTH: + if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; + props->Reverb.ModulationDepth = val; + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + props->Reverb.AirAbsorptionGainHF = val; + break; + + case AL_EAXREVERB_HFREFERENCE: + if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; + props->Reverb.HFReference = val; + break; + + case AL_EAXREVERB_LFREFERENCE: + if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; + props->Reverb.LFReference = val; + break; + + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + props->Reverb.RoomRolloffFactor = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + } +} +void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ + switch(param) + { + case AL_EAXREVERB_REFLECTIONS_PAN: + if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; + props->Reverb.ReflectionsPan[0] = vals[0]; + props->Reverb.ReflectionsPan[1] = vals[1]; + props->Reverb.ReflectionsPan[2] = vals[2]; + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; + props->Reverb.LateReverbPan[0] = vals[0]; + props->Reverb.LateReverbPan[1] = vals[1]; + props->Reverb.LateReverbPan[2] = vals[2]; + break; + + default: + Reverb_setParamf(props, param, vals[0]); + break; + } +} + +void Reverb_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_EAXREVERB_DECAY_HFLIMIT: + *val = props->Reverb.DecayHFLimit; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; + } +} +void Reverb_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ Reverb_getParami(props, param, vals); } +void Reverb_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: + *val = props->Reverb.Density; + break; + + case AL_EAXREVERB_DIFFUSION: + *val = props->Reverb.Diffusion; + break; + + case AL_EAXREVERB_GAIN: + *val = props->Reverb.Gain; + break; + + case AL_EAXREVERB_GAINHF: + *val = props->Reverb.GainHF; + break; + + case AL_EAXREVERB_GAINLF: + *val = props->Reverb.GainLF; + break; + + case AL_EAXREVERB_DECAY_TIME: + *val = props->Reverb.DecayTime; + break; + + case AL_EAXREVERB_DECAY_HFRATIO: + *val = props->Reverb.DecayHFRatio; + break; + + case AL_EAXREVERB_DECAY_LFRATIO: + *val = props->Reverb.DecayLFRatio; + break; + + case AL_EAXREVERB_REFLECTIONS_GAIN: + *val = props->Reverb.ReflectionsGain; + break; + + case AL_EAXREVERB_REFLECTIONS_DELAY: + *val = props->Reverb.ReflectionsDelay; + break; + + case AL_EAXREVERB_LATE_REVERB_GAIN: + *val = props->Reverb.LateReverbGain; + break; + + case AL_EAXREVERB_LATE_REVERB_DELAY: + *val = props->Reverb.LateReverbDelay; + break; + + case AL_EAXREVERB_ECHO_TIME: + *val = props->Reverb.EchoTime; + break; + + case AL_EAXREVERB_ECHO_DEPTH: + *val = props->Reverb.EchoDepth; + break; + + case AL_EAXREVERB_MODULATION_TIME: + *val = props->Reverb.ModulationTime; + break; + + case AL_EAXREVERB_MODULATION_DEPTH: + *val = props->Reverb.ModulationDepth; + break; + + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: + *val = props->Reverb.AirAbsorptionGainHF; + break; + + case AL_EAXREVERB_HFREFERENCE: + *val = props->Reverb.HFReference; + break; + + case AL_EAXREVERB_LFREFERENCE: + *val = props->Reverb.LFReference; + break; + + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: + *val = props->Reverb.RoomRolloffFactor; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + } +} +void Reverb_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ + switch(param) + { + case AL_EAXREVERB_REFLECTIONS_PAN: + vals[0] = props->Reverb.ReflectionsPan[0]; + vals[1] = props->Reverb.ReflectionsPan[1]; + vals[2] = props->Reverb.ReflectionsPan[2]; + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + vals[0] = props->Reverb.LateReverbPan[0]; + vals[1] = props->Reverb.LateReverbPan[1]; + vals[2] = props->Reverb.LateReverbPan[2]; + break; + + default: + Reverb_getParamf(props, param, vals); + break; + } +} + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; + props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; + props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; + props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; + props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; + props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; + props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; + props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; + props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; + props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; + props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; + props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; + props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; + props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; + props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; + props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; + props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; + props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; + props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + + +void StdReverb_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "Reverb decay hflimit out of range"}; + props->Reverb.DecayHFLimit = val != AL_FALSE; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + } +} +void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals) +{ StdReverb_setParami(props, param, vals[0]); } +void StdReverb_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_REVERB_DENSITY: + if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) + throw effect_exception{AL_INVALID_VALUE, "Reverb density out of range"}; + props->Reverb.Density = val; + break; + + case AL_REVERB_DIFFUSION: + if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) + throw effect_exception{AL_INVALID_VALUE, "Reverb diffusion out of range"}; + props->Reverb.Diffusion = val; + break; + + case AL_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Reverb gain out of range"}; + props->Reverb.Gain = val; + break; + + case AL_REVERB_GAINHF: + if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "Reverb gainhf out of range"}; + props->Reverb.GainHF = val; + break; + + case AL_REVERB_DECAY_TIME: + if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) + throw effect_exception{AL_INVALID_VALUE, "Reverb decay time out of range"}; + props->Reverb.DecayTime = val; + break; + + case AL_REVERB_DECAY_HFRATIO: + if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "Reverb decay hfratio out of range"}; + props->Reverb.DecayHFRatio = val; + break; + + case AL_REVERB_REFLECTIONS_GAIN: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Reverb reflections gain out of range"}; + props->Reverb.ReflectionsGain = val; + break; + + case AL_REVERB_REFLECTIONS_DELAY: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Reverb reflections delay out of range"}; + props->Reverb.ReflectionsDelay = val; + break; + + case AL_REVERB_LATE_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb gain out of range"}; + props->Reverb.LateReverbGain = val; + break; + + case AL_REVERB_LATE_REVERB_DELAY: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb delay out of range"}; + props->Reverb.LateReverbDelay = val; + break; + + case AL_REVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "Reverb air absorption gainhf out of range"}; + props->Reverb.AirAbsorptionGainHF = val; + break; + + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) + throw effect_exception{AL_INVALID_VALUE, "Reverb room rolloff factor out of range"}; + props->Reverb.RoomRolloffFactor = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; + } +} +void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ StdReverb_setParamf(props, param, vals[0]); } + +void StdReverb_getParami(const EffectProps *props, ALenum param, int *val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + *val = props->Reverb.DecayHFLimit; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + } +} +void StdReverb_getParamiv(const EffectProps *props, ALenum param, int *vals) +{ StdReverb_getParami(props, param, vals); } +void StdReverb_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_REVERB_DENSITY: + *val = props->Reverb.Density; + break; + + case AL_REVERB_DIFFUSION: + *val = props->Reverb.Diffusion; + break; + + case AL_REVERB_GAIN: + *val = props->Reverb.Gain; + break; + + case AL_REVERB_GAINHF: + *val = props->Reverb.GainHF; + break; + + case AL_REVERB_DECAY_TIME: + *val = props->Reverb.DecayTime; + break; + + case AL_REVERB_DECAY_HFRATIO: + *val = props->Reverb.DecayHFRatio; + break; + + case AL_REVERB_REFLECTIONS_GAIN: + *val = props->Reverb.ReflectionsGain; + break; + + case AL_REVERB_REFLECTIONS_DELAY: + *val = props->Reverb.ReflectionsDelay; + break; + + case AL_REVERB_LATE_REVERB_GAIN: + *val = props->Reverb.LateReverbGain; + break; + + case AL_REVERB_LATE_REVERB_DELAY: + *val = props->Reverb.LateReverbDelay; + break; + + case AL_REVERB_AIR_ABSORPTION_GAINHF: + *val = props->Reverb.AirAbsorptionGainHF; + break; + + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + *val = props->Reverb.RoomRolloffFactor; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; + } +} +void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ StdReverb_getParamf(props, param, vals); } + +EffectProps genDefaultStdProps() noexcept +{ + EffectProps props{}; + props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY; + props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; + props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN; + props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF; + props.Reverb.GainLF = 1.0f; + props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; + props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; + props.Reverb.DecayLFRatio = 1.0f; + props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; + props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; + props.Reverb.ReflectionsPan[0] = 0.0f; + props.Reverb.ReflectionsPan[1] = 0.0f; + props.Reverb.ReflectionsPan[2] = 0.0f; + props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; + props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; + props.Reverb.LateReverbPan[0] = 0.0f; + props.Reverb.LateReverbPan[1] = 0.0f; + props.Reverb.LateReverbPan[2] = 0.0f; + props.Reverb.EchoTime = 0.25f; + props.Reverb.EchoDepth = 0.0f; + props.Reverb.ModulationTime = 0.25f; + props.Reverb.ModulationDepth = 0.0f; + props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.Reverb.HFReference = 5000.0f; + props.Reverb.LFReference = 250.0f; + props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Reverb); + +const EffectProps ReverbEffectProps{genDefaultProps()}; + +DEFINE_ALEFFECT_VTABLE(StdReverb); + +const EffectProps StdReverbEffectProps{genDefaultStdProps()}; + +#ifdef ALSOFT_EAX +namespace { + +class EaxReverbEffectException : public EaxException +{ +public: + explicit EaxReverbEffectException(const char* message) + : EaxException{"EAX_REVERB_EFFECT", message} + {} +}; // EaxReverbEffectException + +struct EnvironmentValidator1 { + void operator()(unsigned long ulEnvironment) const + { + eax_validate_range<EaxReverbEffectException>( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + EAX1REVERB_MAXENVIRONMENT); + } +}; // EnvironmentValidator1 + +struct VolumeValidator { + void operator()(float volume) const + { + eax_validate_range<EaxReverbEffectException>( + "Volume", + volume, + EAX1REVERB_MINVOLUME, + EAX1REVERB_MAXVOLUME); + } +}; // VolumeValidator + +struct DecayTimeValidator { + void operator()(float flDecayTime) const + { + eax_validate_range<EaxReverbEffectException>( + "Decay Time", + flDecayTime, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + } +}; // DecayTimeValidator + +struct DampingValidator { + void operator()(float damping) const + { + eax_validate_range<EaxReverbEffectException>( + "Damping", + damping, + EAX1REVERB_MINDAMPING, + EAX1REVERB_MAXDAMPING); + } +}; // DampingValidator + +struct AllValidator1 { + void operator()(const EAX_REVERBPROPERTIES& all) const + { + EnvironmentValidator1{}(all.environment); + VolumeValidator{}(all.fVolume); + DecayTimeValidator{}(all.fDecayTime_sec); + DampingValidator{}(all.fDamping); + } +}; // AllValidator1 + +struct RoomValidator { + void operator()(long lRoom) const + { + eax_validate_range<EaxReverbEffectException>( + "Room", + lRoom, + EAXREVERB_MINROOM, + EAXREVERB_MAXROOM); + } +}; // RoomValidator + +struct RoomHFValidator { + void operator()(long lRoomHF) const + { + eax_validate_range<EaxReverbEffectException>( + "Room HF", + lRoomHF, + EAXREVERB_MINROOMHF, + EAXREVERB_MAXROOMHF); + } +}; // RoomHFValidator + +struct RoomRolloffFactorValidator { + void operator()(float flRoomRolloffFactor) const + { + eax_validate_range<EaxReverbEffectException>( + "Room Rolloff Factor", + flRoomRolloffFactor, + EAXREVERB_MINROOMROLLOFFFACTOR, + EAXREVERB_MAXROOMROLLOFFFACTOR); + } +}; // RoomRolloffFactorValidator + +struct DecayHFRatioValidator { + void operator()(float flDecayHFRatio) const + { + eax_validate_range<EaxReverbEffectException>( + "Decay HF Ratio", + flDecayHFRatio, + EAXREVERB_MINDECAYHFRATIO, + EAXREVERB_MAXDECAYHFRATIO); + } +}; // DecayHFRatioValidator + +struct ReflectionsValidator { + void operator()(long lReflections) const + { + eax_validate_range<EaxReverbEffectException>( + "Reflections", + lReflections, + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + } +}; // ReflectionsValidator + +struct ReflectionsDelayValidator { + void operator()(float flReflectionsDelay) const + { + eax_validate_range<EaxReverbEffectException>( + "Reflections Delay", + flReflectionsDelay, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + } +}; // ReflectionsDelayValidator + +struct ReverbValidator { + void operator()(long lReverb) const + { + eax_validate_range<EaxReverbEffectException>( + "Reverb", + lReverb, + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + } +}; // ReverbValidator + +struct ReverbDelayValidator { + void operator()(float flReverbDelay) const + { + eax_validate_range<EaxReverbEffectException>( + "Reverb Delay", + flReverbDelay, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + } +}; // ReverbDelayValidator + +struct EnvironmentSizeValidator { + void operator()(float flEnvironmentSize) const + { + eax_validate_range<EaxReverbEffectException>( + "Environment Size", + flEnvironmentSize, + EAXREVERB_MINENVIRONMENTSIZE, + EAXREVERB_MAXENVIRONMENTSIZE); + } +}; // EnvironmentSizeValidator + +struct EnvironmentDiffusionValidator { + void operator()(float flEnvironmentDiffusion) const + { + eax_validate_range<EaxReverbEffectException>( + "Environment Diffusion", + flEnvironmentDiffusion, + EAXREVERB_MINENVIRONMENTDIFFUSION, + EAXREVERB_MAXENVIRONMENTDIFFUSION); + } +}; // EnvironmentDiffusionValidator + +struct AirAbsorptionHFValidator { + void operator()(float flAirAbsorptionHF) const + { + eax_validate_range<EaxReverbEffectException>( + "Air Absorbtion HF", + flAirAbsorptionHF, + EAXREVERB_MINAIRABSORPTIONHF, + EAXREVERB_MAXAIRABSORPTIONHF); + } +}; // AirAbsorptionHFValidator + +struct FlagsValidator2 { + void operator()(unsigned long ulFlags) const + { + eax_validate_range<EaxReverbEffectException>( + "Flags", + ulFlags, + 0UL, + ~EAX2LISTENERFLAGS_RESERVED); + } +}; // FlagsValidator2 + +struct AllValidator2 { + void operator()(const EAX20LISTENERPROPERTIES& all) const + { + RoomValidator{}(all.lRoom); + RoomHFValidator{}(all.lRoomHF); + RoomRolloffFactorValidator{}(all.flRoomRolloffFactor); + DecayTimeValidator{}(all.flDecayTime); + DecayHFRatioValidator{}(all.flDecayHFRatio); + ReflectionsValidator{}(all.lReflections); + ReflectionsDelayValidator{}(all.flReflectionsDelay); + ReverbValidator{}(all.lReverb); + ReverbDelayValidator{}(all.flReverbDelay); + EnvironmentValidator1{}(all.dwEnvironment); + EnvironmentSizeValidator{}(all.flEnvironmentSize); + EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion); + AirAbsorptionHFValidator{}(all.flAirAbsorptionHF); + FlagsValidator2{}(all.dwFlags); + } +}; // AllValidator2 + +struct EnvironmentValidator3 { + void operator()(unsigned long ulEnvironment) const + { + eax_validate_range<EaxReverbEffectException>( + "Environment", + ulEnvironment, + EAXREVERB_MINENVIRONMENT, + EAX30REVERB_MAXENVIRONMENT); + } +}; // EnvironmentValidator1 + +struct RoomLFValidator { + void operator()(long lRoomLF) const + { + eax_validate_range<EaxReverbEffectException>( + "Room LF", + lRoomLF, + EAXREVERB_MINROOMLF, + EAXREVERB_MAXROOMLF); + } +}; // RoomLFValidator + +struct DecayLFRatioValidator { + void operator()(float flDecayLFRatio) const + { + eax_validate_range<EaxReverbEffectException>( + "Decay LF Ratio", + flDecayLFRatio, + EAXREVERB_MINDECAYLFRATIO, + EAXREVERB_MAXDECAYLFRATIO); + } +}; // DecayLFRatioValidator + +struct VectorValidator { + void operator()(const EAXVECTOR&) const + {} +}; // VectorValidator + +struct EchoTimeValidator { + void operator()(float flEchoTime) const + { + eax_validate_range<EaxReverbEffectException>( + "Echo Time", + flEchoTime, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); + } +}; // EchoTimeValidator + +struct EchoDepthValidator { + void operator()(float flEchoDepth) const + { + eax_validate_range<EaxReverbEffectException>( + "Echo Depth", + flEchoDepth, + EAXREVERB_MINECHODEPTH, + EAXREVERB_MAXECHODEPTH); + } +}; // EchoDepthValidator + +struct ModulationTimeValidator { + void operator()(float flModulationTime) const + { + eax_validate_range<EaxReverbEffectException>( + "Modulation Time", + flModulationTime, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); + } +}; // ModulationTimeValidator + +struct ModulationDepthValidator { + void operator()(float flModulationDepth) const + { + eax_validate_range<EaxReverbEffectException>( + "Modulation Depth", + flModulationDepth, + EAXREVERB_MINMODULATIONDEPTH, + EAXREVERB_MAXMODULATIONDEPTH); + } +}; // ModulationDepthValidator + +struct HFReferenceValidator { + void operator()(float flHFReference) const + { + eax_validate_range<EaxReverbEffectException>( + "HF Reference", + flHFReference, + EAXREVERB_MINHFREFERENCE, + EAXREVERB_MAXHFREFERENCE); + } +}; // HFReferenceValidator + +struct LFReferenceValidator { + void operator()(float flLFReference) const + { + eax_validate_range<EaxReverbEffectException>( + "LF Reference", + flLFReference, + EAXREVERB_MINLFREFERENCE, + EAXREVERB_MAXLFREFERENCE); + } +}; // LFReferenceValidator + +struct FlagsValidator3 { + void operator()(unsigned long ulFlags) const + { + eax_validate_range<EaxReverbEffectException>( + "Flags", + ulFlags, + 0UL, + ~EAXREVERBFLAGS_RESERVED); + } +}; // FlagsValidator3 + +struct AllValidator3 { + void operator()(const EAXREVERBPROPERTIES& all) const + { + EnvironmentValidator3{}(all.ulEnvironment); + EnvironmentSizeValidator{}(all.flEnvironmentSize); + EnvironmentDiffusionValidator{}(all.flEnvironmentDiffusion); + RoomValidator{}(all.lRoom); + RoomHFValidator{}(all.lRoomHF); + RoomLFValidator{}(all.lRoomLF); + DecayTimeValidator{}(all.flDecayTime); + DecayHFRatioValidator{}(all.flDecayHFRatio); + DecayLFRatioValidator{}(all.flDecayLFRatio); + ReflectionsValidator{}(all.lReflections); + ReflectionsDelayValidator{}(all.flReflectionsDelay); + VectorValidator{}(all.vReflectionsPan); + ReverbValidator{}(all.lReverb); + ReverbDelayValidator{}(all.flReverbDelay); + VectorValidator{}(all.vReverbPan); + EchoTimeValidator{}(all.flEchoTime); + EchoDepthValidator{}(all.flEchoDepth); + ModulationTimeValidator{}(all.flModulationTime); + ModulationDepthValidator{}(all.flModulationDepth); + AirAbsorptionHFValidator{}(all.flAirAbsorptionHF); + HFReferenceValidator{}(all.flHFReference); + LFReferenceValidator{}(all.flLFReference); + RoomRolloffFactorValidator{}(all.flRoomRolloffFactor); + FlagsValidator3{}(all.ulFlags); + } +}; // AllValidator3 + +struct EnvironmentDeferrer2 { + void operator()(EAX20LISTENERPROPERTIES& props, unsigned long dwEnvironment) const + { + props = EAX2REVERB_PRESETS[dwEnvironment]; + } +}; // EnvironmentDeferrer2 + +struct EnvironmentSizeDeferrer2 { + void operator()(EAX20LISTENERPROPERTIES& props, float flEnvironmentSize) const + { + if (props.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / props.flEnvironmentSize; + props.flEnvironmentSize = flEnvironmentSize; + + if ((props.dwFlags & EAX2LISTENERFLAGS_DECAYTIMESCALE) != 0) + { + props.flDecayTime = clamp( + props.flDecayTime * scale, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSSCALE) != 0 && + (props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.lReflections = clamp( + props.lReflections - static_cast<long>(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.flReflectionsDelay = clamp( + props.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((props.dwFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + + props.lReverb = clamp( + props.lReverb - static_cast<long>(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + } + + if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBDELAYSCALE) != 0) + { + props.flReverbDelay = clamp( + props.flReverbDelay * scale, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + } + } +}; // EnvironmentSizeDeferrer2 + +struct EnvironmentDeferrer3 { + void operator()(EAXREVERBPROPERTIES& props, unsigned long ulEnvironment) const + { + if (ulEnvironment == EAX_ENVIRONMENT_UNDEFINED) + { + props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; + return; + } + + props = EAXREVERB_PRESETS[ulEnvironment]; + } +}; // EnvironmentDeferrer3 + +struct EnvironmentSizeDeferrer3 { + void operator()(EAXREVERBPROPERTIES& props, float flEnvironmentSize) const + { + if (props.flEnvironmentSize == flEnvironmentSize) + { + return; + } + + const auto scale = flEnvironmentSize / props.flEnvironmentSize; + props.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; + props.flEnvironmentSize = flEnvironmentSize; + + if ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) + { + props.flDecayTime = clamp( + props.flDecayTime * scale, + EAXREVERB_MINDECAYTIME, + EAXREVERB_MAXDECAYTIME); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0 && + (props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.lReflections = clamp( + props.lReflections - static_cast<long>(gain_to_level_mb(scale)), + EAXREVERB_MINREFLECTIONS, + EAXREVERB_MAXREFLECTIONS); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) + { + props.flReflectionsDelay = clamp( + props.flReflectionsDelay * scale, + EAXREVERB_MINREFLECTIONSDELAY, + EAXREVERB_MAXREFLECTIONSDELAY); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) + { + const auto log_scalar = ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; + props.lReverb = clamp( + props.lReverb - static_cast<long>(std::log10(scale) * log_scalar), + EAXREVERB_MINREVERB, + EAXREVERB_MAXREVERB); + } + + if ((props.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) + { + props.flReverbDelay = clamp( + props.flReverbDelay * scale, + EAXREVERB_MINREVERBDELAY, + EAXREVERB_MAXREVERBDELAY); + } + + if ((props.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) + { + props.flEchoTime = clamp( + props.flEchoTime * scale, + EAXREVERB_MINECHOTIME, + EAXREVERB_MAXECHOTIME); + } + + if ((props.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) + { + props.flModulationTime = clamp( + props.flModulationTime * scale, + EAXREVERB_MINMODULATIONTIME, + EAXREVERB_MAXMODULATIONTIME); + } + } +}; // EnvironmentSizeDeferrer3 + +} // namespace + + +struct EaxReverbCommitter::Exception : public EaxReverbEffectException +{ + using EaxReverbEffectException::EaxReverbEffectException; +}; + +[[noreturn]] void EaxReverbCommitter::fail(const char* message) +{ + throw Exception{message}; +} + +void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept +{ + assert(src.environment <= EAX1REVERB_MAXENVIRONMENT); + dst.mType = EaxEffectType::Reverb; + dst.mReverb = EAXREVERB_PRESETS[src.environment]; + dst.mReverb.flDecayTime = src.fDecayTime_sec; + dst.mReverb.flDecayHFRatio = src.fDamping; + dst.mReverb.lReverb = mini(static_cast<int>(gain_to_level_mb(src.fVolume)), 0); +} + +void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept +{ + assert(src.dwEnvironment <= EAX1REVERB_MAXENVIRONMENT); + const auto& env = EAXREVERB_PRESETS[src.dwEnvironment]; + dst.mType = EaxEffectType::Reverb; + dst.mReverb.ulEnvironment = src.dwEnvironment; + dst.mReverb.flEnvironmentSize = src.flEnvironmentSize; + dst.mReverb.flEnvironmentDiffusion = src.flEnvironmentDiffusion; + dst.mReverb.lRoom = src.lRoom; + dst.mReverb.lRoomHF = src.lRoomHF; + dst.mReverb.lRoomLF = env.lRoomLF; + dst.mReverb.flDecayTime = src.flDecayTime; + dst.mReverb.flDecayHFRatio = src.flDecayHFRatio; + dst.mReverb.flDecayLFRatio = env.flDecayLFRatio; + dst.mReverb.lReflections = src.lReflections; + dst.mReverb.flReflectionsDelay = src.flReflectionsDelay; + dst.mReverb.vReflectionsPan = env.vReflectionsPan; + dst.mReverb.lReverb = src.lReverb; + dst.mReverb.flReverbDelay = src.flReverbDelay; + dst.mReverb.vReverbPan = env.vReverbPan; + dst.mReverb.flEchoTime = env.flEchoTime; + dst.mReverb.flEchoDepth = env.flEchoDepth; + dst.mReverb.flModulationTime = env.flModulationTime; + dst.mReverb.flModulationDepth = env.flModulationDepth; + dst.mReverb.flAirAbsorptionHF = src.flAirAbsorptionHF; + dst.mReverb.flHFReference = env.flHFReference; + dst.mReverb.flLFReference = env.flLFReference; + dst.mReverb.flRoomRolloffFactor = src.flRoomRolloffFactor; + dst.mReverb.ulFlags = src.dwFlags; +} + +void EaxReverbCommitter::translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept +{ + dst.mType = EaxEffectType::Reverb; + dst.mReverb = src; +} + +bool EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props) +{ + EaxEffectProps dst{}; + translate(props, dst); + return commit(dst); +} + +bool EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props) +{ + EaxEffectProps dst{}; + translate(props, dst); + return commit(dst); +} + +bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) +{ + EaxEffectProps dst{}; + translate(props, dst); + return commit(dst); +} + +bool EaxReverbCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && memcmp(&props.mReverb, &mEaxProps.mReverb, sizeof(mEaxProps.mReverb)) == 0) + return false; + + mEaxProps = props; + + const auto size = props.mReverb.flEnvironmentSize; + const auto density = (size * size * size) / 16.0F; + mAlProps.Reverb.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY); + mAlProps.Reverb.Diffusion = props.mReverb.flEnvironmentDiffusion; + mAlProps.Reverb.Gain = level_mb_to_gain(static_cast<float>(props.mReverb.lRoom)); + mAlProps.Reverb.GainHF = level_mb_to_gain(static_cast<float>(props.mReverb.lRoomHF)); + mAlProps.Reverb.GainLF = level_mb_to_gain(static_cast<float>(props.mReverb.lRoomLF)); + mAlProps.Reverb.DecayTime = props.mReverb.flDecayTime; + mAlProps.Reverb.DecayHFRatio = props.mReverb.flDecayHFRatio; + mAlProps.Reverb.DecayLFRatio = mEaxProps.mReverb.flDecayLFRatio; + mAlProps.Reverb.ReflectionsGain = level_mb_to_gain(static_cast<float>(props.mReverb.lReflections)); + mAlProps.Reverb.ReflectionsDelay = props.mReverb.flReflectionsDelay; + mAlProps.Reverb.ReflectionsPan[0] = props.mReverb.vReflectionsPan.x; + mAlProps.Reverb.ReflectionsPan[1] = props.mReverb.vReflectionsPan.y; + mAlProps.Reverb.ReflectionsPan[2] = props.mReverb.vReflectionsPan.z; + mAlProps.Reverb.LateReverbGain = level_mb_to_gain(static_cast<float>(props.mReverb.lReverb)); + mAlProps.Reverb.LateReverbDelay = props.mReverb.flReverbDelay; + mAlProps.Reverb.LateReverbPan[0] = props.mReverb.vReverbPan.x; + mAlProps.Reverb.LateReverbPan[1] = props.mReverb.vReverbPan.y; + mAlProps.Reverb.LateReverbPan[2] = props.mReverb.vReverbPan.z; + mAlProps.Reverb.EchoTime = props.mReverb.flEchoTime; + mAlProps.Reverb.EchoDepth = props.mReverb.flEchoDepth; + mAlProps.Reverb.ModulationTime = props.mReverb.flModulationTime; + mAlProps.Reverb.ModulationDepth = props.mReverb.flModulationDepth; + mAlProps.Reverb.AirAbsorptionGainHF = level_mb_to_gain(props.mReverb.flAirAbsorptionHF); + mAlProps.Reverb.HFReference = props.mReverb.flHFReference; + mAlProps.Reverb.LFReference = props.mReverb.flLFReference; + mAlProps.Reverb.RoomRolloffFactor = props.mReverb.flRoomRolloffFactor; + mAlProps.Reverb.DecayHFLimit = ((props.mReverb.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + return true; +} + +void EaxReverbCommitter::SetDefaults(EAX_REVERBPROPERTIES &props) +{ + props = EAX1REVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; +} + +void EaxReverbCommitter::SetDefaults(EAX20LISTENERPROPERTIES &props) +{ + props = EAX2REVERB_PRESETS[EAX2_ENVIRONMENT_GENERIC]; + props.lRoom = -10'000L; +} + +void EaxReverbCommitter::SetDefaults(EAXREVERBPROPERTIES &props) +{ + props = EAXREVERB_PRESETS[EAX_ENVIRONMENT_GENERIC]; +} + +void EaxReverbCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::Reverb; + SetDefaults(props.mReverb); +} + + +void EaxReverbCommitter::Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: call.set_value<Exception>(props); break; + case DSPROPERTY_EAX_ENVIRONMENT: call.set_value<Exception>(props.environment); break; + case DSPROPERTY_EAX_VOLUME: call.set_value<Exception>(props.fVolume); break; + case DSPROPERTY_EAX_DECAYTIME: call.set_value<Exception>(props.fDecayTime_sec); break; + case DSPROPERTY_EAX_DAMPING: call.set_value<Exception>(props.fDamping); break; + default: fail_unknown_property_id(); + } +} + +void EaxReverbCommitter::Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case DSPROPERTY_EAX20LISTENER_NONE: break; + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: call.set_value<Exception>(props); break; + case DSPROPERTY_EAX20LISTENER_ROOM: call.set_value<Exception>(props.lRoom); break; + case DSPROPERTY_EAX20LISTENER_ROOMHF: call.set_value<Exception>(props.lRoomHF); break; + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: call.set_value<Exception>(props.flRoomRolloffFactor); break; + case DSPROPERTY_EAX20LISTENER_DECAYTIME: call.set_value<Exception>(props.flDecayTime); break; + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: call.set_value<Exception>(props.flDecayHFRatio); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: call.set_value<Exception>(props.lReflections); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: call.set_value<Exception>(props.flReflectionsDelay); break; + case DSPROPERTY_EAX20LISTENER_REVERB: call.set_value<Exception>(props.lReverb); break; + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: call.set_value<Exception>(props.flReverbDelay); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: call.set_value<Exception>(props.dwEnvironment); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: call.set_value<Exception>(props.flEnvironmentSize); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: call.set_value<Exception>(props.flEnvironmentDiffusion); break; + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: call.set_value<Exception>(props.flAirAbsorptionHF); break; + case DSPROPERTY_EAX20LISTENER_FLAGS: call.set_value<Exception>(props.dwFlags); break; + default: fail_unknown_property_id(); + } +} + +void EaxReverbCommitter::Get(const EaxCall &call, const EAXREVERBPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case EAXREVERB_NONE: break; + case EAXREVERB_ALLPARAMETERS: call.set_value<Exception>(props); break; + case EAXREVERB_ENVIRONMENT: call.set_value<Exception>(props.ulEnvironment); break; + case EAXREVERB_ENVIRONMENTSIZE: call.set_value<Exception>(props.flEnvironmentSize); break; + case EAXREVERB_ENVIRONMENTDIFFUSION: call.set_value<Exception>(props.flEnvironmentDiffusion); break; + case EAXREVERB_ROOM: call.set_value<Exception>(props.lRoom); break; + case EAXREVERB_ROOMHF: call.set_value<Exception>(props.lRoomHF); break; + case EAXREVERB_ROOMLF: call.set_value<Exception>(props.lRoomLF); break; + case EAXREVERB_DECAYTIME: call.set_value<Exception>(props.flDecayTime); break; + case EAXREVERB_DECAYHFRATIO: call.set_value<Exception>(props.flDecayHFRatio); break; + case EAXREVERB_DECAYLFRATIO: call.set_value<Exception>(props.flDecayLFRatio); break; + case EAXREVERB_REFLECTIONS: call.set_value<Exception>(props.lReflections); break; + case EAXREVERB_REFLECTIONSDELAY: call.set_value<Exception>(props.flReflectionsDelay); break; + case EAXREVERB_REFLECTIONSPAN: call.set_value<Exception>(props.vReflectionsPan); break; + case EAXREVERB_REVERB: call.set_value<Exception>(props.lReverb); break; + case EAXREVERB_REVERBDELAY: call.set_value<Exception>(props.flReverbDelay); break; + case EAXREVERB_REVERBPAN: call.set_value<Exception>(props.vReverbPan); break; + case EAXREVERB_ECHOTIME: call.set_value<Exception>(props.flEchoTime); break; + case EAXREVERB_ECHODEPTH: call.set_value<Exception>(props.flEchoDepth); break; + case EAXREVERB_MODULATIONTIME: call.set_value<Exception>(props.flModulationTime); break; + case EAXREVERB_MODULATIONDEPTH: call.set_value<Exception>(props.flModulationDepth); break; + case EAXREVERB_AIRABSORPTIONHF: call.set_value<Exception>(props.flAirAbsorptionHF); break; + case EAXREVERB_HFREFERENCE: call.set_value<Exception>(props.flHFReference); break; + case EAXREVERB_LFREFERENCE: call.set_value<Exception>(props.flLFReference); break; + case EAXREVERB_ROOMROLLOFFFACTOR: call.set_value<Exception>(props.flRoomRolloffFactor); break; + case EAXREVERB_FLAGS: call.set_value<Exception>(props.ulFlags); break; + default: fail_unknown_property_id(); + } +} + +void EaxReverbCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + Get(call, props.mReverb); +} + + +void EaxReverbCommitter::Set(const EaxCall &call, EAX_REVERBPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case DSPROPERTY_EAX_ALL: defer<AllValidator1>(call, props); break; + case DSPROPERTY_EAX_ENVIRONMENT: defer<EnvironmentValidator1>(call, props.environment); break; + case DSPROPERTY_EAX_VOLUME: defer<VolumeValidator>(call, props.fVolume); break; + case DSPROPERTY_EAX_DECAYTIME: defer<DecayTimeValidator>(call, props.fDecayTime_sec); break; + case DSPROPERTY_EAX_DAMPING: defer<DampingValidator>(call, props.fDamping); break; + default: fail_unknown_property_id(); + } +} + +void EaxReverbCommitter::Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case DSPROPERTY_EAX20LISTENER_NONE: + break; + + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: + defer<AllValidator2>(call, props); + break; + + case DSPROPERTY_EAX20LISTENER_ROOM: + defer<RoomValidator>(call, props.lRoom); + break; + + case DSPROPERTY_EAX20LISTENER_ROOMHF: + defer<RoomHFValidator>(call, props.lRoomHF); + break; + + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: + defer<RoomRolloffFactorValidator>(call, props.flRoomRolloffFactor); + break; + + case DSPROPERTY_EAX20LISTENER_DECAYTIME: + defer<DecayTimeValidator>(call, props.flDecayTime); + break; + + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: + defer<DecayHFRatioValidator>(call, props.flDecayHFRatio); + break; + + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: + defer<ReflectionsValidator>(call, props.lReflections); + break; + + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: + defer<ReflectionsDelayValidator>(call, props.flReverbDelay); + break; + + case DSPROPERTY_EAX20LISTENER_REVERB: + defer<ReverbValidator>(call, props.lReverb); + break; + + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: + defer<ReverbDelayValidator>(call, props.flReverbDelay); + break; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: + defer<EnvironmentValidator1, EnvironmentDeferrer2>(call, props, props.dwEnvironment); + break; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: + defer<EnvironmentSizeValidator, EnvironmentSizeDeferrer2>(call, props, props.flEnvironmentSize); + break; + + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: + defer<EnvironmentDiffusionValidator>(call, props.flEnvironmentDiffusion); + break; + + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: + defer<AirAbsorptionHFValidator>(call, props.flAirAbsorptionHF); + break; + + case DSPROPERTY_EAX20LISTENER_FLAGS: + defer<FlagsValidator2>(call, props.dwFlags); + break; + + default: + fail_unknown_property_id(); + } +} + +void EaxReverbCommitter::Set(const EaxCall &call, EAXREVERBPROPERTIES &props) +{ + switch(call.get_property_id()) + { + case EAXREVERB_NONE: + break; + + case EAXREVERB_ALLPARAMETERS: + defer<AllValidator3>(call, props); + break; + + case EAXREVERB_ENVIRONMENT: + defer<EnvironmentValidator3, EnvironmentDeferrer3>(call, props, props.ulEnvironment); + break; + + case EAXREVERB_ENVIRONMENTSIZE: + defer<EnvironmentSizeValidator, EnvironmentSizeDeferrer3>(call, props, props.flEnvironmentSize); + break; + + case EAXREVERB_ENVIRONMENTDIFFUSION: + defer3<EnvironmentDiffusionValidator>(call, props, props.flEnvironmentDiffusion); + break; + + case EAXREVERB_ROOM: + defer3<RoomValidator>(call, props, props.lRoom); + break; + + case EAXREVERB_ROOMHF: + defer3<RoomHFValidator>(call, props, props.lRoomHF); + break; + + case EAXREVERB_ROOMLF: + defer3<RoomLFValidator>(call, props, props.lRoomLF); + break; + + case EAXREVERB_DECAYTIME: + defer3<DecayTimeValidator>(call, props, props.flDecayTime); + break; + + case EAXREVERB_DECAYHFRATIO: + defer3<DecayHFRatioValidator>(call, props, props.flDecayHFRatio); + break; + + case EAXREVERB_DECAYLFRATIO: + defer3<DecayLFRatioValidator>(call, props, props.flDecayLFRatio); + break; + + case EAXREVERB_REFLECTIONS: + defer3<ReflectionsValidator>(call, props, props.lReflections); + break; + + case EAXREVERB_REFLECTIONSDELAY: + defer3<ReflectionsDelayValidator>(call, props, props.flReflectionsDelay); + break; + + case EAXREVERB_REFLECTIONSPAN: + defer3<VectorValidator>(call, props, props.vReflectionsPan); + break; + + case EAXREVERB_REVERB: + defer3<ReverbValidator>(call, props, props.lReverb); + break; + + case EAXREVERB_REVERBDELAY: + defer3<ReverbDelayValidator>(call, props, props.flReverbDelay); + break; + + case EAXREVERB_REVERBPAN: + defer3<VectorValidator>(call, props, props.vReverbPan); + break; + + case EAXREVERB_ECHOTIME: + defer3<EchoTimeValidator>(call, props, props.flEchoTime); + break; + + case EAXREVERB_ECHODEPTH: + defer3<EchoDepthValidator>(call, props, props.flEchoDepth); + break; + + case EAXREVERB_MODULATIONTIME: + defer3<ModulationTimeValidator>(call, props, props.flModulationTime); + break; + + case EAXREVERB_MODULATIONDEPTH: + defer3<ModulationDepthValidator>(call, props, props.flModulationDepth); + break; + + case EAXREVERB_AIRABSORPTIONHF: + defer3<AirAbsorptionHFValidator>(call, props, props.flAirAbsorptionHF); + break; + + case EAXREVERB_HFREFERENCE: + defer3<HFReferenceValidator>(call, props, props.flHFReference); + break; + + case EAXREVERB_LFREFERENCE: + defer3<LFReferenceValidator>(call, props, props.flLFReference); + break; + + case EAXREVERB_ROOMROLLOFFFACTOR: + defer3<RoomRolloffFactorValidator>(call, props, props.flRoomRolloffFactor); + break; + + case EAXREVERB_FLAGS: + defer3<FlagsValidator3>(call, props, props.ulFlags); + break; + + default: + fail_unknown_property_id(); + } +} + +void EaxReverbCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + Set(call, props.mReverb); +} + +#endif // ALSOFT_EAX diff --git a/al/effects/vmorpher.cpp b/al/effects/vmorpher.cpp new file mode 100644 index 00000000..21ea3680 --- /dev/null +++ b/al/effects/vmorpher.cpp @@ -0,0 +1,520 @@ + +#include "config.h" + +#include <stdexcept> + +#include "AL/al.h" +#include "AL/efx.h" + +#include "alc/effects/base.h" +#include "aloptional.h" +#include "effects.h" + +#ifdef ALSOFT_EAX +#include <cassert> +#include "alnumeric.h" +#include "al/eax/exception.h" +#include "al/eax/utils.h" +#endif // ALSOFT_EAX + + +namespace { + +al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val) +{ +#define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ + return VMorpherPhenome::x + switch(val) + { + HANDLE_PHENOME(A); + HANDLE_PHENOME(E); + HANDLE_PHENOME(I); + HANDLE_PHENOME(O); + HANDLE_PHENOME(U); + HANDLE_PHENOME(AA); + HANDLE_PHENOME(AE); + HANDLE_PHENOME(AH); + HANDLE_PHENOME(AO); + HANDLE_PHENOME(EH); + HANDLE_PHENOME(ER); + HANDLE_PHENOME(IH); + HANDLE_PHENOME(IY); + HANDLE_PHENOME(UH); + HANDLE_PHENOME(UW); + HANDLE_PHENOME(B); + HANDLE_PHENOME(D); + HANDLE_PHENOME(F); + HANDLE_PHENOME(G); + HANDLE_PHENOME(J); + HANDLE_PHENOME(K); + HANDLE_PHENOME(L); + HANDLE_PHENOME(M); + HANDLE_PHENOME(N); + HANDLE_PHENOME(P); + HANDLE_PHENOME(R); + HANDLE_PHENOME(S); + HANDLE_PHENOME(T); + HANDLE_PHENOME(V); + HANDLE_PHENOME(Z); + } + return al::nullopt; +#undef HANDLE_PHENOME +} +ALenum EnumFromPhenome(VMorpherPhenome phenome) +{ +#define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x + switch(phenome) + { + HANDLE_PHENOME(A); + HANDLE_PHENOME(E); + HANDLE_PHENOME(I); + HANDLE_PHENOME(O); + HANDLE_PHENOME(U); + HANDLE_PHENOME(AA); + HANDLE_PHENOME(AE); + HANDLE_PHENOME(AH); + HANDLE_PHENOME(AO); + HANDLE_PHENOME(EH); + HANDLE_PHENOME(ER); + HANDLE_PHENOME(IH); + HANDLE_PHENOME(IY); + HANDLE_PHENOME(UH); + HANDLE_PHENOME(UW); + HANDLE_PHENOME(B); + HANDLE_PHENOME(D); + HANDLE_PHENOME(F); + HANDLE_PHENOME(G); + HANDLE_PHENOME(J); + HANDLE_PHENOME(K); + HANDLE_PHENOME(L); + HANDLE_PHENOME(M); + HANDLE_PHENOME(N); + HANDLE_PHENOME(P); + HANDLE_PHENOME(R); + HANDLE_PHENOME(S); + HANDLE_PHENOME(T); + HANDLE_PHENOME(V); + HANDLE_PHENOME(Z); + } + throw std::runtime_error{"Invalid phenome: "+std::to_string(static_cast<int>(phenome))}; +#undef HANDLE_PHENOME +} + +al::optional<VMorpherWaveform> WaveformFromEmum(ALenum value) +{ + switch(value) + { + case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return VMorpherWaveform::Sinusoid; + case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle; + case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth; + } + return al::nullopt; +} +ALenum EnumFromWaveform(VMorpherWaveform type) +{ + switch(type) + { + case VMorpherWaveform::Sinusoid: return AL_VOCAL_MORPHER_WAVEFORM_SINUSOID; + case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE; + case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH; + } + throw std::runtime_error{"Invalid vocal morpher waveform: " + + std::to_string(static_cast<int>(type))}; +} + +void Vmorpher_setParami(EffectProps *props, ALenum param, int val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_PHONEMEA: + if(auto phenomeopt = PhenomeFromEnum(val)) + props->Vmorpher.PhonemeA = *phenomeopt; + else + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; + break; + + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: + if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; + props->Vmorpher.PhonemeACoarseTuning = val; + break; + + case AL_VOCAL_MORPHER_PHONEMEB: + if(auto phenomeopt = PhenomeFromEnum(val)) + props->Vmorpher.PhonemeB = *phenomeopt; + else + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; + break; + + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: + if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; + props->Vmorpher.PhonemeBCoarseTuning = val; + break; + + case AL_VOCAL_MORPHER_WAVEFORM: + if(auto formopt = WaveformFromEmum(val)) + props->Vmorpher.Waveform = *formopt; + else + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", + param}; + } +} +void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", + param}; +} +void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_RATE: + if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) + throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; + props->Vmorpher.Rate = val; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", + param}; + } +} +void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals) +{ Vmorpher_setParamf(props, param, vals[0]); } + +void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_PHONEMEA: + *val = EnumFromPhenome(props->Vmorpher.PhonemeA); + break; + + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: + *val = props->Vmorpher.PhonemeACoarseTuning; + break; + + case AL_VOCAL_MORPHER_PHONEMEB: + *val = EnumFromPhenome(props->Vmorpher.PhonemeB); + break; + + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: + *val = props->Vmorpher.PhonemeBCoarseTuning; + break; + + case AL_VOCAL_MORPHER_WAVEFORM: + *val = EnumFromWaveform(props->Vmorpher.Waveform); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", + param}; + } +} +void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", + param}; +} +void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) +{ + switch(param) + { + case AL_VOCAL_MORPHER_RATE: + *val = props->Vmorpher.Rate; + break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x", + param}; + } +} +void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals) +{ Vmorpher_getParamf(props, param, vals); } + +EffectProps genDefaultProps() noexcept +{ + EffectProps props{}; + props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; + props.Vmorpher.PhonemeA = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA); + props.Vmorpher.PhonemeB = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB); + props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; + props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; + props.Vmorpher.Waveform = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM); + return props; +} + +} // namespace + +DEFINE_ALEFFECT_VTABLE(Vmorpher); + +const EffectProps VmorpherEffectProps{genDefaultProps()}; + +#ifdef ALSOFT_EAX +namespace { + +using VocalMorpherCommitter = EaxCommitter<EaxVocalMorpherCommitter>; + +struct PhonemeAValidator { + void operator()(unsigned long ulPhonemeA) const + { + eax_validate_range<VocalMorpherCommitter::Exception>( + "Phoneme A", + ulPhonemeA, + EAXVOCALMORPHER_MINPHONEMEA, + EAXVOCALMORPHER_MAXPHONEMEA); + } +}; // PhonemeAValidator + +struct PhonemeACoarseTuningValidator { + void operator()(long lPhonemeACoarseTuning) const + { + eax_validate_range<VocalMorpherCommitter::Exception>( + "Phoneme A Coarse Tuning", + lPhonemeACoarseTuning, + EAXVOCALMORPHER_MINPHONEMEACOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING); + } +}; // PhonemeACoarseTuningValidator + +struct PhonemeBValidator { + void operator()(unsigned long ulPhonemeB) const + { + eax_validate_range<VocalMorpherCommitter::Exception>( + "Phoneme B", + ulPhonemeB, + EAXVOCALMORPHER_MINPHONEMEB, + EAXVOCALMORPHER_MAXPHONEMEB); + } +}; // PhonemeBValidator + +struct PhonemeBCoarseTuningValidator { + void operator()(long lPhonemeBCoarseTuning) const + { + eax_validate_range<VocalMorpherCommitter::Exception>( + "Phoneme B Coarse Tuning", + lPhonemeBCoarseTuning, + EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING, + EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING); + } +}; // PhonemeBCoarseTuningValidator + +struct WaveformValidator { + void operator()(unsigned long ulWaveform) const + { + eax_validate_range<VocalMorpherCommitter::Exception>( + "Waveform", + ulWaveform, + EAXVOCALMORPHER_MINWAVEFORM, + EAXVOCALMORPHER_MAXWAVEFORM); + } +}; // WaveformValidator + +struct RateValidator { + void operator()(float flRate) const + { + eax_validate_range<VocalMorpherCommitter::Exception>( + "Rate", + flRate, + EAXVOCALMORPHER_MINRATE, + EAXVOCALMORPHER_MAXRATE); + } +}; // RateValidator + +struct AllValidator { + void operator()(const EAXVOCALMORPHERPROPERTIES& all) const + { + PhonemeAValidator{}(all.ulPhonemeA); + PhonemeACoarseTuningValidator{}(all.lPhonemeACoarseTuning); + PhonemeBValidator{}(all.ulPhonemeB); + PhonemeBCoarseTuningValidator{}(all.lPhonemeBCoarseTuning); + WaveformValidator{}(all.ulWaveform); + RateValidator{}(all.flRate); + } +}; // AllValidator + +} // namespace + +template<> +struct VocalMorpherCommitter::Exception : public EaxException { + explicit Exception(const char *message) : EaxException{"EAX_VOCAL_MORPHER_EFFECT", message} + { } +}; + +template<> +[[noreturn]] void VocalMorpherCommitter::fail(const char *message) +{ + throw Exception{message}; +} + +template<> +bool VocalMorpherCommitter::commit(const EaxEffectProps &props) +{ + if(props.mType == mEaxProps.mType + && mEaxProps.mVocalMorpher.ulPhonemeA == props.mVocalMorpher.ulPhonemeA + && mEaxProps.mVocalMorpher.lPhonemeACoarseTuning == props.mVocalMorpher.lPhonemeACoarseTuning + && mEaxProps.mVocalMorpher.ulPhonemeB == props.mVocalMorpher.ulPhonemeB + && mEaxProps.mVocalMorpher.lPhonemeBCoarseTuning == props.mVocalMorpher.lPhonemeBCoarseTuning + && mEaxProps.mVocalMorpher.ulWaveform == props.mVocalMorpher.ulWaveform + && mEaxProps.mVocalMorpher.flRate == props.mVocalMorpher.flRate) + return false; + + mEaxProps = props; + + auto get_phoneme = [](unsigned long phoneme) noexcept + { +#define HANDLE_PHENOME(x) case x: return VMorpherPhenome::x + switch(phoneme) + { + HANDLE_PHENOME(A); + HANDLE_PHENOME(E); + HANDLE_PHENOME(I); + HANDLE_PHENOME(O); + HANDLE_PHENOME(U); + HANDLE_PHENOME(AA); + HANDLE_PHENOME(AE); + HANDLE_PHENOME(AH); + HANDLE_PHENOME(AO); + HANDLE_PHENOME(EH); + HANDLE_PHENOME(ER); + HANDLE_PHENOME(IH); + HANDLE_PHENOME(IY); + HANDLE_PHENOME(UH); + HANDLE_PHENOME(UW); + HANDLE_PHENOME(B); + HANDLE_PHENOME(D); + HANDLE_PHENOME(F); + HANDLE_PHENOME(G); + HANDLE_PHENOME(J); + HANDLE_PHENOME(K); + HANDLE_PHENOME(L); + HANDLE_PHENOME(M); + HANDLE_PHENOME(N); + HANDLE_PHENOME(P); + HANDLE_PHENOME(R); + HANDLE_PHENOME(S); + HANDLE_PHENOME(T); + HANDLE_PHENOME(V); + HANDLE_PHENOME(Z); + } + return VMorpherPhenome::A; +#undef HANDLE_PHENOME + }; + auto get_waveform = [](unsigned long form) noexcept + { + if(form == EAX_VOCALMORPHER_SINUSOID) return VMorpherWaveform::Sinusoid; + if(form == EAX_VOCALMORPHER_TRIANGLE) return VMorpherWaveform::Triangle; + if(form == EAX_VOCALMORPHER_SAWTOOTH) return VMorpherWaveform::Sawtooth; + return VMorpherWaveform::Sinusoid; + }; + + mAlProps.Vmorpher.PhonemeA = get_phoneme(props.mVocalMorpher.ulPhonemeA); + mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeACoarseTuning); + mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB); + mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeBCoarseTuning); + mAlProps.Vmorpher.Waveform = get_waveform(props.mVocalMorpher.ulWaveform); + mAlProps.Vmorpher.Rate = props.mVocalMorpher.flRate; + + return true; +} + +template<> +void VocalMorpherCommitter::SetDefaults(EaxEffectProps &props) +{ + props.mType = EaxEffectType::VocalMorpher; + props.mVocalMorpher.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; + props.mVocalMorpher.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; + props.mVocalMorpher.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; + props.mVocalMorpher.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; + props.mVocalMorpher.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; + props.mVocalMorpher.flRate = EAXVOCALMORPHER_DEFAULTRATE; +} + +template<> +void VocalMorpherCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + call.set_value<Exception>(props.mVocalMorpher); + break; + + case EAXVOCALMORPHER_PHONEMEA: + call.set_value<Exception>(props.mVocalMorpher.ulPhonemeA); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + call.set_value<Exception>(props.mVocalMorpher.lPhonemeACoarseTuning); + break; + + case EAXVOCALMORPHER_PHONEMEB: + call.set_value<Exception>(props.mVocalMorpher.ulPhonemeB); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + call.set_value<Exception>(props.mVocalMorpher.lPhonemeBCoarseTuning); + break; + + case EAXVOCALMORPHER_WAVEFORM: + call.set_value<Exception>(props.mVocalMorpher.ulWaveform); + break; + + case EAXVOCALMORPHER_RATE: + call.set_value<Exception>(props.mVocalMorpher.flRate); + break; + + default: + fail_unknown_property_id(); + } +} + +template<> +void VocalMorpherCommitter::Set(const EaxCall &call, EaxEffectProps &props) +{ + switch(call.get_property_id()) + { + case EAXVOCALMORPHER_NONE: + break; + + case EAXVOCALMORPHER_ALLPARAMETERS: + defer<AllValidator>(call, props.mVocalMorpher); + break; + + case EAXVOCALMORPHER_PHONEMEA: + defer<PhonemeAValidator>(call, props.mVocalMorpher.ulPhonemeA); + break; + + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: + defer<PhonemeACoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeACoarseTuning); + break; + + case EAXVOCALMORPHER_PHONEMEB: + defer<PhonemeBValidator>(call, props.mVocalMorpher.ulPhonemeB); + break; + + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: + defer<PhonemeBCoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeBCoarseTuning); + break; + + case EAXVOCALMORPHER_WAVEFORM: + defer<WaveformValidator>(call, props.mVocalMorpher.ulWaveform); + break; + + case EAXVOCALMORPHER_RATE: + defer<RateValidator>(call, props.mVocalMorpher.flRate); + break; + + default: + fail_unknown_property_id(); + } +} + +#endif // ALSOFT_EAX |