Audio effects¶
1. Generalities¶
The audio effects are effects that are applied to the sound between the speaker devices and the actual sound output. In the current implementation the effect chain is fixed (but not the effect parameters), and the parameters are stored in the cfg files. They are honestly not really designed for extensibility yet, if ever.
Adding an effect requires working on four parts:
audio_effects/aeffects.* for effect object creation and "publishing"
audio_effects/youreffect.* for the effect implementation
frontend/mame/ui/audioeffects.cpp to be able to instantiate the effect configuration menu
frontend/mame/ui/audioyoureffect.* to implement the effect configuration menu
2. audio_effects/aeffects.*¶
The audio_effect class in the aeffect sources provides three things:
an enum value to designate the effect type and which must match its position in the chain (iow, the effect chain follows the enum order), in the .h
the effect name in the audio_effect::effect_names array in the .cpp
the creation of a correct effect object in audio_effect::create in the .cpp
3. audio_effects/youreffect.*¶
This is where you implement the effect. It takes the shape of an audio_effect_youreffect class which derives from audio_effect.
The methods to implement are:
audio_effect_youreffect(u32 sample_rate, audio_effect *def);
virtual int type() const override;
virtual void config_load(util::xml::data_node const *ef_node) override;
virtual void config_save(util::xml::data_node *ef_node) const override;
virtual void default_changed() override;
virtual u32 history_size() const; // optional
The constructor must pass the parameters to audio_effect
and
initialize the effect parameters. type
must return the enum value
for the effect. config_load
and config_save
should load or
save the effect parameters from/to the cfg file xml tree.
default_changed
is called when the parameters in m_default
are
changed and the parameters may need to be updated. history_size
allows to tell how many samples should still be available of the
previous input frame. Note that this number must not depend on the
parameters and only on the sample rate.
An effect has a number of parameters that can come from three sources:
fixed default value
equivalent effect object from the default effect chain
user setting through the UI
The first two are recognized through the value of m_default
which
gets the value of def
in the constructor. When it's nullptr, the
value to use when not set by the user is the fixed one, otherwise it's
the one in m_default
.
At minimum an effect should have a parameter allowing to bypass it.
Managing a parameter uses four methods:
type param() const;
returns the current parameter valuevoid set_param(type value);
sets the current parameter value and marks it as set by the userbool isset_param() const;
returns true is the parameter was set by the uservoid reset_param();
resets the parameter to the default value (from m_default or fixed) and marks it as not set by the user
config_save
must only save the user-set parameters.
config_load
must retrieve the parameters that are present and mark
them as set by the user and reset all the others.
Finally the actual implementation goes into the apply
method:
virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override;
That method takes two buffers with the same number of channels and has
to apply the effect to src
to produce dest
. The
output_buffer_flat
is non-interleaved with independant per-channel
buffers.
To make bypassing easier, the copy(src, dest)
method of
audio_effect allows to copy the samples from src
to dest
without changing them.
The effect application part should looks like:
u32 samples = src.available_samples();
dest.prepare_space(samples);
u32 channels = src.channels();
// generate channels * samples results and put them in dest
dest.commit(samples);
To get pointers to the buffers, one uses:
const sample_t *source = src.ptrs(channel, source_index); // source_index must be in [-history_size()..samples-1]
sample_t *destination = dest.ptrw(channel, destination_index); // destination_index must be in [0..samples-1]
The samples pointed by source and destination are contiguous. The number of channels will not change from one apply call to another, the number of samples will vary though. Also the call happens in a different thread than the main thread and also in a different thread than the parameter setting calls are made from.
4. frontend/mame/ui/audioeffects.cpp¶
Here it suffices to add a creation of the menu menu_audio_effect_youreffect in menu_audio_effects::handle. The menu effect will pick the effect names from audio_effect (in aeffect.*).
5. frontend/mame/ui/audioyoureffect.*¶
This is used to implement the configuration menu for the effect. It's a little complicated because it needs to generate the list of parameters and their values, set left or right arrow flags depending on the possible modifications, dim them (FLAG_INVERT) when not set by the user, and manage the arrows and clear keys to change them. Just copy an existing one and change it as needed.