From 19ed994dc30ed84ea7cbbb5152577669fc25caf6 Mon Sep 17 00:00:00 2001
From: "Boris I. Bendovsky" <bibendovsky@hotmail.com>
Date: Sun, 30 Jan 2022 14:47:32 +0200
Subject: Add EAX extensions (EAX 2.0-5.0, X-RAM) (#632)

* Add EAX extensions (EAX 2.0-5.0, X-RAM)

* Comment out C++17 leftovers

* Remove everything related to patching

* Update alsoftrc.sample

* Rewrite integration

* Fix GCC compilation under Linux

* Always reset EAX effect properties when loading it into FX slot
---
 al/effects/vmorpher.cpp | 624 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 624 insertions(+)

(limited to 'al/effects/vmorpher.cpp')

diff --git a/al/effects/vmorpher.cpp b/al/effects/vmorpher.cpp
index 1b4710ff..2a9e0702 100644
--- a/al/effects/vmorpher.cpp
+++ b/al/effects/vmorpher.cpp
@@ -10,6 +10,15 @@
 #include "aloptional.h"
 #include "effects.h"
 
+#if ALSOFT_EAX
+#include <cassert>
+
+#include "alnumeric.h"
+
+#include "al/eax_exception.h"
+#include "al/eax_utils.h"
+#endif // ALSOFT_EAX
+
 
 namespace {
 
@@ -247,3 +256,618 @@ EffectProps genDefaultProps() noexcept
 DEFINE_ALEFFECT_VTABLE(Vmorpher);
 
 const EffectProps VmorpherEffectProps{genDefaultProps()};
+
+#if ALSOFT_EAX
+namespace
+{
+
+
+using EaxVocalMorpherEffectDirtyFlagsValue = std::uint_least8_t;
+
+struct EaxVocalMorpherEffectDirtyFlags
+{
+    using EaxIsBitFieldStruct = bool;
+
+    EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeA : 1;
+    EaxVocalMorpherEffectDirtyFlagsValue lPhonemeACoarseTuning : 1;
+    EaxVocalMorpherEffectDirtyFlagsValue ulPhonemeB : 1;
+    EaxVocalMorpherEffectDirtyFlagsValue lPhonemeBCoarseTuning : 1;
+    EaxVocalMorpherEffectDirtyFlagsValue ulWaveform : 1;
+    EaxVocalMorpherEffectDirtyFlagsValue flRate : 1;
+}; // EaxPitchShifterEffectDirtyFlags
+
+
+class EaxVocalMorpherEffect final :
+    public EaxEffect
+{
+public:
+    EaxVocalMorpherEffect(
+        EffectProps& al_effect_props);
+
+
+    // [[nodiscard]]
+    bool dispatch(
+        const EaxEaxCall& eax_call) override;
+
+
+private:
+    EffectProps& al_effect_props_;
+
+    EAXVOCALMORPHERPROPERTIES eax_{};
+    EAXVOCALMORPHERPROPERTIES eax_d_{};
+    EaxVocalMorpherEffectDirtyFlags eax_dirty_flags_{};
+
+
+    void set_eax_defaults();
+
+
+    void set_efx_phoneme_a();
+
+    void set_efx_phoneme_a_coarse_tuning();
+
+    void set_efx_phoneme_b();
+
+    void set_efx_phoneme_b_coarse_tuning();
+
+    void set_efx_waveform();
+
+    void set_efx_rate();
+
+    void set_efx_defaults();
+
+
+    // [[nodiscard]]
+    bool get(
+        const EaxEaxCall& eax_call);
+
+
+    void validate_phoneme_a(
+        unsigned long ulPhonemeA);
+
+    void validate_phoneme_a_coarse_tuning(
+        long lPhonemeACoarseTuning);
+
+    void validate_phoneme_b(
+        unsigned long ulPhonemeB);
+
+    void validate_phoneme_b_coarse_tuning(
+        long lPhonemeBCoarseTuning);
+
+    void validate_waveform(
+        unsigned long ulWaveform);
+
+    void validate_rate(
+        float flRate);
+
+    void validate_all(
+        const EAXVOCALMORPHERPROPERTIES& all);
+
+
+    void defer_phoneme_a(
+        unsigned long ulPhonemeA);
+
+    void defer_phoneme_a_coarse_tuning(
+        long lPhonemeACoarseTuning);
+
+    void defer_phoneme_b(
+        unsigned long ulPhonemeB);
+
+    void defer_phoneme_b_coarse_tuning(
+        long lPhonemeBCoarseTuning);
+
+    void defer_waveform(
+        unsigned long ulWaveform);
+
+    void defer_rate(
+        float flRate);
+
+    void defer_all(
+        const EAXVOCALMORPHERPROPERTIES& all);
+
+
+    void defer_phoneme_a(
+        const EaxEaxCall& eax_call);
+
+    void defer_phoneme_a_coarse_tuning(
+        const EaxEaxCall& eax_call);
+
+    void defer_phoneme_b(
+        const EaxEaxCall& eax_call);
+
+    void defer_phoneme_b_coarse_tuning(
+        const EaxEaxCall& eax_call);
+
+    void defer_waveform(
+        const EaxEaxCall& eax_call);
+
+    void defer_rate(
+        const EaxEaxCall& eax_call);
+
+    void defer_all(
+        const EaxEaxCall& eax_call);
+
+
+    // [[nodiscard]]
+    bool apply_deferred();
+
+    // [[nodiscard]]
+    bool set(
+        const EaxEaxCall& eax_call);
+}; // EaxVocalMorpherEffect
+
+
+class EaxVocalMorpherEffectException :
+    public EaxException
+{
+public:
+    explicit EaxVocalMorpherEffectException(
+        const char* message)
+        :
+        EaxException{"EAX_VOCAL_MORPHER_EFFECT", message}
+    {
+    }
+}; // EaxVocalMorpherEffectException
+
+
+EaxVocalMorpherEffect::EaxVocalMorpherEffect(
+    EffectProps& al_effect_props)
+    :
+    al_effect_props_{al_effect_props}
+{
+    set_eax_defaults();
+    set_efx_defaults();
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::dispatch(
+    const EaxEaxCall& eax_call)
+{
+    return eax_call.is_get() ? get(eax_call) : set(eax_call);
+}
+
+void EaxVocalMorpherEffect::set_eax_defaults()
+{
+    eax_.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
+    eax_.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
+    eax_.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
+    eax_.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
+    eax_.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
+    eax_.flRate = EAXVOCALMORPHER_DEFAULTRATE;
+
+    eax_d_ = eax_;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_a()
+{
+    const auto phoneme_a = clamp(
+        static_cast<ALint>(eax_.ulPhonemeA),
+        AL_VOCAL_MORPHER_MIN_PHONEMEA,
+        AL_VOCAL_MORPHER_MAX_PHONEMEA);
+
+    const auto efx_phoneme_a = PhenomeFromEnum(phoneme_a);
+    assert(efx_phoneme_a.has_value());
+    al_effect_props_.Vmorpher.PhonemeA = *efx_phoneme_a;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_a_coarse_tuning()
+{
+    const auto phoneme_a_coarse_tuning = clamp(
+        static_cast<ALint>(eax_.lPhonemeACoarseTuning),
+        AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING,
+        AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING);
+
+    al_effect_props_.Vmorpher.PhonemeACoarseTuning = phoneme_a_coarse_tuning;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_b()
+{
+    const auto phoneme_b = clamp(
+        static_cast<ALint>(eax_.ulPhonemeB),
+        AL_VOCAL_MORPHER_MIN_PHONEMEB,
+        AL_VOCAL_MORPHER_MAX_PHONEMEB);
+
+    const auto efx_phoneme_b = PhenomeFromEnum(phoneme_b);
+    assert(efx_phoneme_b.has_value());
+    al_effect_props_.Vmorpher.PhonemeB = *efx_phoneme_b;
+}
+
+void EaxVocalMorpherEffect::set_efx_phoneme_b_coarse_tuning()
+{
+    const auto phoneme_b_coarse_tuning = clamp(
+        static_cast<ALint>(eax_.lPhonemeBCoarseTuning),
+        AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING,
+        AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING);
+
+    al_effect_props_.Vmorpher.PhonemeBCoarseTuning = phoneme_b_coarse_tuning;
+}
+
+void EaxVocalMorpherEffect::set_efx_waveform()
+{
+    const auto waveform = clamp(
+        static_cast<ALint>(eax_.ulWaveform),
+        AL_VOCAL_MORPHER_MIN_WAVEFORM,
+        AL_VOCAL_MORPHER_MAX_WAVEFORM);
+
+    const auto wfx_waveform = WaveformFromEmum(waveform);
+    assert(wfx_waveform.has_value());
+    al_effect_props_.Vmorpher.Waveform = *wfx_waveform;
+}
+
+void EaxVocalMorpherEffect::set_efx_rate()
+{
+    const auto rate = clamp(
+        eax_.flRate,
+        AL_VOCAL_MORPHER_MIN_RATE,
+        AL_VOCAL_MORPHER_MAX_RATE);
+
+    al_effect_props_.Vmorpher.Rate = rate;
+}
+
+void EaxVocalMorpherEffect::set_efx_defaults()
+{
+    set_efx_phoneme_a();
+    set_efx_phoneme_a_coarse_tuning();
+    set_efx_phoneme_b();
+    set_efx_phoneme_b_coarse_tuning();
+    set_efx_waveform();
+    set_efx_rate();
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::get(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_property_id())
+    {
+        case EAXVOCALMORPHER_NONE:
+            break;
+
+        case EAXVOCALMORPHER_ALLPARAMETERS:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEA:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeA);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeACoarseTuning);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEB:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulPhonemeB);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.lPhonemeBCoarseTuning);
+            break;
+
+        case EAXVOCALMORPHER_WAVEFORM:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.ulWaveform);
+            break;
+
+        case EAXVOCALMORPHER_RATE:
+            eax_call.set_value<EaxVocalMorpherEffectException>(eax_.flRate);
+            break;
+
+        default:
+            throw EaxVocalMorpherEffectException{"Unsupported property id."};
+    }
+
+    return false;
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_a(
+    unsigned long ulPhonemeA)
+{
+    eax_validate_range<EaxVocalMorpherEffectException>(
+        "Phoneme A",
+        ulPhonemeA,
+        EAXVOCALMORPHER_MINPHONEMEA,
+        EAXVOCALMORPHER_MAXPHONEMEA);
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_a_coarse_tuning(
+    long lPhonemeACoarseTuning)
+{
+    eax_validate_range<EaxVocalMorpherEffectException>(
+        "Phoneme A Coarse Tuning",
+        lPhonemeACoarseTuning,
+        EAXVOCALMORPHER_MINPHONEMEACOARSETUNING,
+        EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING);
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_b(
+    unsigned long ulPhonemeB)
+{
+    eax_validate_range<EaxVocalMorpherEffectException>(
+        "Phoneme B",
+        ulPhonemeB,
+        EAXVOCALMORPHER_MINPHONEMEB,
+        EAXVOCALMORPHER_MAXPHONEMEB);
+}
+
+void EaxVocalMorpherEffect::validate_phoneme_b_coarse_tuning(
+    long lPhonemeBCoarseTuning)
+{
+    eax_validate_range<EaxVocalMorpherEffectException>(
+        "Phoneme B Coarse Tuning",
+        lPhonemeBCoarseTuning,
+        EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING,
+        EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING);
+}
+
+void EaxVocalMorpherEffect::validate_waveform(
+    unsigned long ulWaveform)
+{
+    eax_validate_range<EaxVocalMorpherEffectException>(
+        "Waveform",
+        ulWaveform,
+        EAXVOCALMORPHER_MINWAVEFORM,
+        EAXVOCALMORPHER_MAXWAVEFORM);
+}
+
+void EaxVocalMorpherEffect::validate_rate(
+    float flRate)
+{
+    eax_validate_range<EaxVocalMorpherEffectException>(
+        "Rate",
+        flRate,
+        EAXVOCALMORPHER_MINRATE,
+        EAXVOCALMORPHER_MAXRATE);
+}
+
+void EaxVocalMorpherEffect::validate_all(
+    const EAXVOCALMORPHERPROPERTIES& all)
+{
+    validate_phoneme_a(all.ulPhonemeA);
+    validate_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
+    validate_phoneme_b(all.ulPhonemeB);
+    validate_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
+    validate_waveform(all.ulWaveform);
+    validate_rate(all.flRate);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a(
+    unsigned long ulPhonemeA)
+{
+    eax_d_.ulPhonemeA = ulPhonemeA;
+    eax_dirty_flags_.ulPhonemeA = (eax_.ulPhonemeA != eax_d_.ulPhonemeA);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
+    long lPhonemeACoarseTuning)
+{
+    eax_d_.lPhonemeACoarseTuning = lPhonemeACoarseTuning;
+    eax_dirty_flags_.lPhonemeACoarseTuning = (eax_.lPhonemeACoarseTuning != eax_d_.lPhonemeACoarseTuning);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b(
+    unsigned long ulPhonemeB)
+{
+    eax_d_.ulPhonemeB = ulPhonemeB;
+    eax_dirty_flags_.ulPhonemeB = (eax_.ulPhonemeB != eax_d_.ulPhonemeB);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
+    long lPhonemeBCoarseTuning)
+{
+    eax_d_.lPhonemeBCoarseTuning = lPhonemeBCoarseTuning;
+    eax_dirty_flags_.lPhonemeBCoarseTuning = (eax_.lPhonemeBCoarseTuning != eax_d_.lPhonemeBCoarseTuning);
+}
+
+void EaxVocalMorpherEffect::defer_waveform(
+    unsigned long ulWaveform)
+{
+    eax_d_.ulWaveform = ulWaveform;
+    eax_dirty_flags_.ulWaveform = (eax_.ulWaveform != eax_d_.ulWaveform);
+}
+
+void EaxVocalMorpherEffect::defer_rate(
+    float flRate)
+{
+    eax_d_.flRate = flRate;
+    eax_dirty_flags_.flRate = (eax_.flRate != eax_d_.flRate);
+}
+
+void EaxVocalMorpherEffect::defer_all(
+    const EAXVOCALMORPHERPROPERTIES& all)
+{
+    defer_phoneme_a(all.ulPhonemeA);
+    defer_phoneme_a_coarse_tuning(all.lPhonemeACoarseTuning);
+    defer_phoneme_b(all.ulPhonemeB);
+    defer_phoneme_b_coarse_tuning(all.lPhonemeBCoarseTuning);
+    defer_waveform(all.ulWaveform);
+    defer_rate(all.flRate);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a(
+    const EaxEaxCall& eax_call)
+{
+    const auto& phoneme_a = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeA)
+    >();
+
+    validate_phoneme_a(phoneme_a);
+    defer_phoneme_a(phoneme_a);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_a_coarse_tuning(
+    const EaxEaxCall& eax_call)
+{
+    const auto& phoneme_a_coarse_tuning = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeACoarseTuning)
+    >();
+
+    validate_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
+    defer_phoneme_a_coarse_tuning(phoneme_a_coarse_tuning);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b(
+    const EaxEaxCall& eax_call)
+{
+    const auto& phoneme_b = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const decltype(EAXVOCALMORPHERPROPERTIES::ulPhonemeB)
+    >();
+
+    validate_phoneme_b(phoneme_b);
+    defer_phoneme_b(phoneme_b);
+}
+
+void EaxVocalMorpherEffect::defer_phoneme_b_coarse_tuning(
+    const EaxEaxCall& eax_call)
+{
+    const auto& phoneme_b_coarse_tuning = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const decltype(EAXVOCALMORPHERPROPERTIES::lPhonemeBCoarseTuning)
+    >();
+
+    validate_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
+    defer_phoneme_b_coarse_tuning(phoneme_b_coarse_tuning);
+}
+
+void EaxVocalMorpherEffect::defer_waveform(
+    const EaxEaxCall& eax_call)
+{
+    const auto& waveform = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const decltype(EAXVOCALMORPHERPROPERTIES::ulWaveform)
+    >();
+
+    validate_waveform(waveform);
+    defer_waveform(waveform);
+}
+
+void EaxVocalMorpherEffect::defer_rate(
+    const EaxEaxCall& eax_call)
+{
+    const auto& rate = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const decltype(EAXVOCALMORPHERPROPERTIES::flRate)
+    >();
+
+    validate_rate(rate);
+    defer_rate(rate);
+}
+
+void EaxVocalMorpherEffect::defer_all(
+    const EaxEaxCall& eax_call)
+{
+    const auto& all = eax_call.get_value<
+        EaxVocalMorpherEffectException,
+        const EAXVOCALMORPHERPROPERTIES
+    >();
+
+    validate_all(all);
+    defer_all(all);
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::apply_deferred()
+{
+    if (eax_dirty_flags_ == EaxVocalMorpherEffectDirtyFlags{})
+    {
+        return false;
+    }
+
+    eax_ = eax_d_;
+
+    if (eax_dirty_flags_.ulPhonemeA)
+    {
+        set_efx_phoneme_a();
+    }
+
+    if (eax_dirty_flags_.lPhonemeACoarseTuning)
+    {
+        set_efx_phoneme_a_coarse_tuning();
+    }
+
+    if (eax_dirty_flags_.ulPhonemeB)
+    {
+        set_efx_phoneme_b();
+    }
+
+    if (eax_dirty_flags_.lPhonemeBCoarseTuning)
+    {
+        set_efx_phoneme_b_coarse_tuning();
+    }
+
+    if (eax_dirty_flags_.ulWaveform)
+    {
+        set_efx_waveform();
+    }
+
+    if (eax_dirty_flags_.flRate)
+    {
+        set_efx_rate();
+    }
+
+    eax_dirty_flags_ = EaxVocalMorpherEffectDirtyFlags{};
+
+    return true;
+}
+
+// [[nodiscard]]
+bool EaxVocalMorpherEffect::set(
+    const EaxEaxCall& eax_call)
+{
+    switch (eax_call.get_property_id())
+    {
+        case EAXVOCALMORPHER_NONE:
+            break;
+
+        case EAXVOCALMORPHER_ALLPARAMETERS:
+            defer_all(eax_call);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEA:
+            defer_phoneme_a(eax_call);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
+            defer_phoneme_a_coarse_tuning(eax_call);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEB:
+            defer_phoneme_b(eax_call);
+            break;
+
+        case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
+            defer_phoneme_b_coarse_tuning(eax_call);
+            break;
+
+        case EAXVOCALMORPHER_WAVEFORM:
+            defer_waveform(eax_call);
+            break;
+
+        case EAXVOCALMORPHER_RATE:
+            defer_rate(eax_call);
+            break;
+
+        default:
+            throw EaxVocalMorpherEffectException{"Unsupported property id."};
+    }
+
+    if (!eax_call.is_deferred())
+    {
+        return apply_deferred();
+    }
+
+    return false;
+}
+
+
+} // namespace
+
+
+EaxEffectUPtr eax_create_eax_vocal_morpher_effect(
+    EffectProps& al_effect_props)
+{
+    return std::make_unique<EaxVocalMorpherEffect>(al_effect_props);
+}
+
+
+#endif // ALSOFT_EAX
-- 
cgit v1.2.3