Эффекты: обработка шины и захват сэмплов
Порядок обработки, фильтр и усиление, Capture и Record; как выбрать эффект под задачу.
Где действует эффект
Эффект обрабатывает суммарный сигнал своей шины, а не только последний запущенный плеер. Цепочка идёт сверху вниз. Затем применяется громкость/mute этой шины, и результат отправляется дальше. Компрессор перед усилителем и после него даст разный результат; фиксируйте порядок намеренно.
Один ресурс AudioEffect хранит параметры, а AudioEffectInstance выполняет обработку. Если назначить один ресурс в разные места, изменение его параметров будет общим. Для отдельных настроек создавайте отдельные ресурсы. Тяжёлая цепочка на каждой отдельной шине стоит дороже общей обработки категории.
Проверить цепочку без внешнего файла
Сохраните DemoTone.cs в проекте. Прикрепите EffectsDemo.cs ниже к корню Control отдельного уровня, соберите и запустите F6. Запустить / остановить включает тон 3000 Гц. Отключение фильтра 500 Гц делает его заметно сильнее, переключатель -6 dB меняет усиление примерно вдвое по амплитуде. Это проверка маршрута, не настройка комфортной громкости ваших колонок.
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);
}
}Класс создаёт собственную шину с уникальным именем, добавляет Amplify → LowPass → Capture и удаляет только эту шину при выходе. Выключение отдельного эффекта сохраняет остальные; bypass всей шины обходит и фильтр, и Capture. Не ожидайте новых захваченных кадров, пока Capture выключен или обойдён.
Выберите эффект по задаче
| Задача | API | Что проверить |
|---|---|---|
| Постоянный коэффициент | AudioEffectAmplify | Усиление и запас до перегрузки |
| Убрать верх / низ | LowPass, HighPass | CutoffHz; фильтр не заменяет акустическую модель стены |
| Выделить / ослабить диапазон | BandPass, BandLimit, Notch | Частота и резонанс |
| Коррекция спектра | EQ6, EQ10, EQ21, LowShelf, HighShelf | Полосы и уровни; несколько усилений суммируются |
| Сжать динамический диапазон | Compressor | Threshold, Ratio, время атаки/восстановления; Sidechain для внешнего управляющего сигнала |
| Ограничить пики | HardLimiter | CeilingDb, PreGainDb, Release; старый AudioEffectLimiter помечен устаревшим |
| Анализировать спектр | SpectrumAnalyzer | Получает уровни частот; сам не изменяет звук и не подавляет шум |
| Эхо / пространство | Delay, Reverb | Dry/wet, feedback/время хвоста; исходный звук и хвост — разные сигналы |
| Модуляция и окраска | Chorus, Phaser, Distortion, StereoEnhance | Изменение тембра, фазы, уровня и моносовместимости |
| Сдвиг высоты после микширования | PitchShift | Размер FFT и артефакты; это не то же самое, что PitchScale плеера |
| Ручная панорама шины | Panner | Действует на все источники шины; для положения объекта используйте 2D/3D-плеер |
| Получить сырые данные | Capture | Ёмкость кольцевого буфера, чтение и потерянные кадры |
| Собрать записанный ресурс | Record | Начало/конец записи, формат и длительность |
Читать Capture
Один элемент Vector2 — один стереокадр: X левый, Y правый; это float PCM, не два последовательных момента времени. GetFramesAvailable() показывает доступное число кадров, GetBuffer(n) потребляет n кадров. Для безопасного запроса сначала используйте CanGetBuffer(n); пример возвращает пустой массив при недостатке. Читайте данные регулярно в основном потоке и выносите дорогой анализ из кадра. Не удерживайте глобальную блокировку аудиосервера на время своей обработки.
BufferLength задаётся до добавления эффекта: уже созданный буфер не меняет ёмкость от простого присваивания. Если потребитель не успевает, GetDiscardedFrames() растёт. Это потеря данных, а не корректное прореживание. ClearBuffer() отбрасывает накопленное; для непрерывной записи не очищайте буфер между каждым чтением. Float-сигнал до конечного выхода может выйти за ±1; при переводе в int PCM ограничивайте диапазон явно.
Capture внутри шины находится до её фейдера/mute. Для измерения уже ослабленной Music разместите захват на принимающей шине. Захват на Master всё ещё находится перед фейдером самого Master; это не запись физического выхода устройства.
Capture или Record
Capture отдаёт сырые кадры для визуализации/анализа/вашего транспорта. Record собирает AudioStreamWav: добавьте эффект в шину, вызовите SetRecordingActive(true), после нужного интервала — SetRecordingActive(false), затем GetRecording(). Новый старт очищает предыдущую запись. При сохранении через SaveToWav("user://recording.wav") проверяйте Error; длинная запись требует памяти. Захват шины не требует доступа к микрофону, пока источником не назначен микрофон.
Диагностика
| Симптом | Проверка |
|---|---|
| Эффект не слышен | Реальный Bus плеера, enable, bypass, порядок и режим Stream |
| Capture пуст | Сигнал доходит до шины, эффект активен, запрошено не больше доступного |
| Захват теряет участки | GetDiscardedFrames, частота чтения и ёмкость до инициализации |
| Треск после «улучшения» | Суммарное усиление, feedback, перегрузка и стоимость DSP |
| Одинаково меняются разные цепочки | Они используют общий ресурс эффекта |