4 steps to make an effect stateless

Let use use EffectEcho as an example.

"Define Settings" (reference commit: 2d02c26)

In header file:

In cpp file:

"Define validator" (reference commit: 2592061)

In header file:

  • you can remove forward declarations like: class wxCheckBox; class wxSlider; class wxSpinCtrl;

  • add line "struct Validator;" in public section

  • if the following is declared, remove it: bool TransferDataToWindow(const EffectSettings &settings) override;

In cpp file:



    struct EffectEcho::Validator
       : EffectUIValidator
    {
       Validator(EffectUIClientInterface& effect,
          EffectSettingsAccess& access, EffectEchoSettings& settings)
          : EffectUIValidator{ effect, access }
          , mSettings{ settings }
       {}
       virtual ~Validator() = default;

       Effect& GetEffect() const { return static_cast<Effect&>(mEffect); }

       bool ValidateUI() override;
       bool UpdateUI() override;

       void PopulateOrExchange(ShuttleGui& S);

       EffectEchoSettings& mSettings;
    };


    bool EffectEcho::Validator::ValidateUI()
    {
       mAccess.ModifySettings
       (
          [this](EffectSettings& settings)
       {
          // pass back the modified settings to the MessageBuffer

          // TODO uncomment at last step
          //EffectEcho::GetSettings(settings) = mSettings;
       }
       );

       return true;
    }
 
 bool EffectEcho::Validator::UpdateUI()
 {
    // get the settings from the MessageBuffer and write them to our local copy
    const auto& settings = mAccess.Get();

    // TODO uncomment at last step
    //mSettings = GetSettings(settings);

    return true;
 }
        std::unique_ptr<EffectUIValidator>
        EffectEcho::PopulateOrExchange(ShuttleGui& S, EffectSettingsAccess& access)
        {
           // ENABLE AT LAST STEP
           // auto& settings = access.Get();
           // auto& myEffSettings = GetSettings(settings);

           // DISABLE AT LAST STEP
           auto &myEffSettings = mSettings;

           auto& settings = access.Get();
           auto& myEffSettings = GetSettings(settings);
           auto result = std::make_unique<Validator>(*this, access, myEffSettings);
           result->PopulateOrExchange(S);
           return result;
        }

ADDITIONAL STEPS for an effect which has explicitly declared Sliders, Textboxes etc.

"Define the instance" (reference commit: 5961be1)

however, since that commit, the following has changed:

  • some methods of Instance have a different signature

  • MakeInstance now takes no args

  • the instance does not inherit EffectInstanceWithSampleRate anymore (?))


In header file:

     struct Instance;

     std::shared_ptr<EffectInstance> MakeInstance() const override;

In cpp file:

  struct EffectEcho::Instance
    : public PerTrackEffect::Instance
    , public EffectInstanceWithBlockSize           
 {
    explicit Instance(const PerTrackEffect& effect)
       : PerTrackEffect::Instance{ effect }
    {}

    bool ProcessInitialize(EffectSettings& settings,
       sampleCount totalLen, ChannelNames chanMap) override;

    size_t ProcessBlock(EffectSettings& settings,
       const float* const* inBlock, float* const* outBlock, size_t blockLen)  override;

    bool ProcessFinalize(void) override; // not every effect needs this
    
    /* you will need these for realtime-capable effects
    
    bool RealtimeInitialize(EffectSettings& settings) override;

    bool RealtimeAddProcessor(EffectSettings& settings,
       unsigned numChannels, float sampleRate) override;

    bool RealtimeFinalize(EffectSettings& settings) noexcept override;

    size_t RealtimeProcess(int group, EffectSettings& settings,
       const float* const* inbuf, float* const* outbuf, size_t numSamples)
       override;
    
    */

    // << MEMBERS WHICH REPRESENT STATE which you commented out earlier >>
 };


 std::shared_ptr<EffectInstance>
 EffectEcho::MakeInstance() const
 {
    return std::make_shared<Instance>(*this);
 }

"Real Statelessness" (reference commit: 231bf76)

In header file:

In cpp file:

Some notes by Paul Licameli:

Regarding step 2:

"Another possibility, is that I MAY put all the needed calls to GetSettings() in early, BUT I do not yet use EffectWithSettings<> and the definition of GetSettings() that it generates. Instead, I define GetSettings() to ignore its argument and return mSettings in the effect object. At the last step of real statelessness, I can delete the definition of GetSettings() but leave the calls."

Regarding step 4:

"There may be a fifth step: see the commit I recently pushed to the branch for ladspa effects.

MakeSettings and CopySettingsContents may require explicit definitions instead of those generated by the template EffectWithSettings.

That does not apply to built-in effects where the settings structure simply contains several scalar fields and no variable sized containers.

For Ladspa, you can see that MakeSettings() instead allocates a vector of values, whose size is not known at compilation time but depends on other information that the effect is queried for. So default construction of the settings, as in the generated MakeSettings(), will not do.

Then CopySettingsContents must also be overridden, not for correctness but for efficiency. The override is supposed to avoid memory allocations when the main thread calls it, so it should not use the copy constructors of the embedded std::vector but instead rewrite an existing vector without changing its capacity. The assumption will be made that the destination settings containers are already correctly sized, because MakeSettings did that, or else full copies done in the main thread (where allocations are allowed) already did that.

Other third party effect families like AudioUnits may have Settings structures containing maps from strings to values, instead of vectors, but the problems will be similar."

Last updated