From dae225e88dbf795e776a2c9f2dbe5bb07c2228b9 Mon Sep 17 00:00:00 2001
From: Chris Robinson <chris.kcat@gmail.com>
Date: Sun, 24 Dec 2023 02:48:20 -0800
Subject: Rework effect property handling

To nake EffectProps a variant instead of a union, and avoid manual vtables.
---
 al/effect.cpp              | 231 +++++++++++--------
 al/effect.h                |   2 -
 al/effects/autowah.cpp     | 102 ++++-----
 al/effects/chorus.cpp      | 194 +++++++---------
 al/effects/compressor.cpp  |  49 ++--
 al/effects/convolution.cpp |  82 ++++---
 al/effects/dedicated.cpp   |  93 ++++----
 al/effects/distortion.cpp  | 100 ++++-----
 al/effects/echo.cpp        | 100 ++++-----
 al/effects/effects.h       |  80 ++++---
 al/effects/equalizer.cpp   | 155 ++++++-------
 al/effects/fshifter.cpp    |  96 ++++----
 al/effects/modulator.cpp   | 110 +++++----
 al/effects/null.cpp        |  49 ++--
 al/effects/pshifter.cpp    |  72 +++---
 al/effects/reverb.cpp      | 545 +++++++++++++++++++--------------------------
 al/effects/vmorpher.cpp    | 107 ++++-----
 17 files changed, 996 insertions(+), 1171 deletions(-)

(limited to 'al')

diff --git a/al/effect.cpp b/al/effect.cpp
index 005b1557..c33faa2c 100644
--- a/al/effect.cpp
+++ b/al/effect.cpp
@@ -89,72 +89,34 @@ effect_exception::~effect_exception() = default;
 
 namespace {
 
-struct EffectPropsItem {
-    ALenum Type;
-    const EffectProps &DefaultProps;
-    const EffectVtable &Vtable;
-};
-constexpr std::array EffectPropsList{
-    EffectPropsItem{AL_EFFECT_NULL, NullEffectProps, NullEffectVtable},
-    EffectPropsItem{AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable},
-    EffectPropsItem{AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable},
-    EffectPropsItem{AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable},
-    EffectPropsItem{AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable},
-    EffectPropsItem{AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable},
-    EffectPropsItem{AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable},
-    EffectPropsItem{AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable},
-    EffectPropsItem{AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable},
-    EffectPropsItem{AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable},
-    EffectPropsItem{AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable},
-    EffectPropsItem{AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable},
-    EffectPropsItem{AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable},
-    EffectPropsItem{AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable},
-    EffectPropsItem{AL_EFFECT_DEDICATED_DIALOGUE, DedicatedDialogEffectProps, DedicatedDialogEffectVtable},
-    EffectPropsItem{AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedLfeEffectProps, DedicatedLfeEffectVtable},
-    EffectPropsItem{AL_EFFECT_CONVOLUTION_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable},
-};
-
-
-void ALeffect_setParami(ALeffect *effect, ALenum param, int value)
-{ effect->vtab->setParami(&effect->Props, param, value); }
-void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values)
-{ effect->vtab->setParamiv(&effect->Props, param, values); }
-void ALeffect_setParamf(ALeffect *effect, ALenum param, float value)
-{ effect->vtab->setParamf(&effect->Props, param, value); }
-void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values)
-{ effect->vtab->setParamfv(&effect->Props, param, values); }
-
-void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value)
-{ effect->vtab->getParami(&effect->Props, param, value); }
-void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values)
-{ effect->vtab->getParamiv(&effect->Props, param, values); }
-void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value)
-{ effect->vtab->getParamf(&effect->Props, param, value); }
-void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values)
-{ effect->vtab->getParamfv(&effect->Props, param, values); }
-
-
-const EffectPropsItem *getEffectPropsItemByType(ALenum type)
+auto GetDefaultProps(ALenum type) -> const EffectProps&
 {
-    auto iter = std::find_if(EffectPropsList.begin(), EffectPropsList.end(),
-        [type](const EffectPropsItem &item) noexcept -> bool
-        { return item.Type == type; });
-    return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr;
+    switch(type)
+    {
+    case AL_EFFECT_NULL: return NullEffectProps;
+    case AL_EFFECT_EAXREVERB: return ReverbEffectProps;
+    case AL_EFFECT_REVERB: return StdReverbEffectProps;
+    case AL_EFFECT_AUTOWAH: return AutowahEffectProps;
+    case AL_EFFECT_CHORUS: return ChorusEffectProps;
+    case AL_EFFECT_COMPRESSOR: return CompressorEffectProps;
+    case AL_EFFECT_DISTORTION: return DistortionEffectProps;
+    case AL_EFFECT_ECHO: return EchoEffectProps;
+    case AL_EFFECT_EQUALIZER: return EqualizerEffectProps;
+    case AL_EFFECT_FLANGER: return FlangerEffectProps;
+    case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps;
+    case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps;
+    case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps;
+    case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps;
+    case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps;
+    case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps;
+    case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps;
+    }
+    return NullEffectProps;
 }
 
 void InitEffectParams(ALeffect *effect, ALenum type)
 {
-    const EffectPropsItem *item{getEffectPropsItemByType(type)};
-    if(item)
-    {
-        effect->Props = item->DefaultProps;
-        effect->vtab = &item->Vtable;
-    }
-    else
-    {
-        effect->Props = EffectProps{};
-        effect->vtab = &NullEffectVtable;
-    }
+    effect->Props = GetDefaultProps(type);
     effect->type = type;
 }
 
@@ -342,7 +304,16 @@ FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect,
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_setParami(aleffect, param, value);
+        std::visit([aleffect,param,value](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbSetParami(arg, param, value);
+            }
+            return EffectHandler::SetParami(arg, param, value);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -369,7 +340,16 @@ FORCE_ALIGN void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_setParamiv(aleffect, param, values);
+        std::visit([aleffect,param,values](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbSetParamiv(arg, param, values);
+            }
+            return EffectHandler::SetParamiv(arg, param, values);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -389,7 +369,16 @@ FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect,
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_setParamf(aleffect, param, value);
+        std::visit([aleffect,param,value](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbSetParamf(arg, param, value);
+            }
+            return EffectHandler::SetParamf(arg, param, value);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -409,7 +398,16 @@ FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_setParamfv(aleffect, param, values);
+        std::visit([aleffect,param,values](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbSetParamfv(arg, param, values);
+            }
+            return EffectHandler::SetParamfv(arg, param, values);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -431,7 +429,16 @@ FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effe
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_getParami(aleffect, param, value);
+        std::visit([aleffect,param,value](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbGetParami(arg, param, value);
+            }
+            return EffectHandler::GetParami(arg, param, value);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -458,7 +465,16 @@ FORCE_ALIGN void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint eff
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_getParamiv(aleffect, param, values);
+        std::visit([aleffect,param,values](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbGetParamiv(arg, param, values);
+            }
+            return EffectHandler::GetParamiv(arg, param, values);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -478,7 +494,16 @@ FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effe
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_getParamf(aleffect, param, value);
+        std::visit([aleffect,param,value](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbGetParamf(arg, param, value);
+            }
+            return EffectHandler::GetParamf(arg, param, value);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -498,7 +523,16 @@ FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint eff
     else try
     {
         /* Call the appropriate handler */
-        ALeffect_getParamfv(aleffect, param, values);
+        std::visit([aleffect,param,values](auto &arg)
+        {
+            using Type = std::remove_cv_t<std::remove_reference_t<decltype(arg)>>;
+            if constexpr(std::is_same_v<Type,ReverbProps>)
+            {
+                if(aleffect->type == AL_EFFECT_REVERB)
+                    return EffectHandler::StdReverbGetParamfv(arg, param, values);
+            }
+            return EffectHandler::GetParamfv(arg, param, values);
+        }, aleffect->Props);
     }
     catch(effect_exception &e) {
         context->setError(e.errorCode(), "%s", e.what());
@@ -694,40 +728,39 @@ void LoadReverbPreset(const char *name, ALeffect *effect)
         InitEffectParams(effect, AL_EFFECT_NULL);
     for(const auto &reverbitem : reverblist)
     {
-        const EFXEAXREVERBPROPERTIES *props;
-
         if(al::strcasecmp(name, reverbitem.name) != 0)
             continue;
 
         TRACE("Loading reverb '%s'\n", reverbitem.name);
-        props = &reverbitem.props;
-        effect->Props.Reverb.Density   = props->flDensity;
-        effect->Props.Reverb.Diffusion = props->flDiffusion;
-        effect->Props.Reverb.Gain   = props->flGain;
-        effect->Props.Reverb.GainHF = props->flGainHF;
-        effect->Props.Reverb.GainLF = props->flGainLF;
-        effect->Props.Reverb.DecayTime    = props->flDecayTime;
-        effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
-        effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
-        effect->Props.Reverb.ReflectionsGain   = props->flReflectionsGain;
-        effect->Props.Reverb.ReflectionsDelay  = props->flReflectionsDelay;
-        effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
-        effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
-        effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
-        effect->Props.Reverb.LateReverbGain   = props->flLateReverbGain;
-        effect->Props.Reverb.LateReverbDelay  = props->flLateReverbDelay;
-        effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
-        effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
-        effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
-        effect->Props.Reverb.EchoTime  = props->flEchoTime;
-        effect->Props.Reverb.EchoDepth = props->flEchoDepth;
-        effect->Props.Reverb.ModulationTime  = props->flModulationTime;
-        effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
-        effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
-        effect->Props.Reverb.HFReference = props->flHFReference;
-        effect->Props.Reverb.LFReference = props->flLFReference;
-        effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
-        effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE;
+        const auto &props = reverbitem.props;
+        auto &dst = std::get<ReverbProps>(effect->Props);
+        dst.Density   = props.flDensity;
+        dst.Diffusion = props.flDiffusion;
+        dst.Gain   = props.flGain;
+        dst.GainHF = props.flGainHF;
+        dst.GainLF = props.flGainLF;
+        dst.DecayTime    = props.flDecayTime;
+        dst.DecayHFRatio = props.flDecayHFRatio;
+        dst.DecayLFRatio = props.flDecayLFRatio;
+        dst.ReflectionsGain   = props.flReflectionsGain;
+        dst.ReflectionsDelay  = props.flReflectionsDelay;
+        dst.ReflectionsPan[0] = props.flReflectionsPan[0];
+        dst.ReflectionsPan[1] = props.flReflectionsPan[1];
+        dst.ReflectionsPan[2] = props.flReflectionsPan[2];
+        dst.LateReverbGain   = props.flLateReverbGain;
+        dst.LateReverbDelay  = props.flLateReverbDelay;
+        dst.LateReverbPan[0] = props.flLateReverbPan[0];
+        dst.LateReverbPan[1] = props.flLateReverbPan[1];
+        dst.LateReverbPan[2] = props.flLateReverbPan[2];
+        dst.EchoTime  = props.flEchoTime;
+        dst.EchoDepth = props.flEchoDepth;
+        dst.ModulationTime  = props.flModulationTime;
+        dst.ModulationDepth = props.flModulationDepth;
+        dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF;
+        dst.HFReference = props.flHFReference;
+        dst.LFReference = props.flLFReference;
+        dst.RoomRolloffFactor = props.flRoomRolloffFactor;
+        dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE;
         return;
     }
 
diff --git a/al/effect.h b/al/effect.h
index 71fe9c87..820c7d28 100644
--- a/al/effect.h
+++ b/al/effect.h
@@ -47,8 +47,6 @@ struct ALeffect {
 
     EffectProps Props{};
 
-    const EffectVtable *vtab{nullptr};
-
     /* Self ID */
     ALuint id{0u};
 
diff --git a/al/effects/autowah.cpp b/al/effects/autowah.cpp
index c0f845ac..68704c11 100644
--- a/al/effects/autowah.cpp
+++ b/al/effects/autowah.cpp
@@ -20,100 +20,87 @@
 
 namespace {
 
-void Autowah_setParamf(EffectProps *props, ALenum param, float val)
+EffectProps genDefaultProps() noexcept
+{
+    AutowahProps props{};
+    props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
+    props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
+    props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
+    props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
+    return props;
+}
+
+} // namespace
+
+const EffectProps AutowahEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(AutowahProps&, ALenum param, int)
+{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
+void EffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*)
+{
+    throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
+        param};
+}
+
+void EffectHandler::SetParamf(AutowahProps &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;
+        props.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;
+        props.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;
+        props.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;
+        props.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 EffectHandler::SetParamfv(AutowahProps &props,  ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Autowah_setParami(EffectProps*, ALenum param, int)
+void EffectHandler::GetParami(const AutowahProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
-void Autowah_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::GetParamiv(const AutowahProps&, ALenum param, 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)
+void EffectHandler::GetParamf(const AutowahProps &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;
+    case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break;
+    case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break;
+    case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break;
+    case AL_AUTOWAH_PEAK_GAIN: *val = props.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()};
+void EffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
 #ifdef ALSOFT_EAX
 namespace {
@@ -195,11 +182,14 @@ bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props)
         return false;
 
     mEaxProps = props;
-
-    mAlProps.Autowah.AttackTime = props.flAttackTime;
-    mAlProps.Autowah.ReleaseTime = props.flReleaseTime;
-    mAlProps.Autowah.Resonance = level_mb_to_gain(static_cast<float>(props.lResonance));
-    mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast<float>(props.lPeakLevel));
+    mAlProps = [&]{
+        AutowahProps ret{};
+        ret.AttackTime = props.flAttackTime;
+        ret.ReleaseTime = props.flReleaseTime;
+        ret.Resonance = level_mb_to_gain(static_cast<float>(props.lResonance));
+        ret.PeakGain = level_mb_to_gain(static_cast<float>(props.lPeakLevel));
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/chorus.cpp b/al/effects/chorus.cpp
index 24aa0a49..dba59d1d 100644
--- a/al/effects/chorus.cpp
+++ b/al/effects/chorus.cpp
@@ -46,13 +46,41 @@ inline ALenum EnumFromWaveform(ChorusWaveform type)
     throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast<int>(type))};
 }
 
-void Chorus_setParami(EffectProps *props, ALenum param, int val)
+EffectProps genDefaultChorusProps() noexcept
+{
+    ChorusProps props{};
+    props.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM);
+    props.Phase = AL_CHORUS_DEFAULT_PHASE;
+    props.Rate = AL_CHORUS_DEFAULT_RATE;
+    props.Depth = AL_CHORUS_DEFAULT_DEPTH;
+    props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
+    props.Delay = AL_CHORUS_DEFAULT_DELAY;
+    return props;
+}
+
+EffectProps genDefaultFlangerProps() noexcept
+{
+    FlangerProps props{};
+    props.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM);
+    props.Phase = AL_FLANGER_DEFAULT_PHASE;
+    props.Rate = AL_FLANGER_DEFAULT_RATE;
+    props.Depth = AL_FLANGER_DEFAULT_DEPTH;
+    props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
+    props.Delay = AL_FLANGER_DEFAULT_DELAY;
+    return props;
+}
+
+} // namespace
+
+const EffectProps ChorusEffectProps{genDefaultChorusProps()};
+
+void EffectHandler::SetParami(ChorusProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_CHORUS_WAVEFORM:
         if(auto formopt = WaveformFromEnum(val))
-            props->Chorus.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val};
         break;
@@ -60,115 +88,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val)
     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;
+        props.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)
+void EffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
+void EffectHandler::SetParamf(ChorusProps &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;
+        props.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;
+        props.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;
+        props.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;
+        props.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 EffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Chorus_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const ChorusProps &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;
+    case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
+    case AL_CHORUS_PHASE: *val = props.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)
+void EffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const ChorusProps &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;
+    case AL_CHORUS_RATE: *val = props.Rate; break;
+    case AL_CHORUS_DEPTH: *val = props.Depth; break;
+    case AL_CHORUS_FEEDBACK: *val = props.Feedback; break;
+    case AL_CHORUS_DELAY: *val = props.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); }
+void EffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals)
+{ 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;
-}
 
+const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
 
-void Flanger_setParami(EffectProps *props, ALenum param, int val)
+void EffectHandler::SetParami(FlangerProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_FLANGER_WAVEFORM:
         if(auto formopt = WaveformFromEnum(val))
-            props->Flanger.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val};
         break;
@@ -176,116 +178,78 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val)
     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->Flanger.Phase = val;
+        props.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)
+void EffectHandler::SetParamiv(FlangerProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
+void EffectHandler::SetParamf(FlangerProps &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->Flanger.Rate = val;
+        props.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->Flanger.Depth = val;
+        props.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->Flanger.Feedback = val;
+        props.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->Flanger.Delay = val;
+        props.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 EffectHandler::SetParamfv(FlangerProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Flanger_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const FlangerProps &props, ALenum param, int *val)
 {
     switch(param)
     {
-    case AL_FLANGER_WAVEFORM:
-        *val = EnumFromWaveform(props->Flanger.Waveform);
-        break;
-
-    case AL_FLANGER_PHASE:
-        *val = props->Flanger.Phase;
-        break;
+    case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break;
+    case AL_FLANGER_PHASE: *val = props.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)
+void EffectHandler::GetParamiv(const FlangerProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const FlangerProps &props, ALenum param, float *val)
 {
     switch(param)
     {
-    case AL_FLANGER_RATE:
-        *val = props->Flanger.Rate;
-        break;
-
-    case AL_FLANGER_DEPTH:
-        *val = props->Flanger.Depth;
-        break;
-
-    case AL_FLANGER_FEEDBACK:
-        *val = props->Flanger.Feedback;
-        break;
-
-    case AL_FLANGER_DELAY:
-        *val = props->Flanger.Delay;
-        break;
+    case AL_FLANGER_RATE: *val = props.Rate; break;
+    case AL_FLANGER_DEPTH: *val = props.Depth; break;
+    case AL_FLANGER_FEEDBACK: *val = props.Feedback; break;
+    case AL_FLANGER_DELAY: *val = props.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.Flanger.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM);
-    props.Flanger.Phase = AL_FLANGER_DEFAULT_PHASE;
-    props.Flanger.Rate = AL_FLANGER_DEFAULT_RATE;
-    props.Flanger.Depth = AL_FLANGER_DEFAULT_DEPTH;
-    props.Flanger.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
-    props.Flanger.Delay = AL_FLANGER_DEFAULT_DELAY;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Chorus);
-
-const EffectProps ChorusEffectProps{genDefaultChorusProps()};
-
-DEFINE_ALEFFECT_VTABLE(Flanger);
-
-const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
+void EffectHandler::GetParamfv(const FlangerProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
 
 #ifdef ALSOFT_EAX
@@ -626,7 +590,7 @@ template<>
 bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props)
 {
     using Committer = ChorusFlangerEffect<EaxChorusTraits>;
-    return Committer::Commit(props, mEaxProps, mAlProps.Chorus);
+    return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
 }
 
 void EaxChorusCommitter::SetDefaults(EaxEffectProps &props)
@@ -663,7 +627,7 @@ template<>
 bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props)
 {
     using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
-    return Committer::Commit(props, mEaxProps, mAlProps.Flanger);
+    return Committer::Commit(props, mEaxProps, mAlProps.emplace<FlangerProps>());
 }
 
 void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props)
diff --git a/al/effects/compressor.cpp b/al/effects/compressor.cpp
index 9c4308f4..9fcc8c61 100644
--- a/al/effects/compressor.cpp
+++ b/al/effects/compressor.cpp
@@ -16,14 +16,25 @@
 
 namespace {
 
-void Compressor_setParami(EffectProps *props, ALenum param, int val)
+EffectProps genDefaultProps() noexcept
+{
+    CompressorProps props{};
+    props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
+    return props;
+}
+
+} // namespace
+
+const EffectProps CompressorEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(CompressorProps &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);
+        props.OnOff = (val != AL_FALSE);
         break;
 
     default:
@@ -31,22 +42,22 @@ void Compressor_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Compressor_setParami(props, param, vals[0]); }
-void Compressor_setParamf(EffectProps*, ALenum param, float)
+void EffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
+void EffectHandler::SetParamf(CompressorProps&, ALenum param, float)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; }
-void Compressor_setParamfv(EffectProps*, ALenum param, const float*)
+void EffectHandler::SetParamfv(CompressorProps&, 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)
+void EffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val)
 { 
     switch(param)
     {
     case AL_COMPRESSOR_ONOFF:
-        *val = props->Compressor.OnOff;
+        *val = props.OnOff;
         break;
 
     default:
@@ -54,28 +65,16 @@ void Compressor_getParami(const EffectProps *props, ALenum param, int *val)
             param};
     }
 }
-void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals)
-{ Compressor_getParami(props, param, vals); }
-void Compressor_getParamf(const EffectProps*, ALenum param, float*)
+void EffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const CompressorProps&, ALenum param, float*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; }
-void Compressor_getParamfv(const EffectProps*, ALenum param, float*)
+void EffectHandler::GetParamfv(const CompressorProps&, 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 {
@@ -121,8 +120,8 @@ bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props)
         return false;
 
     mEaxProps = props;
+    mAlProps = CompressorProps{props.ulOnOff != 0};
 
-    mAlProps.Compressor.OnOff = props.ulOnOff != 0;
     return true;
 }
 
diff --git a/al/effects/convolution.cpp b/al/effects/convolution.cpp
index 9c091e53..1f8d1111 100644
--- a/al/effects/convolution.cpp
+++ b/al/effects/convolution.cpp
@@ -12,33 +12,45 @@
 
 namespace {
 
-void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
+EffectProps genDefaultProps() noexcept
+{
+    ConvolutionProps props{};
+    props.OrientAt = {0.0f,  0.0f, -1.0f};
+    props.OrientUp = {0.0f,  1.0f,  0.0f};
+    return props;
+}
+
+} // namespace
+
+const EffectProps ConvolutionEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x",
             param};
     }
 }
-void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals)
+void EffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals)
 {
     switch(param)
     {
     default:
-        Convolution_setParami(props, param, vals[0]);
+        SetParami(props, param, vals[0]);
     }
 }
-void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
+void EffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x",
             param};
     }
 }
-void Convolution_setParamfv(EffectProps *props, ALenum param, const float *values)
+void EffectHandler::SetParamfv(ConvolutionProps &props, ALenum param, const float *values)
 {
     switch(param)
     {
@@ -47,73 +59,59 @@ void Convolution_setParamfv(EffectProps *props, ALenum param, const float *value
             && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
             throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param};
 
-        props->Convolution.OrientAt[0] = values[0];
-        props->Convolution.OrientAt[1] = values[1];
-        props->Convolution.OrientAt[2] = values[2];
-        props->Convolution.OrientUp[0] = values[3];
-        props->Convolution.OrientUp[1] = values[4];
-        props->Convolution.OrientUp[2] = values[5];
+        props.OrientAt[0] = values[0];
+        props.OrientAt[1] = values[1];
+        props.OrientAt[2] = values[2];
+        props.OrientUp[0] = values[3];
+        props.OrientUp[1] = values[4];
+        props.OrientUp[2] = values[5];
         break;
 
     default:
-        Convolution_setParamf(props, param, values[0]);
+        SetParamf(props, param, values[0]);
     }
 }
 
-void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
+void EffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x",
             param};
     }
 }
-void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals)
+void EffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals)
 {
     switch(param)
     {
     default:
-        Convolution_getParami(props, param, vals);
+        GetParami(props, param, vals);
     }
 }
-void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
+void EffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/)
 {
     switch(param)
     {
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
+        throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x",
             param};
     }
 }
-void Convolution_getParamfv(const EffectProps *props, ALenum param, float *values)
+void EffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values)
 {
     switch(param)
     {
     case AL_CONVOLUTION_ORIENTATION_SOFT:
-        values[0] = props->Convolution.OrientAt[0];
-        values[1] = props->Convolution.OrientAt[1];
-        values[2] = props->Convolution.OrientAt[2];
-        values[3] = props->Convolution.OrientUp[0];
-        values[4] = props->Convolution.OrientUp[1];
-        values[5] = props->Convolution.OrientUp[2];
+        values[0] = props.OrientAt[0];
+        values[1] = props.OrientAt[1];
+        values[2] = props.OrientAt[2];
+        values[3] = props.OrientUp[0];
+        values[4] = props.OrientUp[1];
+        values[5] = props.OrientUp[2];
         break;
 
     default:
-        Convolution_getParamf(props, param, values);
+        GetParamf(props, param, values);
     }
 }
-
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    props.Convolution.OrientAt = {0.0f,  0.0f, -1.0f};
-    props.Convolution.OrientUp = {0.0f,  1.0f,  0.0f};
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Convolution);
-
-const EffectProps ConvolutionEffectProps{genDefaultProps()};
diff --git a/al/effects/dedicated.cpp b/al/effects/dedicated.cpp
index f5edfd51..518c224c 100644
--- a/al/effects/dedicated.cpp
+++ b/al/effects/dedicated.cpp
@@ -12,120 +12,115 @@
 
 namespace {
 
-void DedicatedDialog_setParami(EffectProps*, ALenum param, int)
+EffectProps genDefaultDialogProps() noexcept
+{
+    DedicatedDialogProps props{};
+    props.Gain = 1.0f;
+    return props;
+}
+
+EffectProps genDefaultLfeProps() noexcept
+{
+    DedicatedLfeProps props{};
+    props.Gain = 1.0f;
+    return props;
+}
+
+} // namespace
+
+const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()};
+
+void EffectHandler::SetParami(DedicatedDialogProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
-void DedicatedDialog_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(DedicatedDialogProps&, ALenum param, const int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
         param};
 }
-void DedicatedDialog_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(DedicatedDialogProps &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->DedicatedDialog.Gain = val;
+        props.Gain = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
     }
 }
-void DedicatedDialog_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ DedicatedDialog_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(DedicatedDialogProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void DedicatedDialog_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const DedicatedDialogProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
-void DedicatedDialog_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const DedicatedDialogProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
         param};
 }
-void DedicatedDialog_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const DedicatedDialogProps &props, ALenum param, float *val)
 {
     switch(param)
     {
     case AL_DEDICATED_GAIN:
-        *val = props->DedicatedDialog.Gain;
+        *val = props.Gain;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
     }
 }
-void DedicatedDialog_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ DedicatedDialog_getParamf(props, param, vals); }
+void EffectHandler::GetParamfv(const DedicatedDialogProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
 
-void DedicatedLfe_setParami(EffectProps*, ALenum param, int)
+const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()};
+
+void EffectHandler::SetParami(DedicatedLfeProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
-void DedicatedLfe_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(DedicatedLfeProps&, ALenum param, const int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
         param};
 }
-void DedicatedLfe_setParamf(EffectProps *props, ALenum param, float val)
+void EffectHandler::SetParamf(DedicatedLfeProps &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->DedicatedLfe.Gain = val;
+        props.Gain = val;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
     }
 }
-void DedicatedLfe_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ DedicatedLfe_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(DedicatedLfeProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void DedicatedLfe_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const DedicatedLfeProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
-void DedicatedLfe_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const DedicatedLfeProps&, ALenum param, int*)
 {
     throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
         param};
 }
-void DedicatedLfe_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const DedicatedLfeProps &props, ALenum param, float *val)
 {
     switch(param)
     {
     case AL_DEDICATED_GAIN:
-        *val = props->DedicatedLfe.Gain;
+        *val = props.Gain;
         break;
 
     default:
         throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
     }
 }
-void DedicatedLfe_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ DedicatedLfe_getParamf(props, param, vals); }
-
-
-EffectProps genDefaultDialogProps() noexcept
-{
-    EffectProps props{};
-    props.DedicatedDialog.Gain = 1.0f;
-    return props;
-}
-
-EffectProps genDefaultLfeProps() noexcept
-{
-    EffectProps props{};
-    props.DedicatedLfe.Gain = 1.0f;
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(DedicatedDialog);
-
-const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()};
-
-DEFINE_ALEFFECT_VTABLE(DedicatedLfe);
-
-const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()};
+void EffectHandler::GetParamfv(const DedicatedLfeProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
diff --git a/al/effects/distortion.cpp b/al/effects/distortion.cpp
index bec2af53..534b6c24 100644
--- a/al/effects/distortion.cpp
+++ b/al/effects/distortion.cpp
@@ -16,108 +16,93 @@
 
 namespace {
 
-void Distortion_setParami(EffectProps*, ALenum param, int)
+EffectProps genDefaultProps() noexcept
+{
+    DistortionProps props{};
+    props.Edge = AL_DISTORTION_DEFAULT_EDGE;
+    props.Gain = AL_DISTORTION_DEFAULT_GAIN;
+    props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
+    props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
+    props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
+    return props;
+}
+
+} // namespace
+
+const EffectProps DistortionEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(DistortionProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
-void Distortion_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(DistortionProps&, 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)
+void EffectHandler::SetParamf(DistortionProps &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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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 EffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Distortion_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const DistortionProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
-void Distortion_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const DistortionProps&, 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)
+void EffectHandler::GetParamf(const DistortionProps &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;
+    case AL_DISTORTION_EDGE: *val = props.Edge; break;
+    case AL_DISTORTION_GAIN: *val = props.Gain; break;
+    case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break;
+    case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break;
+    case AL_DISTORTION_EQBANDWIDTH: *val = props.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); }
+void EffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals)
+{ 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 {
@@ -210,12 +195,15 @@ bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props)
         return false;
 
     mEaxProps = props;
-
-    mAlProps.Distortion.Edge = props.flEdge;
-    mAlProps.Distortion.Gain = level_mb_to_gain(static_cast<float>(props.lGain));
-    mAlProps.Distortion.LowpassCutoff = props.flLowPassCutOff;
-    mAlProps.Distortion.EQCenter = props.flEQCenter;
-    mAlProps.Distortion.EQBandwidth = props.flEdge;
+    mAlProps = [&]{
+        DistortionProps ret{};
+        ret.Edge = props.flEdge;
+        ret.Gain = level_mb_to_gain(static_cast<float>(props.lGain));
+        ret.LowpassCutoff = props.flLowPassCutOff;
+        ret.EQCenter = props.flEQCenter;
+        ret.EQBandwidth = props.flEdge;
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/echo.cpp b/al/effects/echo.cpp
index 90f109da..9412039b 100644
--- a/al/effects/echo.cpp
+++ b/al/effects/echo.cpp
@@ -19,102 +19,87 @@ 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)
+EffectProps genDefaultProps() noexcept
+{
+    EchoProps props{};
+    props.Delay    = AL_ECHO_DEFAULT_DELAY;
+    props.LRDelay  = AL_ECHO_DEFAULT_LRDELAY;
+    props.Damping  = AL_ECHO_DEFAULT_DAMPING;
+    props.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
+    props.Spread   = AL_ECHO_DEFAULT_SPREAD;
+    return props;
+}
+
+} // namespace
+
+const EffectProps EchoEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(EchoProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
-void Echo_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(EchoProps&, 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)
+void EffectHandler::SetParamf(EchoProps &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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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 EffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Echo_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const EchoProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
-void Echo_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const EchoProps&, 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)
+void EffectHandler::GetParamf(const EchoProps &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;
+    case AL_ECHO_DELAY: *val = props.Delay; break;
+    case AL_ECHO_LRDELAY: *val = props.LRDelay; break;
+    case AL_ECHO_DAMPING: *val = props.Damping; break;
+    case AL_ECHO_FEEDBACK: *val = props.Feedback; break;
+    case AL_ECHO_SPREAD: *val = props.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); }
+void EffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals)
+{ 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 {
@@ -207,12 +192,15 @@ bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props)
         return false;
 
     mEaxProps = props;
-
-    mAlProps.Echo.Delay = props.flDelay;
-    mAlProps.Echo.LRDelay = props.flLRDelay;
-    mAlProps.Echo.Damping = props.flDamping;
-    mAlProps.Echo.Feedback = props.flFeedback;
-    mAlProps.Echo.Spread = props.flSpread;
+    mAlProps = [&]{
+        EchoProps ret{};
+        ret.Delay = props.flDelay;
+        ret.LRDelay = props.flLRDelay;
+        ret.Damping = props.flDamping;
+        ret.Feedback = props.flFeedback;
+        ret.Spread = props.flSpread;
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/effects.h b/al/effects/effects.h
index 2e49eb00..38d47f86 100644
--- a/al/effects/effects.h
+++ b/al/effects/effects.h
@@ -3,14 +3,52 @@
 
 #include "AL/al.h"
 
+#include "core/effects/base.h"
 #include "core/except.h"
 
 #ifdef ALSOFT_EAX
 #include "al/eax/effect.h"
 #endif // ALSOFT_EAX
 
-union EffectProps;
 
+struct EffectHandler {
+#define DECL_HANDLER(T)                                                       \
+    static void SetParami(T &props, ALenum param, int val);                   \
+    static void SetParamiv(T &props, ALenum param, const int *vals);          \
+    static void SetParamf(T &props, ALenum param, float val);                 \
+    static void SetParamfv(T &props, ALenum param, const float *vals);        \
+    static void GetParami(const T &props, ALenum param, int *val);            \
+    static void GetParamiv(const T &props, ALenum param, int *vals);          \
+    static void GetParamf(const T &props, ALenum param, float *val);          \
+    static void GetParamfv(const T &props, ALenum param, float *vals);
+
+    DECL_HANDLER(std::monostate)
+    DECL_HANDLER(ReverbProps)
+    DECL_HANDLER(ChorusProps)
+    DECL_HANDLER(AutowahProps)
+    DECL_HANDLER(CompressorProps)
+    DECL_HANDLER(ConvolutionProps)
+    DECL_HANDLER(DedicatedDialogProps)
+    DECL_HANDLER(DedicatedLfeProps)
+    DECL_HANDLER(DistortionProps)
+    DECL_HANDLER(EchoProps)
+    DECL_HANDLER(EqualizerProps)
+    DECL_HANDLER(FlangerProps)
+    DECL_HANDLER(FshifterProps)
+    DECL_HANDLER(ModulatorProps)
+    DECL_HANDLER(PshifterProps)
+    DECL_HANDLER(VmorpherProps)
+#undef DECL_HANDLER
+
+    static void StdReverbSetParami(ReverbProps &props, ALenum param, int val);
+    static void StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals);
+    static void StdReverbSetParamf(ReverbProps &props, ALenum param, float val);
+    static void StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals);
+    static void StdReverbGetParami(const ReverbProps &props, ALenum param, int *val);
+    static void StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals);
+    static void StdReverbGetParamf(const ReverbProps &props, ALenum param, float *val);
+    static void StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals);
+};
 
 class effect_exception final : public al::base_exception {
     ALenum mErrorCode;
@@ -28,27 +66,6 @@ public:
 };
 
 
-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;
@@ -68,23 +85,4 @@ extern const EffectProps DedicatedDialogEffectProps;
 extern const EffectProps DedicatedLfeEffectProps;
 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 DedicatedDialogEffectVtable;
-extern const EffectVtable DedicatedLfeEffectVtable;
-extern const EffectVtable ConvolutionEffectVtable;
-
 #endif /* AL_EFFECTS_EFFECTS_H */
diff --git a/al/effects/equalizer.cpp b/al/effects/equalizer.cpp
index 921d1090..74fc43fc 100644
--- a/al/effects/equalizer.cpp
+++ b/al/effects/equalizer.cpp
@@ -16,163 +16,133 @@
 
 namespace {
 
-void Equalizer_setParami(EffectProps*, ALenum param, int)
+EffectProps genDefaultProps() noexcept
+{
+    EqualizerProps props{};
+    props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
+    props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
+    props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
+    props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
+    props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
+    props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
+    props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
+    props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
+    props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
+    props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
+    return props;
+}
+
+} // namespace
+
+const EffectProps EqualizerEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(EqualizerProps&, ALenum param, int)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
-void Equalizer_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(EqualizerProps&, 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)
+void EffectHandler::SetParamf(EqualizerProps &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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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 EffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Equalizer_getParami(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParami(const EqualizerProps&, ALenum param, int*)
 { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
-void Equalizer_getParamiv(const EffectProps*, ALenum param, int*)
+void EffectHandler::GetParamiv(const EqualizerProps&, 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)
+void EffectHandler::GetParamf(const EqualizerProps &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;
+    case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break;
+    case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break;
+    case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break;
+    case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break;
+    case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break;
+    case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break;
+    case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break;
+    case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break;
+    case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break;
+    case AL_EQUALIZER_HIGH_CUTOFF: *val = props.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;
-}
+void EffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Equalizer);
-
-const EffectProps EqualizerEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
@@ -325,17 +295,20 @@ bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props)
         return false;
 
     mEaxProps = props;
-
-    mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast<float>(props.lLowGain));
-    mAlProps.Equalizer.LowCutoff = props.flLowCutOff;
-    mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast<float>(props.lMid1Gain));
-    mAlProps.Equalizer.Mid1Center = props.flMid1Center;
-    mAlProps.Equalizer.Mid1Width = props.flMid1Width;
-    mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast<float>(props.lMid2Gain));
-    mAlProps.Equalizer.Mid2Center = props.flMid2Center;
-    mAlProps.Equalizer.Mid2Width = props.flMid2Width;
-    mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast<float>(props.lHighGain));
-    mAlProps.Equalizer.HighCutoff = props.flHighCutOff;
+    mAlProps = [&]{
+        EqualizerProps ret{};
+        ret.LowGain = level_mb_to_gain(static_cast<float>(props.lLowGain));
+        ret.LowCutoff = props.flLowCutOff;
+        ret.Mid1Gain = level_mb_to_gain(static_cast<float>(props.lMid1Gain));
+        ret.Mid1Center = props.flMid1Center;
+        ret.Mid1Width = props.flMid1Width;
+        ret.Mid2Gain = level_mb_to_gain(static_cast<float>(props.lMid2Gain));
+        ret.Mid2Center = props.flMid2Center;
+        ret.Mid2Width = props.flMid2Width;
+        ret.HighGain = level_mb_to_gain(static_cast<float>(props.lHighGain));
+        ret.HighCutoff = props.flHighCutOff;
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/fshifter.cpp b/al/effects/fshifter.cpp
index a6b3c86c..6f19e0dd 100644
--- a/al/effects/fshifter.cpp
+++ b/al/effects/fshifter.cpp
@@ -41,31 +41,26 @@ ALenum EnumFromDirection(FShifterDirection dir)
     throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast<int>(dir))};
 }
 
-void Fshifter_setParamf(EffectProps *props, ALenum param, float val)
+EffectProps genDefaultProps() noexcept
 {
-    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};
-    }
+    FshifterProps props{};
+    props.Frequency      = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
+    props.LeftDirection  = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION);
+    props.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION);
+    return props;
 }
-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)
+} // namespace
+
+const EffectProps FshifterEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(FshifterProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
         if(auto diropt = DirectionFromEmum(val))
-            props->Fshifter.LeftDirection = *diropt;
+            props.LeftDirection = *diropt;
         else
             throw effect_exception{AL_INVALID_VALUE,
                 "Unsupported frequency shifter left direction: 0x%04x", val};
@@ -73,7 +68,7 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val)
 
     case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
         if(auto diropt = DirectionFromEmum(val))
-            props->Fshifter.RightDirection = *diropt;
+            props.RightDirection = *diropt;
         else
             throw effect_exception{AL_INVALID_VALUE,
                 "Unsupported frequency shifter right direction: 0x%04x", val};
@@ -84,33 +79,52 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val)
             "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 EffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
+
+void EffectHandler::SetParamf(FshifterProps &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.Frequency = val;
+        break;
+
+    default:
+        throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
+            param};
+    }
+}
+void EffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Fshifter_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val)
 {
     switch(param)
     {
     case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
-        *val = EnumFromDirection(props->Fshifter.LeftDirection);
+        *val = EnumFromDirection(props.LeftDirection);
         break;
     case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
-        *val = EnumFromDirection(props->Fshifter.RightDirection);
+        *val = EnumFromDirection(props.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 EffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
 
-void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
+void EffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val)
 {
     switch(param)
     {
     case AL_FREQUENCY_SHIFTER_FREQUENCY:
-        *val = props->Fshifter.Frequency;
+        *val = props.Frequency;
         break;
 
     default:
@@ -118,23 +132,9 @@ void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
             param};
     }
 }
-void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ Fshifter_getParamf(props, param, vals); }
+void EffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals)
+{ 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 {
@@ -213,9 +213,13 @@ bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &p
         return FShifterDirection::Off;
     };
 
-    mAlProps.Fshifter.Frequency = props.flFrequency;
-    mAlProps.Fshifter.LeftDirection = get_direction(props.ulLeftDirection);
-    mAlProps.Fshifter.RightDirection = get_direction(props.ulRightDirection);
+    mAlProps = [&]{
+        FshifterProps ret{};
+        ret.Frequency = props.flFrequency;
+        ret.LeftDirection = get_direction(props.ulLeftDirection);
+        ret.RightDirection = get_direction(props.ulRightDirection);
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/modulator.cpp b/al/effects/modulator.cpp
index d0a2df02..566b333e 100644
--- a/al/effects/modulator.cpp
+++ b/al/effects/modulator.cpp
@@ -42,40 +42,31 @@ ALenum EnumFromWaveform(ModulatorWaveform type)
         std::to_string(static_cast<int>(type))};
 }
 
-void Modulator_setParamf(EffectProps *props, ALenum param, float val)
+EffectProps genDefaultProps() noexcept
 {
-    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;
+    ModulatorProps props{};
+    props.Frequency      = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
+    props.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
+    props.Waveform       = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM);
+    return props;
+}
 
-    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;
+} // namespace
 
-    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)
+const EffectProps ModulatorEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(ModulatorProps &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));
+        SetParamf(props, param, static_cast<float>(val));
         break;
 
     case AL_RING_MODULATOR_WAVEFORM:
         if(auto formopt = WaveformFromEmum(val))
-            props->Modulator.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val};
         break;
@@ -85,62 +76,61 @@ void Modulator_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Modulator_setParami(props, param, vals[0]); }
+void EffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
 
-void Modulator_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val)
 {
     switch(param)
     {
     case AL_RING_MODULATOR_FREQUENCY:
-        *val = static_cast<int>(props->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.Frequency = val;
         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);
+        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.HighPassCutoff = val;
         break;
 
+    default:
+        throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
+    }
+}
+void EffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
+
+void EffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val)
+{
+    switch(param)
+    {
+    case AL_RING_MODULATOR_FREQUENCY: *val = static_cast<int>(props.Frequency); break;
+    case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(props.HighPassCutoff); break;
+    case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.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)
+void EffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const ModulatorProps &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;
+    case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break;
+    case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.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
+void EffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-DEFINE_ALEFFECT_VTABLE(Modulator);
-
-const EffectProps ModulatorEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
@@ -221,9 +211,13 @@ bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props)
         return ModulatorWaveform::Sinusoid;
     };
 
-    mAlProps.Modulator.Frequency = props.flFrequency;
-    mAlProps.Modulator.HighPassCutoff = props.flHighPassCutOff;
-    mAlProps.Modulator.Waveform = get_waveform(props.ulWaveform);
+    mAlProps = [&]{
+        ModulatorProps ret{};
+        ret.Frequency = props.flFrequency;
+        ret.HighPassCutoff = props.flHighPassCutOff;
+        ret.Waveform = get_waveform(props.ulWaveform);
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/null.cpp b/al/effects/null.cpp
index 4b68a28f..4b00ef4f 100644
--- a/al/effects/null.cpp
+++ b/al/effects/null.cpp
@@ -14,7 +14,17 @@
 
 namespace {
 
-void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
+EffectProps genDefaultProps() noexcept
+{
+    EffectProps props{};
+    return props;
+}
+
+} // namespace
+
+const EffectProps NullEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/)
 {
     switch(param)
     {
@@ -23,15 +33,15 @@ void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
             param};
     }
 }
-void Null_setParamiv(EffectProps *props, ALenum param, const int *vals)
+void EffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals)
 {
     switch(param)
     {
     default:
-        Null_setParami(props, param, vals[0]);
+        SetParami(props, param, vals[0]);
     }
 }
-void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
+void EffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/)
 {
     switch(param)
     {
@@ -40,16 +50,16 @@ void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
             param};
     }
 }
-void Null_setParamfv(EffectProps *props, ALenum param, const float *vals)
+void EffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals)
 {
     switch(param)
     {
     default:
-        Null_setParamf(props, param, vals[0]);
+        SetParamf(props, param, vals[0]);
     }
 }
 
-void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
+void EffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/)
 {
     switch(param)
     {
@@ -58,15 +68,15 @@ void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
             param};
     }
 }
-void Null_getParamiv(const EffectProps *props, ALenum param, int *vals)
+void EffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals)
 {
     switch(param)
     {
     default:
-        Null_getParami(props, param, vals);
+        GetParami(props, param, vals);
     }
 }
-void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
+void EffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/)
 {
     switch(param)
     {
@@ -75,27 +85,15 @@ void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
             param};
     }
 }
-void Null_getParamfv(const EffectProps *props, ALenum param, float *vals)
+void EffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals)
 {
     switch(param)
     {
     default:
-        Null_getParamf(props, param, vals);
+        GetParamf(props, param, vals);
     }
 }
 
-EffectProps genDefaultProps() noexcept
-{
-    EffectProps props{};
-    return props;
-}
-
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Null);
-
-const EffectProps NullEffectProps{genDefaultProps()};
-
 
 #ifdef ALSOFT_EAX
 namespace {
@@ -121,12 +119,13 @@ bool EaxNullCommitter::commit(const std::monostate &props)
 {
     const bool ret{std::holds_alternative<std::monostate>(mEaxProps)};
     mEaxProps = props;
+    mAlProps = std::monostate{};
     return ret;
 }
 
 void EaxNullCommitter::SetDefaults(EaxEffectProps &props)
 {
-    props.emplace<std::monostate>();
+    props = std::monostate{};
 }
 
 void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&)
diff --git a/al/effects/pshifter.cpp b/al/effects/pshifter.cpp
index f29a3593..b36fe034 100644
--- a/al/effects/pshifter.cpp
+++ b/al/effects/pshifter.cpp
@@ -16,28 +16,32 @@
 
 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*)
+EffectProps genDefaultProps() noexcept
 {
-    throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x",
-        param};
+    PshifterProps props{};
+    props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
+    props.FineTune   = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
+    return props;
 }
 
-void Pshifter_setParami(EffectProps *props, ALenum param, int val)
+} // namespace
+
+const EffectProps PshifterEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(PshifterProps &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;
+        props.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;
+        props.FineTune = val;
         break;
 
     default:
@@ -45,49 +49,40 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
-{ Pshifter_setParami(props, param, vals[0]); }
+void EffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
+
+void EffectHandler::SetParamf(PshifterProps&, ALenum param, float)
+{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
+void EffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*)
+{
+    throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x",
+        param};
+}
 
-void Pshifter_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const PshifterProps &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;
+    case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break;
+    case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.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 EffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
 
-void Pshifter_getParamf(const EffectProps*, ALenum param, float*)
+void EffectHandler::GetParamf(const PshifterProps&, 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*)
+void EffectHandler::GetParamfv(const PshifterProps&, 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 {
@@ -144,9 +139,12 @@ bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props)
         return false;
 
     mEaxProps = props;
-
-    mAlProps.Pshifter.CoarseTune = static_cast<int>(props.lCoarseTune);
-    mAlProps.Pshifter.FineTune = static_cast<int>(props.lFineTune);
+    mAlProps = [&]{
+        PshifterProps ret{};
+        ret.CoarseTune = static_cast<int>(props.lCoarseTune);
+        ret.FineTune = static_cast<int>(props.lFineTune);
+        return ret;
+    }();
 
     return true;
 }
diff --git a/al/effects/reverb.cpp b/al/effects/reverb.cpp
index 7f549f04..f0df51b2 100644
--- a/al/effects/reverb.cpp
+++ b/al/effects/reverb.cpp
@@ -20,14 +20,80 @@
 
 namespace {
 
-void Reverb_setParami(EffectProps *props, ALenum param, int val)
+EffectProps genDefaultProps() noexcept
+{
+    ReverbProps props{};
+    props.Density   = AL_EAXREVERB_DEFAULT_DENSITY;
+    props.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
+    props.Gain   = AL_EAXREVERB_DEFAULT_GAIN;
+    props.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
+    props.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
+    props.DecayTime    = AL_EAXREVERB_DEFAULT_DECAY_TIME;
+    props.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
+    props.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
+    props.ReflectionsGain   = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
+    props.ReflectionsDelay  = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
+    props.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+    props.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+    props.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
+    props.LateReverbGain   = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
+    props.LateReverbDelay  = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
+    props.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+    props.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+    props.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
+    props.EchoTime  = AL_EAXREVERB_DEFAULT_ECHO_TIME;
+    props.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
+    props.ModulationTime  = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
+    props.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
+    props.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+    props.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
+    props.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
+    props.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+    props.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
+    return props;
+}
+
+EffectProps genDefaultStdProps() noexcept
+{
+    ReverbProps props{};
+    props.Density   = AL_REVERB_DEFAULT_DENSITY;
+    props.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
+    props.Gain   = AL_REVERB_DEFAULT_GAIN;
+    props.GainHF = AL_REVERB_DEFAULT_GAINHF;
+    props.GainLF = 1.0f;
+    props.DecayTime    = AL_REVERB_DEFAULT_DECAY_TIME;
+    props.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
+    props.DecayLFRatio = 1.0f;
+    props.ReflectionsGain  = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
+    props.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
+    props.ReflectionsPan   = {0.0f, 0.0f, 0.0f};
+    props.LateReverbGain  = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
+    props.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
+    props.LateReverbPan   = {0.0f, 0.0f, 0.0f};
+    props.EchoTime  = 0.25f;
+    props.EchoDepth = 0.0f;
+    props.ModulationTime  = 0.25f;
+    props.ModulationDepth = 0.0f;
+    props.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
+    props.HFReference = 5000.0f;
+    props.LFReference = 250.0f;
+    props.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
+    props.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
+    return props;
+}
+
+} // namespace
+
+const EffectProps ReverbEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(ReverbProps &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;
+        props.DecayHFLimit = val != AL_FALSE;
         break;
 
     default:
@@ -35,167 +101,167 @@ void Reverb_setParami(EffectProps *props, ALenum param, int val)
             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)
+void EffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals)
+{ SetParami(props, param, vals[0]); }
+void EffectHandler::SetParamf(ReverbProps &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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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;
+        props.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)
+void EffectHandler::SetParamfv(ReverbProps &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];
+        props.ReflectionsPan[0] = vals[0];
+        props.ReflectionsPan[1] = vals[1];
+        props.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];
+        props.LateReverbPan[0] = vals[0];
+        props.LateReverbPan[1] = vals[1];
+        props.LateReverbPan[2] = vals[2];
         break;
 
     default:
-        Reverb_setParamf(props, param, vals[0]);
+        SetParamf(props, param, vals[0]);
         break;
     }
 }
 
-void Reverb_getParami(const EffectProps *props, ALenum param, int *val)
+void EffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val)
 {
     switch(param)
     {
     case AL_EAXREVERB_DECAY_HFLIMIT:
-        *val = props->Reverb.DecayHFLimit;
+        *val = props.DecayHFLimit;
         break;
 
     default:
@@ -203,365 +269,214 @@ void Reverb_getParami(const EffectProps *props, ALenum param, int *val)
             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)
+void EffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals)
+{ GetParami(props, param, vals); }
+void EffectHandler::GetParamf(const ReverbProps &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;
+    case AL_EAXREVERB_DENSITY: *val = props.Density; break;
+    case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; break;
+    case AL_EAXREVERB_GAIN: *val = props.Gain; break;
+    case AL_EAXREVERB_GAINHF: *val = props.GainHF; break;
+    case AL_EAXREVERB_GAINLF: *val = props.GainLF; break;
+    case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; break;
+    case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break;
+    case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; break;
+    case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break;
+    case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break;
+    case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break;
+    case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break;
+    case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; break;
+    case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; break;
+    case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; break;
+    case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; break;
+    case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break;
+    case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; break;
+    case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; break;
+    case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.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)
+void EffectHandler::GetParamfv(const ReverbProps &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];
+        vals[0] = props.ReflectionsPan[0];
+        vals[1] = props.ReflectionsPan[1];
+        vals[2] = props.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];
+        vals[0] = props.LateReverbPan[0];
+        vals[1] = props.LateReverbPan[1];
+        vals[2] = props.LateReverbPan[2];
         break;
 
     default:
-        Reverb_getParamf(props, param, vals);
+        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;
-}
 
+const EffectProps StdReverbEffectProps{genDefaultStdProps()};
 
-void StdReverb_setParami(EffectProps *props, ALenum param, int val)
+void EffectHandler::StdReverbSetParami(ReverbProps &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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"};
+        props.DecayHFLimit = val != AL_FALSE;
         break;
 
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX 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)
+void EffectHandler::StdReverbSetParamiv(ReverbProps &props, ALenum param, const int *vals)
+{ StdReverbSetParami(props, param, vals[0]); }
+void EffectHandler::StdReverbSetParamf(ReverbProps &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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"};
+        props.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;
+            throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"};
+        props.RoomRolloffFactor = val;
         break;
 
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
+    }
+}
+void EffectHandler::StdReverbSetParamfv(ReverbProps &props, ALenum param, const float *vals)
+{
+    switch(param)
+    {
+    default:
+        StdReverbSetParamf(props, param, vals[0]);
+        break;
     }
 }
-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)
+void EffectHandler::StdReverbGetParami(const ReverbProps &props, ALenum param, int *val)
 {
     switch(param)
     {
     case AL_REVERB_DECAY_HFLIMIT:
-        *val = props->Reverb.DecayHFLimit;
+        *val = props.DecayHFLimit;
         break;
 
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX 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)
+void EffectHandler::StdReverbGetParamiv(const ReverbProps &props, ALenum param, int *vals)
+{ StdReverbGetParami(props, param, vals); }
+void EffectHandler::StdReverbGetParamf(const ReverbProps &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;
+    case AL_REVERB_DENSITY: *val = props.Density; break;
+    case AL_REVERB_DIFFUSION: *val = props.Diffusion; break;
+    case AL_REVERB_GAIN: *val = props.Gain; break;
+    case AL_REVERB_GAINHF: *val = props.GainHF; break;
+    case AL_REVERB_DECAY_TIME: *val = props.DecayTime; break;
+    case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break;
+    case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break;
+    case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break;
+    case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break;
+    case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break;
+    case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break;
+    case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break;
 
     default:
-        throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param};
+        throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
     }
 }
-void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals)
-{ StdReverb_getParamf(props, param, vals); }
-
-EffectProps genDefaultStdProps() noexcept
+void EffectHandler::StdReverbGetParamfv(const ReverbProps &props, ALenum param, float *vals)
 {
-    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;
+    switch(param)
+    {
+    default:
+        StdReverbGetParamf(props, param, vals);
+        break;
+    }
 }
 
-} // namespace
-
-DEFINE_ALEFFECT_VTABLE(Reverb);
-
-const EffectProps ReverbEffectProps{genDefaultProps()};
-
-DEFINE_ALEFFECT_VTABLE(StdReverb);
-
-const EffectProps StdReverbEffectProps{genDefaultStdProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
@@ -1138,33 +1053,35 @@ bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props)
 
     const auto size = props.flEnvironmentSize;
     const auto density = (size * size * size) / 16.0f;
-    mAlProps.Reverb.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY);
-    mAlProps.Reverb.Diffusion = props.flEnvironmentDiffusion;
-    mAlProps.Reverb.Gain = level_mb_to_gain(static_cast<float>(props.lRoom));
-    mAlProps.Reverb.GainHF = level_mb_to_gain(static_cast<float>(props.lRoomHF));
-    mAlProps.Reverb.GainLF = level_mb_to_gain(static_cast<float>(props.lRoomLF));
-    mAlProps.Reverb.DecayTime = props.flDecayTime;
-    mAlProps.Reverb.DecayHFRatio = props.flDecayHFRatio;
-    mAlProps.Reverb.DecayLFRatio = props.flDecayLFRatio;
-    mAlProps.Reverb.ReflectionsGain = level_mb_to_gain(static_cast<float>(props.lReflections));
-    mAlProps.Reverb.ReflectionsDelay = props.flReflectionsDelay;
-    mAlProps.Reverb.ReflectionsPan[0] = props.vReflectionsPan.x;
-    mAlProps.Reverb.ReflectionsPan[1] = props.vReflectionsPan.y;
-    mAlProps.Reverb.ReflectionsPan[2] = props.vReflectionsPan.z;
-    mAlProps.Reverb.LateReverbGain = level_mb_to_gain(static_cast<float>(props.lReverb));
-    mAlProps.Reverb.LateReverbDelay = props.flReverbDelay;
-    mAlProps.Reverb.LateReverbPan[0] = props.vReverbPan.x;
-    mAlProps.Reverb.LateReverbPan[1] = props.vReverbPan.y;
-    mAlProps.Reverb.LateReverbPan[2] = props.vReverbPan.z;
-    mAlProps.Reverb.EchoTime = props.flEchoTime;
-    mAlProps.Reverb.EchoDepth = props.flEchoDepth;
-    mAlProps.Reverb.ModulationTime = props.flModulationTime;
-    mAlProps.Reverb.ModulationDepth = props.flModulationDepth;
-    mAlProps.Reverb.AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF);
-    mAlProps.Reverb.HFReference = props.flHFReference;
-    mAlProps.Reverb.LFReference = props.flLFReference;
-    mAlProps.Reverb.RoomRolloffFactor = props.flRoomRolloffFactor;
-    mAlProps.Reverb.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0);
+    mAlProps = [&]{
+        ReverbProps ret{};
+        ret.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY);
+        ret.Diffusion = props.flEnvironmentDiffusion;
+        ret.Gain = level_mb_to_gain(static_cast<float>(props.lRoom));
+        ret.GainHF = level_mb_to_gain(static_cast<float>(props.lRoomHF));
+        ret.GainLF = level_mb_to_gain(static_cast<float>(props.lRoomLF));
+        ret.DecayTime = props.flDecayTime;
+        ret.DecayHFRatio = props.flDecayHFRatio;
+        ret.DecayLFRatio = props.flDecayLFRatio;
+        ret.ReflectionsGain = level_mb_to_gain(static_cast<float>(props.lReflections));
+        ret.ReflectionsDelay = props.flReflectionsDelay;
+        ret.ReflectionsPan = {props.vReflectionsPan.x, props.vReflectionsPan.y,
+            props.vReflectionsPan.z};
+        ret.LateReverbGain = level_mb_to_gain(static_cast<float>(props.lReverb));
+        ret.LateReverbDelay = props.flReverbDelay;
+        ret.LateReverbPan = {props.vReverbPan.x, props.vReverbPan.y, props.vReverbPan.z};
+        ret.EchoTime = props.flEchoTime;
+        ret.EchoDepth = props.flEchoDepth;
+        ret.ModulationTime = props.flModulationTime;
+        ret.ModulationDepth = props.flModulationDepth;
+        ret.AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF);
+        ret.HFReference = props.flHFReference;
+        ret.LFReference = props.flLFReference;
+        ret.RoomRolloffFactor = props.flRoomRolloffFactor;
+        ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0);
+        return ret;
+    }();
+
     return true;
 }
 
diff --git a/al/effects/vmorpher.cpp b/al/effects/vmorpher.cpp
index 240c7b54..a986ddf7 100644
--- a/al/effects/vmorpher.cpp
+++ b/al/effects/vmorpher.cpp
@@ -122,13 +122,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type)
         std::to_string(static_cast<int>(type))};
 }
 
-void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
+EffectProps genDefaultProps() noexcept
+{
+    VmorpherProps props{};
+    props.Rate                 = AL_VOCAL_MORPHER_DEFAULT_RATE;
+    props.PhonemeA             = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA);
+    props.PhonemeB             = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB);
+    props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
+    props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
+    props.Waveform             = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM);
+    return props;
+}
+
+} // namespace
+
+const EffectProps VmorpherEffectProps{genDefaultProps()};
+
+void EffectHandler::SetParami(VmorpherProps &props, ALenum param, int val)
 {
     switch(param)
     {
     case AL_VOCAL_MORPHER_PHONEMEA:
         if(auto phenomeopt = PhenomeFromEnum(val))
-            props->Vmorpher.PhonemeA = *phenomeopt;
+            props.PhonemeA = *phenomeopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val};
         break;
@@ -136,12 +152,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
     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;
+        props.PhonemeACoarseTuning = val;
         break;
 
     case AL_VOCAL_MORPHER_PHONEMEB:
         if(auto phenomeopt = PhenomeFromEnum(val))
-            props->Vmorpher.PhonemeB = *phenomeopt;
+            props.PhonemeB = *phenomeopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val};
         break;
@@ -149,12 +165,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
     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;
+        props.PhonemeBCoarseTuning = val;
         break;
 
     case AL_VOCAL_MORPHER_WAVEFORM:
         if(auto formopt = WaveformFromEmum(val))
-            props->Vmorpher.Waveform = *formopt;
+            props.Waveform = *formopt;
         else
             throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val};
         break;
@@ -164,19 +180,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
             param};
     }
 }
-void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*)
+void EffectHandler::SetParamiv(VmorpherProps&, 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)
+void EffectHandler::SetParamf(VmorpherProps &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;
+        props.Rate = val;
         break;
 
     default:
@@ -184,49 +200,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
             param};
     }
 }
-void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals)
-{ Vmorpher_setParamf(props, param, vals[0]); }
+void EffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals)
+{ SetParamf(props, param, vals[0]); }
 
-void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val)
+void EffectHandler::GetParami(const VmorpherProps &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;
+    case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break;
+    case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break;
+    case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break;
+    case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break;
+    case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.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*)
+void EffectHandler::GetParamiv(const VmorpherProps&, 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)
+void EffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val)
 {
     switch(param)
     {
     case AL_VOCAL_MORPHER_RATE:
-        *val = props->Vmorpher.Rate;
+        *val = props.Rate;
         break;
 
     default:
@@ -234,26 +236,9 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
             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);
+void EffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals)
+{ GetParamf(props, param, vals); }
 
-const EffectProps VmorpherEffectProps{genDefaultProps()};
 
 #ifdef ALSOFT_EAX
 namespace {
@@ -406,12 +391,16 @@ bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props)
         return VMorpherWaveform::Sinusoid;
     };
 
-    mAlProps.Vmorpher.PhonemeA = get_phoneme(props.ulPhonemeA);
-    mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast<int>(props.lPhonemeACoarseTuning);
-    mAlProps.Vmorpher.PhonemeB = get_phoneme(props.ulPhonemeB);
-    mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast<int>(props.lPhonemeBCoarseTuning);
-    mAlProps.Vmorpher.Waveform = get_waveform(props.ulWaveform);
-    mAlProps.Vmorpher.Rate = props.flRate;
+    mAlProps = [&]{
+        VmorpherProps ret{};
+        ret.PhonemeA = get_phoneme(props.ulPhonemeA);
+        ret.PhonemeACoarseTuning = static_cast<int>(props.lPhonemeACoarseTuning);
+        ret.PhonemeB = get_phoneme(props.ulPhonemeB);
+        ret.PhonemeBCoarseTuning = static_cast<int>(props.lPhonemeBCoarseTuning);
+        ret.Waveform = get_waveform(props.ulWaveform);
+        ret.Rate = props.flRate;
+        return ret;
+    }();
 
     return true;
 }
-- 
cgit v1.2.3