#ifndef EAX_EFFECT_INCLUDED #define EAX_EFFECT_INCLUDED #include #include #include "alnumeric.h" #include "AL/al.h" #include "core/effects/base.h" #include "call.h" struct EaxEffectErrorMessages { static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } static constexpr auto unknown_version() noexcept { return "Unknown version."; } }; // EaxEffectErrorMessages /* TODO: Use std::variant (C++17). */ enum class EaxEffectType { None, Reverb, Chorus, Autowah, Compressor, Distortion, Echo, Equalizer, Flanger, FrequencyShifter, Modulator, PitchShifter, VocalMorpher }; struct EaxEffectProps { EaxEffectType mType; union { EAXREVERBPROPERTIES mReverb; EAXCHORUSPROPERTIES mChorus; EAXAUTOWAHPROPERTIES mAutowah; EAXAGCCOMPRESSORPROPERTIES mCompressor; EAXDISTORTIONPROPERTIES mDistortion; EAXECHOPROPERTIES mEcho; EAXEQUALIZERPROPERTIES mEqualizer; EAXFLANGERPROPERTIES mFlanger; EAXFREQUENCYSHIFTERPROPERTIES mFrequencyShifter; EAXRINGMODULATORPROPERTIES mModulator; EAXPITCHSHIFTERPROPERTIES mPitchShifter; EAXVOCALMORPHERPROPERTIES mVocalMorpher; }; }; constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props) { switch(props.mType) { case EaxEffectType::None: break; case EaxEffectType::Reverb: return AL_EFFECT_EAXREVERB; case EaxEffectType::Chorus: return AL_EFFECT_CHORUS; case EaxEffectType::Autowah: return AL_EFFECT_AUTOWAH; case EaxEffectType::Compressor: return AL_EFFECT_COMPRESSOR; case EaxEffectType::Distortion: return AL_EFFECT_DISTORTION; case EaxEffectType::Echo: return AL_EFFECT_ECHO; case EaxEffectType::Equalizer: return AL_EFFECT_EQUALIZER; case EaxEffectType::Flanger: return AL_EFFECT_FLANGER; case EaxEffectType::FrequencyShifter: return AL_EFFECT_FREQUENCY_SHIFTER; case EaxEffectType::Modulator: return AL_EFFECT_RING_MODULATOR; case EaxEffectType::PitchShifter: return AL_EFFECT_PITCH_SHIFTER; case EaxEffectType::VocalMorpher: return AL_EFFECT_VOCAL_MORPHER; } return AL_EFFECT_NULL; } struct EaxReverbCommitter { struct Exception; EaxReverbCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) : props_{eaxprops}, al_effect_props_{alprops} { } EaxEffectProps &props_; EffectProps &al_effect_props_; [[noreturn]] static void fail(const char* message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } template static void defer(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } template static void defer(const EaxCall& call, TProperties& properties, TProperty&) { const auto& value = call.get_value(); TValidator{}(value); TDeferrer{}(properties, value); } template static void defer3(const EaxCall& call, EAXREVERBPROPERTIES& properties, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); if (value == property) return; property = value; properties.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED; } bool commit(const EAX_REVERBPROPERTIES &props); bool commit(const EAX20LISTENERPROPERTIES &props); bool commit(const EAXREVERBPROPERTIES &props); bool commit(const EaxEffectProps &props); static void SetDefaults(EAX_REVERBPROPERTIES &props); static void SetDefaults(EAX20LISTENERPROPERTIES &props); static void SetDefaults(EAXREVERBPROPERTIES &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props); static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props); static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props); static void Get(const EaxCall &call, const EaxEffectProps &props); static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props); static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props); static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props); static void Set(const EaxCall &call, EaxEffectProps &props); static void translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept; static void translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept; static void translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept; }; template struct EaxCommitter { struct Exception; EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops) : props_{eaxprops}, al_effect_props_{alprops} { } EaxEffectProps &props_; EffectProps &al_effect_props_; template static void defer(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } template static void defer(const EaxCall& call, TProperties& properties, TProperty&) { const auto& value = call.get_value(); TValidator{}(value); TDeferrer{}(properties, value); } [[noreturn]] static void fail(const char *message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } bool commit(const EaxEffectProps &props); static void SetDefaults(EaxEffectProps &props); static void Get(const EaxCall &call, const EaxEffectProps &props); static void Set(const EaxCall &call, EaxEffectProps &props); }; struct EaxNullCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; }; class EaxEffect { public: EaxEffect() noexcept = default; virtual ~EaxEffect() = default; ALenum al_effect_type_{AL_EFFECT_NULL}; EffectProps al_effect_props_{}; using Props1 = EAX_REVERBPROPERTIES; using Props2 = EAX20LISTENERPROPERTIES; using Props3 = EAXREVERBPROPERTIES; using Props4 = EaxEffectProps; struct State1 { Props1 i; // Immediate. Props1 d; // Deferred. }; struct State2 { Props2 i; // Immediate. Props2 d; // Deferred. }; struct State3 { Props3 i; // Immediate. Props3 d; // Deferred. }; struct State4 { Props4 i; // Immediate. Props4 d; // Deferred. }; int version_{}; bool changed_{}; Props4 props_{}; State1 state1_{}; State2 state2_{}; State3 state3_{}; State4 state4_{}; State4 state5_{}; [[deprecated]] virtual void dispatch(const EaxCall& /*call*/) { } // Returns "true" if any immediated property was changed. /*[[nodiscard]]*/ [[deprecated]] virtual bool commit() { return false; } template void call_set_defaults(Args&& ...args) { return T::SetDefaults(std::forward(args)...); } void call_set_defaults(const ALenum altype, EaxEffectProps &props) { if(altype == AL_EFFECT_EAXREVERB) return call_set_defaults(props); return call_set_defaults(props); } template void init() { call_set_defaults(state1_.d); state1_.i = state1_.d; call_set_defaults(state2_.d); state2_.i = state2_.d; call_set_defaults(state3_.d); state3_.i = state3_.d; call_set_defaults(state4_.d); state4_.i = state4_.d; call_set_defaults(state5_.d); state5_.i = state5_.d; } void set_defaults(int eax_version, ALenum altype) { switch(eax_version) { case 1: call_set_defaults(state1_.d); break; case 2: call_set_defaults(state2_.d); break; case 3: call_set_defaults(state3_.d); break; case 4: call_set_defaults(altype, state4_.d); break; case 5: call_set_defaults(altype, state5_.d); break; } changed_ = true; } template void do_set(Args&& ...args) { return T::Set(std::forward(args)...); } void do_set(const EaxCall &call, EaxEffectProps &props) { if(props.mType == EaxEffectType::Reverb) return do_set(call, props); return do_set(call, props); } void set(const EaxCall &call) { switch(call.get_version()) { case 1: do_set(call, state1_.d); break; case 2: do_set(call, state2_.d); break; case 3: do_set(call, state3_.d); break; case 4: do_set(call, state4_.d); break; case 5: do_set(call, state5_.d); break; } changed_ = true; } template void do_get(Args&& ...args) { return T::Get(std::forward(args)...); } void do_get(const EaxCall &call, const EaxEffectProps &props) { if(props.mType == EaxEffectType::Reverb) return do_get(call, props); return do_get(call, props); } void get(const EaxCall &call) { switch(call.get_version()) { case 1: do_get(call, state1_.d); break; case 2: do_get(call, state2_.d); break; case 3: do_get(call, state3_.d); break; case 4: do_get(call, state4_.d); break; case 5: do_get(call, state5_.d); break; } } template bool call_commit(Args&& ...args) { return T{props_, al_effect_props_}.commit(std::forward(args)...); } bool call_commit(const EaxEffectProps &props) { if(props.mType == EaxEffectType::Reverb) return call_commit(props); return call_commit(props); } bool do_commit(int eax_version) { changed_ |= version_ != eax_version; if(!changed_) return false; bool ret{version_ != eax_version}; version_ = eax_version; changed_ = false; switch(eax_version) { case 1: state1_.i = state1_.d; ret |= call_commit(state1_.d); break; case 2: state2_.i = state2_.d; ret |= call_commit(state2_.d); break; case 3: state3_.i = state3_.d; ret |= call_commit(state3_.d); break; case 4: state4_.i = state4_.d; ret |= call_commit(state4_.d); break; case 5: state5_.i = state5_.d; ret |= call_commit(state5_.d); break; } al_effect_type_ = EnumFromEaxEffectType(props_); return ret; } }; // EaxEffect // Base class for EAX4+ effects. template class EaxEffect4 : public EaxEffect { public: EaxEffect4(ALenum, int) { } void initialize() { set_defaults(); set_efx_defaults(); } void dispatch(const EaxCall& call) override { call.is_get() ? get(call) : set(call); } bool commit() final { switch (version_) { case 4: return commit_state(state4_); case 5: return commit_state(state5_); default: fail_unknown_version(); } } protected: using Exception = TException; template static void defer(const EaxCall& call, TProperty& property) { const auto& value = call.get_value(); TValidator{}(value); property = value; } virtual void set_defaults(Props4& props) = 0; virtual void set_efx_defaults() = 0; virtual void get(const EaxCall& call, const Props4& props) = 0; virtual void set(const EaxCall& call, Props4& props) = 0; virtual bool commit_props(const Props4& props) = 0; [[noreturn]] static void fail(const char* message) { throw Exception{message}; } [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } [[noreturn]] static void fail_unknown_version() { fail(EaxEffectErrorMessages::unknown_version()); } private: void set_defaults() { set_defaults(props_); state4_.i = props_; state4_.d = props_; state5_.i = props_; state5_.d = props_; } void get(const EaxCall& call) { switch (call.get_version()) { case 4: get(call, state4_.i); break; case 5: get(call, state5_.i); break; default: fail_unknown_version(); } } void set(const EaxCall& call) { const auto version = call.get_version(); switch (version) { case 4: set(call, state4_.d); break; case 5: set(call, state5_.d); break; default: fail_unknown_version(); } version_ = version; } bool commit_state(State4& state) { const auto props = props_; state.i = state.d; props_ = state.d; return commit_props(props); } }; // EaxEffect4 using EaxEffectUPtr = std::unique_ptr; // Creates EAX4+ effect. template EaxEffectUPtr eax_create_eax4_effect(int eax_version) { auto effect = std::make_unique(eax_version); effect->initialize(); return effect; } #endif // !EAX_EFFECT_INCLUDED