|
|
#include "config.h"
|
|
|
|
#include "AL/al.h"
|
|
#include "AL/efx.h"
|
|
|
|
#include "alc/effects/base.h"
|
|
#include "effects.h"
|
|
|
|
#ifdef ALSOFT_EAX
|
|
#include "alnumeric.h"
|
|
|
|
#include "al/eax_exception.h"
|
|
#include "al/eax_utils.h"
|
|
#endif // ALSOFT_EAX
|
|
|
|
|
|
namespace {
|
|
|
|
void Distortion_setParami(EffectProps*, ALenum param, int)
|
|
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
|
|
void Distortion_setParamiv(EffectProps*, ALenum param, const int*)
|
|
{
|
|
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
|
|
param};
|
|
}
|
|
void Distortion_setParamf(EffectProps *props, ALenum param, float val)
|
|
{
|
|
switch(param)
|
|
{
|
|
case AL_DISTORTION_EDGE:
|
|
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
|
|
throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"};
|
|
props->Distortion.Edge = val;
|
|
break;
|
|
|
|
case AL_DISTORTION_GAIN:
|
|
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
|
|
throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"};
|
|
props->Distortion.Gain = val;
|
|
break;
|
|
|
|
case AL_DISTORTION_LOWPASS_CUTOFF:
|
|
if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
|
|
throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"};
|
|
props->Distortion.LowpassCutoff = val;
|
|
break;
|
|
|
|
case AL_DISTORTION_EQCENTER:
|
|
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
|
|
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"};
|
|
props->Distortion.EQCenter = val;
|
|
break;
|
|
|
|
case AL_DISTORTION_EQBANDWIDTH:
|
|
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
|
|
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"};
|
|
props->Distortion.EQBandwidth = val;
|
|
break;
|
|
|
|
default:
|
|
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
|
|
}
|
|
}
|
|
void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals)
|
|
{ Distortion_setParamf(props, param, vals[0]); }
|
|
|
|
void Distortion_getParami(const EffectProps*, ALenum param, int*)
|
|
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
|
|
void Distortion_getParamiv(const EffectProps*, ALenum param, int*)
|
|
{
|
|
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
|
|
param};
|
|
}
|
|
void Distortion_getParamf(const EffectProps *props, ALenum param, float *val)
|
|
{
|
|
switch(param)
|
|
{
|
|
case AL_DISTORTION_EDGE:
|
|
*val = props->Distortion.Edge;
|
|
break;
|
|
|
|
case AL_DISTORTION_GAIN:
|
|
*val = props->Distortion.Gain;
|
|
break;
|
|
|
|
case AL_DISTORTION_LOWPASS_CUTOFF:
|
|
*val = props->Distortion.LowpassCutoff;
|
|
break;
|
|
|
|
case AL_DISTORTION_EQCENTER:
|
|
*val = props->Distortion.EQCenter;
|
|
break;
|
|
|
|
case AL_DISTORTION_EQBANDWIDTH:
|
|
*val = props->Distortion.EQBandwidth;
|
|
break;
|
|
|
|
default:
|
|
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
|
|
}
|
|
}
|
|
void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals)
|
|
{ Distortion_getParamf(props, param, vals); }
|
|
|
|
EffectProps genDefaultProps() noexcept
|
|
{
|
|
EffectProps props{};
|
|
props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
|
|
props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
|
|
props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
|
|
props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
|
|
props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
|
|
return props;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
DEFINE_ALEFFECT_VTABLE(Distortion);
|
|
|
|
const EffectProps DistortionEffectProps{genDefaultProps()};
|
|
|
|
#ifdef ALSOFT_EAX
|
|
namespace {
|
|
|
|
using EaxDistortionEffectDirtyFlagsValue = std::uint_least8_t;
|
|
|
|
struct EaxDistortionEffectDirtyFlags
|
|
{
|
|
using EaxIsBitFieldStruct = bool;
|
|
|
|
EaxDistortionEffectDirtyFlagsValue flEdge : 1;
|
|
EaxDistortionEffectDirtyFlagsValue lGain : 1;
|
|
EaxDistortionEffectDirtyFlagsValue flLowPassCutOff : 1;
|
|
EaxDistortionEffectDirtyFlagsValue flEQCenter : 1;
|
|
EaxDistortionEffectDirtyFlagsValue flEQBandwidth : 1;
|
|
}; // EaxDistortionEffectDirtyFlags
|
|
|
|
|
|
class EaxDistortionEffect final :
|
|
public EaxEffect
|
|
{
|
|
public:
|
|
EaxDistortionEffect();
|
|
|
|
void dispatch(const EaxEaxCall& eax_call) override;
|
|
|
|
// [[nodiscard]]
|
|
bool apply_deferred() override;
|
|
|
|
private:
|
|
EAXDISTORTIONPROPERTIES eax_{};
|
|
EAXDISTORTIONPROPERTIES eax_d_{};
|
|
EaxDistortionEffectDirtyFlags eax_dirty_flags_{};
|
|
|
|
void set_eax_defaults();
|
|
|
|
void set_efx_edge();
|
|
void set_efx_gain();
|
|
void set_efx_lowpass_cutoff();
|
|
void set_efx_eq_center();
|
|
void set_efx_eq_bandwidth();
|
|
void set_efx_defaults();
|
|
|
|
void get(const EaxEaxCall& eax_call);
|
|
|
|
void validate_edge(float flEdge);
|
|
void validate_gain(long lGain);
|
|
void validate_lowpass_cutoff(float flLowPassCutOff);
|
|
void validate_eq_center(float flEQCenter);
|
|
void validate_eq_bandwidth(float flEQBandwidth);
|
|
void validate_all(const EAXDISTORTIONPROPERTIES& eax_all);
|
|
|
|
void defer_edge(float flEdge);
|
|
void defer_gain(long lGain);
|
|
void defer_low_pass_cutoff(float flLowPassCutOff);
|
|
void defer_eq_center(float flEQCenter);
|
|
void defer_eq_bandwidth(float flEQBandwidth);
|
|
void defer_all(const EAXDISTORTIONPROPERTIES& eax_all);
|
|
|
|
void defer_edge(const EaxEaxCall& eax_call);
|
|
void defer_gain(const EaxEaxCall& eax_call);
|
|
void defer_low_pass_cutoff(const EaxEaxCall& eax_call);
|
|
void defer_eq_center(const EaxEaxCall& eax_call);
|
|
void defer_eq_bandwidth(const EaxEaxCall& eax_call);
|
|
void defer_all(const EaxEaxCall& eax_call);
|
|
|
|
void set(const EaxEaxCall& eax_call);
|
|
}; // EaxDistortionEffect
|
|
|
|
|
|
class EaxDistortionEffectException :
|
|
public EaxException
|
|
{
|
|
public:
|
|
explicit EaxDistortionEffectException(
|
|
const char* message)
|
|
:
|
|
EaxException{"EAX_DISTORTION_EFFECT", message}
|
|
{
|
|
}
|
|
}; // EaxDistortionEffectException
|
|
|
|
|
|
EaxDistortionEffect::EaxDistortionEffect()
|
|
: EaxEffect{AL_EFFECT_DISTORTION}
|
|
{
|
|
set_eax_defaults();
|
|
set_efx_defaults();
|
|
}
|
|
|
|
void EaxDistortionEffect::dispatch(const EaxEaxCall& eax_call)
|
|
{
|
|
eax_call.is_get() ? get(eax_call) : set(eax_call);
|
|
}
|
|
|
|
void EaxDistortionEffect::set_eax_defaults()
|
|
{
|
|
eax_.flEdge = EAXDISTORTION_DEFAULTEDGE;
|
|
eax_.lGain = EAXDISTORTION_DEFAULTGAIN;
|
|
eax_.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
|
|
eax_.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
|
|
eax_.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
|
|
|
|
eax_d_ = eax_;
|
|
}
|
|
|
|
void EaxDistortionEffect::set_efx_edge()
|
|
{
|
|
const auto edge = clamp(
|
|
eax_.flEdge,
|
|
AL_DISTORTION_MIN_EDGE,
|
|
AL_DISTORTION_MAX_EDGE);
|
|
|
|
al_effect_props_.Distortion.Edge = edge;
|
|
}
|
|
|
|
void EaxDistortionEffect::set_efx_gain()
|
|
{
|
|
const auto gain = clamp(
|
|
level_mb_to_gain(static_cast<float>(eax_.lGain)),
|
|
AL_DISTORTION_MIN_GAIN,
|
|
AL_DISTORTION_MAX_GAIN);
|
|
|
|
al_effect_props_.Distortion.Gain = gain;
|
|
}
|
|
|
|
void EaxDistortionEffect::set_efx_lowpass_cutoff()
|
|
{
|
|
const auto lowpass_cutoff = clamp(
|
|
eax_.flLowPassCutOff,
|
|
AL_DISTORTION_MIN_LOWPASS_CUTOFF,
|
|
AL_DISTORTION_MAX_LOWPASS_CUTOFF);
|
|
|
|
al_effect_props_.Distortion.LowpassCutoff = lowpass_cutoff;
|
|
}
|
|
|
|
void EaxDistortionEffect::set_efx_eq_center()
|
|
{
|
|
const auto eq_center = clamp(
|
|
eax_.flEQCenter,
|
|
AL_DISTORTION_MIN_EQCENTER,
|
|
AL_DISTORTION_MAX_EQCENTER);
|
|
|
|
al_effect_props_.Distortion.EQCenter = eq_center;
|
|
}
|
|
|
|
void EaxDistortionEffect::set_efx_eq_bandwidth()
|
|
{
|
|
const auto eq_bandwidth = clamp(
|
|
eax_.flEdge,
|
|
AL_DISTORTION_MIN_EQBANDWIDTH,
|
|
AL_DISTORTION_MAX_EQBANDWIDTH);
|
|
|
|
al_effect_props_.Distortion.EQBandwidth = eq_bandwidth;
|
|
}
|
|
|
|
void EaxDistortionEffect::set_efx_defaults()
|
|
{
|
|
set_efx_edge();
|
|
set_efx_gain();
|
|
set_efx_lowpass_cutoff();
|
|
set_efx_eq_center();
|
|
set_efx_eq_bandwidth();
|
|
}
|
|
|
|
void EaxDistortionEffect::get(const EaxEaxCall& eax_call)
|
|
{
|
|
switch(eax_call.get_property_id())
|
|
{
|
|
case EAXDISTORTION_NONE:
|
|
break;
|
|
|
|
case EAXDISTORTION_ALLPARAMETERS:
|
|
eax_call.set_value<EaxDistortionEffectException>(eax_);
|
|
break;
|
|
|
|
case EAXDISTORTION_EDGE:
|
|
eax_call.set_value<EaxDistortionEffectException>(eax_.flEdge);
|
|
break;
|
|
|
|
case EAXDISTORTION_GAIN:
|
|
eax_call.set_value<EaxDistortionEffectException>(eax_.lGain);
|
|
break;
|
|
|
|
case EAXDISTORTION_LOWPASSCUTOFF:
|
|
eax_call.set_value<EaxDistortionEffectException>(eax_.flLowPassCutOff);
|
|
break;
|
|
|
|
case EAXDISTORTION_EQCENTER:
|
|
eax_call.set_value<EaxDistortionEffectException>(eax_.flEQCenter);
|
|
break;
|
|
|
|
case EAXDISTORTION_EQBANDWIDTH:
|
|
eax_call.set_value<EaxDistortionEffectException>(eax_.flEQBandwidth);
|
|
break;
|
|
|
|
default:
|
|
throw EaxDistortionEffectException{"Unsupported property id."};
|
|
}
|
|
}
|
|
|
|
void EaxDistortionEffect::validate_edge(
|
|
float flEdge)
|
|
{
|
|
eax_validate_range<EaxDistortionEffectException>(
|
|
"Edge",
|
|
flEdge,
|
|
EAXDISTORTION_MINEDGE,
|
|
EAXDISTORTION_MAXEDGE);
|
|
}
|
|
|
|
void EaxDistortionEffect::validate_gain(
|
|
long lGain)
|
|
{
|
|
eax_validate_range<EaxDistortionEffectException>(
|
|
"Gain",
|
|
lGain,
|
|
EAXDISTORTION_MINGAIN,
|
|
EAXDISTORTION_MAXGAIN);
|
|
}
|
|
|
|
void EaxDistortionEffect::validate_lowpass_cutoff(
|
|
float flLowPassCutOff)
|
|
{
|
|
eax_validate_range<EaxDistortionEffectException>(
|
|
"Low-pass Cut-off",
|
|
flLowPassCutOff,
|
|
EAXDISTORTION_MINLOWPASSCUTOFF,
|
|
EAXDISTORTION_MAXLOWPASSCUTOFF);
|
|
}
|
|
|
|
void EaxDistortionEffect::validate_eq_center(
|
|
float flEQCenter)
|
|
{
|
|
eax_validate_range<EaxDistortionEffectException>(
|
|
"EQ Center",
|
|
flEQCenter,
|
|
EAXDISTORTION_MINEQCENTER,
|
|
EAXDISTORTION_MAXEQCENTER);
|
|
}
|
|
|
|
void EaxDistortionEffect::validate_eq_bandwidth(
|
|
float flEQBandwidth)
|
|
{
|
|
eax_validate_range<EaxDistortionEffectException>(
|
|
"EQ Bandwidth",
|
|
flEQBandwidth,
|
|
EAXDISTORTION_MINEQBANDWIDTH,
|
|
EAXDISTORTION_MAXEQBANDWIDTH);
|
|
}
|
|
|
|
void EaxDistortionEffect::validate_all(
|
|
const EAXDISTORTIONPROPERTIES& eax_all)
|
|
{
|
|
validate_edge(eax_all.flEdge);
|
|
validate_gain(eax_all.lGain);
|
|
validate_lowpass_cutoff(eax_all.flLowPassCutOff);
|
|
validate_eq_center(eax_all.flEQCenter);
|
|
validate_eq_bandwidth(eax_all.flEQBandwidth);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_edge(
|
|
float flEdge)
|
|
{
|
|
eax_d_.flEdge = flEdge;
|
|
eax_dirty_flags_.flEdge = (eax_.flEdge != eax_d_.flEdge);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_gain(
|
|
long lGain)
|
|
{
|
|
eax_d_.lGain = lGain;
|
|
eax_dirty_flags_.lGain = (eax_.lGain != eax_d_.lGain);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_low_pass_cutoff(
|
|
float flLowPassCutOff)
|
|
{
|
|
eax_d_.flLowPassCutOff = flLowPassCutOff;
|
|
eax_dirty_flags_.flLowPassCutOff = (eax_.flLowPassCutOff != eax_d_.flLowPassCutOff);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_eq_center(
|
|
float flEQCenter)
|
|
{
|
|
eax_d_.flEQCenter = flEQCenter;
|
|
eax_dirty_flags_.flEQCenter = (eax_.flEQCenter != eax_d_.flEQCenter);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_eq_bandwidth(
|
|
float flEQBandwidth)
|
|
{
|
|
eax_d_.flEQBandwidth = flEQBandwidth;
|
|
eax_dirty_flags_.flEQBandwidth = (eax_.flEQBandwidth != eax_d_.flEQBandwidth);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_all(
|
|
const EAXDISTORTIONPROPERTIES& eax_all)
|
|
{
|
|
defer_edge(eax_all.flEdge);
|
|
defer_gain(eax_all.lGain);
|
|
defer_low_pass_cutoff(eax_all.flLowPassCutOff);
|
|
defer_eq_center(eax_all.flEQCenter);
|
|
defer_eq_bandwidth(eax_all.flEQBandwidth);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_edge(
|
|
const EaxEaxCall& eax_call)
|
|
{
|
|
const auto& edge =
|
|
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEdge)>();
|
|
|
|
validate_edge(edge);
|
|
defer_edge(edge);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_gain(
|
|
const EaxEaxCall& eax_call)
|
|
{
|
|
const auto& gain =
|
|
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::lGain)>();
|
|
|
|
validate_gain(gain);
|
|
defer_gain(gain);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_low_pass_cutoff(
|
|
const EaxEaxCall& eax_call)
|
|
{
|
|
const auto& lowpass_cutoff =
|
|
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flLowPassCutOff)>();
|
|
|
|
validate_lowpass_cutoff(lowpass_cutoff);
|
|
defer_low_pass_cutoff(lowpass_cutoff);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_eq_center(
|
|
const EaxEaxCall& eax_call)
|
|
{
|
|
const auto& eq_center =
|
|
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQCenter)>();
|
|
|
|
validate_eq_center(eq_center);
|
|
defer_eq_center(eq_center);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_eq_bandwidth(
|
|
const EaxEaxCall& eax_call)
|
|
{
|
|
const auto& eq_bandwidth =
|
|
eax_call.get_value<EaxDistortionEffectException, const decltype(EAXDISTORTIONPROPERTIES::flEQBandwidth)>();
|
|
|
|
validate_eq_bandwidth(eq_bandwidth);
|
|
defer_eq_bandwidth(eq_bandwidth);
|
|
}
|
|
|
|
void EaxDistortionEffect::defer_all(
|
|
const EaxEaxCall& eax_call)
|
|
{
|
|
const auto& all =
|
|
eax_call.get_value<EaxDistortionEffectException, const EAXDISTORTIONPROPERTIES>();
|
|
|
|
validate_all(all);
|
|
defer_all(all);
|
|
}
|
|
|
|
// [[nodiscard]]
|
|
bool EaxDistortionEffect::apply_deferred()
|
|
{
|
|
if (eax_dirty_flags_ == EaxDistortionEffectDirtyFlags{})
|
|
{
|
|
return false;
|
|
}
|
|
|
|
eax_ = eax_d_;
|
|
|
|
if (eax_dirty_flags_.flEdge)
|
|
{
|
|
set_efx_edge();
|
|
}
|
|
|
|
if (eax_dirty_flags_.lGain)
|
|
{
|
|
set_efx_gain();
|
|
}
|
|
|
|
if (eax_dirty_flags_.flLowPassCutOff)
|
|
{
|
|
set_efx_lowpass_cutoff();
|
|
}
|
|
|
|
if (eax_dirty_flags_.flEQCenter)
|
|
{
|
|
set_efx_eq_center();
|
|
}
|
|
|
|
if (eax_dirty_flags_.flEQBandwidth)
|
|
{
|
|
set_efx_eq_bandwidth();
|
|
}
|
|
|
|
eax_dirty_flags_ = EaxDistortionEffectDirtyFlags{};
|
|
|
|
return true;
|
|
}
|
|
|
|
void EaxDistortionEffect::set(const EaxEaxCall& eax_call)
|
|
{
|
|
switch(eax_call.get_property_id())
|
|
{
|
|
case EAXDISTORTION_NONE:
|
|
break;
|
|
|
|
case EAXDISTORTION_ALLPARAMETERS:
|
|
defer_all(eax_call);
|
|
break;
|
|
|
|
case EAXDISTORTION_EDGE:
|
|
defer_edge(eax_call);
|
|
break;
|
|
|
|
case EAXDISTORTION_GAIN:
|
|
defer_gain(eax_call);
|
|
break;
|
|
|
|
case EAXDISTORTION_LOWPASSCUTOFF:
|
|
defer_low_pass_cutoff(eax_call);
|
|
break;
|
|
|
|
case EAXDISTORTION_EQCENTER:
|
|
defer_eq_center(eax_call);
|
|
break;
|
|
|
|
case EAXDISTORTION_EQBANDWIDTH:
|
|
defer_eq_bandwidth(eax_call);
|
|
break;
|
|
|
|
default:
|
|
throw EaxDistortionEffectException{"Unsupported property id."};
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
EaxEffectUPtr eax_create_eax_distortion_effect()
|
|
{
|
|
return std::make_unique<EaxDistortionEffect>();
|
|
}
|
|
|
|
#endif // ALSOFT_EAX
|