Effects: bus processing and sample capture
Processing order, filtering and gain, Capture and Record; choose an effect for the task.
Where an effect acts
An effect processes its bus's combined signal, not just the most recently started player. The chain runs top to bottom. The bus then applies its volume/mute and sends the result onward. A compressor before an amplifier produces a different result than after it; choose order deliberately.
An AudioEffect resource holds parameters; an AudioEffectInstance performs processing. Reusing one resource in several places shares parameter changes. Create separate resources for independent settings. A heavy chain on every individual bus costs more than shared category processing.
Test a chain without an external file
Keep DemoTone.cs in the project. Attach EffectsDemo.cs below to a separate level's Control root, build and run with F6. Play / stop enables a 3000 Hz tone. Disabling the 500 Hz filter makes it substantially stronger; the -6 dB toggle changes gain by approximately half in amplitude. This checks routing, not comfortable loudspeaker volume.
EffectsDemo.cs
using System;
using Reckfell;
public partial class EffectsDemo : Control
{
public AudioStreamPlayer Voice { get; private set; } = null!;
public AudioEffectAmplify Gain { get; private set; } = null!;
public AudioEffectLowPassFilter Filter { get; private set; } = null!;
public AudioEffectCapture Capture { get; private set; } = null!;
public int BusIndex => AudioServer.GetBusIndex(_bus);
private string _bus = "";
public override void _Ready()
{
bool ru = TranslationServer.GetLocale().StartsWith("ru", StringComparison.OrdinalIgnoreCase);
_bus = "EffectsDemo_" + GetInstanceId();
AudioServer.AddBus(); int bus = AudioServer.BusCount - 1;
AudioServer.SetBusName(bus, _bus); AudioServer.SetBusSend(bus, "Master");
Gain = new AudioEffectAmplify { VolumeDb = -6 };
Filter = new AudioEffectLowPassFilter { CutoffHz = 500 };
Capture = new AudioEffectCapture { BufferLength = 1 };
AudioServer.AddBusEffect(bus, Gain);
AudioServer.AddBusEffect(bus, Filter);
AudioServer.AddBusEffect(bus, Capture);
Voice = new AudioStreamPlayer { Stream = DemoTone.Create(3000, loop: true), Bus = _bus, PlaybackType = AudioServer.PlaybackType.Stream };
AddChild(Voice);
var layout = new VBoxContainer { Position = new Vector2(24, 24) }; AddChild(layout);
var play = new Button { Text = ru ? "Запустить / остановить" : "Play / stop" }; layout.AddChild(play);
play.Pressed += () => { if (Voice.IsPlaying()) Voice.Stop(); else Voice.Play(); };
var filter = new CheckButton { Text = ru ? "Фильтр 500 Гц" : "500 Hz filter", ButtonPressed = true }; layout.AddChild(filter);
filter.Toggled += enabled => AudioServer.SetBusEffectEnabled(BusIndex, 1, enabled);
var gain = new CheckButton { Text = "-6 dB", ButtonPressed = true }; layout.AddChild(gain);
gain.Toggled += enabled => AudioServer.SetBusEffectEnabled(BusIndex, 0, enabled);
}
public Vector2[] ReadFrames(int count)
{
if (count <= 0 || !Capture.CanGetBuffer(count)) return [];
return Capture.GetBuffer(count);
}
public override void _ExitTree()
{
Voice?.Stop();
int index = BusIndex;
if (index > 0) AudioServer.RemoveBus(index);
}
}The class creates its own uniquely named bus, adds Amplify → LowPass → Capture and removes only that bus on exit. Disabling one effect retains the others; bypassing the whole bus skips both filtering and Capture. Do not expect new captured frames while Capture is disabled or bypassed.
Choose an effect for the task
| Task | API | Check |
|---|---|---|
| Constant gain | AudioEffectAmplify | Gain and headroom |
| Remove highs / lows | LowPass, HighPass | CutoffHz; a filter does not replace a wall's acoustic model |
| Pass / attenuate a band | BandPass, BandLimit, Notch | Frequency and resonance |
| Spectral balance | EQ6, EQ10, EQ21, LowShelf, HighShelf | Bands and levels; boosts accumulate |
| Reduce dynamic range | Compressor | Threshold, Ratio, attack/release; Sidechain for an external control signal |
| Limit peaks | HardLimiter | CeilingDb, PreGainDb, Release; the older AudioEffectLimiter is deprecated |
| Analyze the spectrum | SpectrumAnalyzer | Measures frequency levels; does not modify sound or remove noise |
| Echo / space | Delay, Reverb | Dry/wet, feedback/tail time; original signal and tail are different |
| Modulation and coloration | Chorus, Phaser, Distortion, StereoEnhance | Timbre, phase, level and mono compatibility |
| Shift pitch after mixing | PitchShift | FFT size and artifacts; differs from a player's PitchScale |
| Manual bus panning | Panner | Acts on all sources in the bus; use a 2D/3D player for an object's position |
| Access raw data | Capture | Ring-buffer capacity, reads and discarded frames |
| Build a recorded resource | Record | Start/stop, format and duration |
Read Capture
Each Vector2 is one stereo frame: X is left and Y is right, not two successive moments. Data is float PCM. GetFramesAvailable() reports available frames; GetBuffer(n) consumes n frames. Check CanGetBuffer(n) before requesting; the example returns an empty array when insufficient. Read regularly on the main thread and move expensive analysis out of the frame. Do not retain a global audio-server lock while doing your own processing.
Set BufferLength before adding the effect: assigning it later does not resize an initialized buffer. GetDiscardedFrames() grows if the consumer falls behind. That is data loss, not controlled downsampling. ClearBuffer() discards accumulated frames; do not clear between every read during continuous recording. Float signals before final output can exceed ±1; explicitly clamp when converting to integer PCM.
Capture inside a bus is before its fader/mute. To measure Music after its gain, capture on a receiving bus. Capture on Master is still before Master's own fader; it is not a physical device-output recording.
Capture or Record
Capture supplies raw frames for visualization, analysis or your transport. Record builds an AudioStreamWav: add the effect to a bus, call SetRecordingActive(true), then SetRecordingActive(false) after the intended interval and GetRecording(). Starting again clears the previous recording. Check Error when saving through SaveToWav("user://recording.wav"); long recordings require memory. Capturing a bus does not require microphone access unless a microphone is its source.
Diagnostics
| Symptom | Check |
|---|---|
| Effect is inaudible | Actual player Bus, enable, bypass, order and Stream mode |
| Capture is empty | Signal reaches the bus, effect active, requested count within availability |
| Captured sections are missing | GetDiscardedFrames, read frequency and capacity before initialization |
| Crackles after an adjustment | Combined gain, feedback, overload and DSP cost |
| Different chains change together | They share an effect resource |