# `$.sfxgen` — процедурные звуковые эффекты

Звук из описания, без файлов: слова или пресет → **дескриптор R2D SFX** → процедурный синтез (C) → WAV.
Ядро — `src/sfxgen.c`: алгоритм sfxr (Tomas Pettersson, MIT), набор параметров и пресетов совместим с
[rFXGen](https://raylibtech.itch.io/rfxgen) (raylib technologies, zlib). Целиком rFXGen в движок не встроен:
у него интерфейс на raylib/raygui, движку нужен только расчёт сэмплов.

```js
$.sfxgen.play('coin');                              // сгенерировать (кэш) и сыграть
$.sfxgen.play('короткий высокий прыжок');           // слова → пресет + модификаторы
$.sfxgen.render({ preset: 'laser', seed: 7 }, 'assets/sfx/laser.wav');   // записать WAV в проект
$.sfx.play('step');                                 // после $.sfxgen.bank('step', [...])
```

Формат выхода фиксирован: WAV, 44100 Гц, моно, 16 бит. Синтез детерминирован: один дескриптор — одни и те же сэмплы.

## 1. Вызовы

| Вызов | Что делает |
|---|---|
| `presets()` | имена пресетов: `coin`, `laser`, `explosion`, `powerup`, `hurt`, `jump`, `blip`, `random` |
| `defaults()` | дескриптор по умолчанию |
| `preset(name, opts?)` | дескриптор из пресета; `opts` — `seed` и любые поля поверх |
| `describe(text, seed?)` | слова → `{ preset, params }` или `null`, если тип звука не опознан |
| `mutate(params, seed?, amount?)` | вариация (amount 0..1, по умолчанию 0.1) |
| `render(spec, path?)` | посчитать звук; с `path` — записать WAV. → `{ samples, seconds, peak, rms, clipped, pitchHz, path? }` |
| `make(spec, seed?)` | WAV в кэше (по хешу дескриптора) → путь файла |
| `play(spec, opts?)` | `make` + `$.sound.play` |
| `bank(name, specs)` | несколько вариантов в банк для `$.sfx.play(name)` |
| `cacheDir(dir?)` | каталог кэша (по умолчанию `sfxgen_cache`) |
| `resolve(spec, seed?)` | `spec` → дескриптор |

`spec` — строка (пресет или описание словами) или объект: `{ preset: 'coin', seed: 3, decay: 0.2 }`, `{ text: 'big explosion' }`
или просто поля дескриптора. Неизвестный пресет/слово/поле — ошибка с кодом `R2D_SFXGEN_UNKNOWN` / `R2D_SFXGEN_BAD_FIELD`
и перечнем допустимых значений; в сборке без синтезатора — `R2D_SFXGEN_UNAVAILABLE`.

## 2. Дескриптор

| Поле | Диапазон | Смысл |
|---|---|---|
| `wave` | `square`, `saw`, `sine`, `noise` | форма волны |
| `attack`, `sustain`, `decay` | 0..1 | огибающая: нарастание, удержание, спад (длина звука ≈ их сумма) |
| `punch` | 0..1 | всплеск громкости в начале удержания |
| `freq`, `minFreq` | 0..1 | стартовая частота; нижний предел (звук обрывается, когда высота падает до него) |
| `slide`, `deltaSlide` | -1..1 | скольжение высоты и его ускорение (вверх/вниз) |
| `vibratoDepth`, `vibratoSpeed` | 0..1 | вибрато |
| `changeAmount`, `changeSpeed` | -1..1 / 0..1 | скачок высоты через время (арпеджио) |
| `duty`, `dutySweep` | 0..1 / -1..1 | скважность квадрата и её ход |
| `repeat` | 0..1 | перезапуск высоты (повторяющийся эффект) |
| `phaserOffset`, `phaserSweep` | -1..1 | фазер |
| `lpf`, `lpfSweep`, `lpfResonance` | 0..1 (1 — выкл.), -1..1, 0..1 | фильтр низких частот |
| `hpf`, `hpfSweep` | 0..1, -1..1 | фильтр высоких частот |
| `volume` | 0..1 | общая громкость (по умолчанию 0.5; пик пресетов на ней ≈ 0.2–0.8) |
| `seed` | целое | зерно: выбор варианта пресета и шума |

Значения вне диапазона прижимаются, `NaN` заменяется значением по умолчанию.

## 3. Описание словами

`describe`/`render('…')` понимают тип звука (русские и английские слова): монета/coin/подбор → `coin`; выстрел/лазер/laser/shoot →
`laser`; взрыв/boom → `explosion`; бонус/power-up/улучшение → `powerup`; удар/урон/hit/hurt → `hurt`; прыжок/jump → `jump`;
клик/выбор/меню/beep → `blip`; случайный/random → `random`. Модификаторы: высокий/high, низкий/low, короткий/short,
длинный/long, тихий/quiet, громкий/loud, грубый/harsh, ретро/8-bit. Побеждает слово, стоящее раньше в тексте.
Это не нейросеть, а словарь: непонятное описание даёт `null`/ошибку, а не случайный звук.

## 4. Инструменты вне игры

* CLI: `r2d sfx-gen <spec.json>` (`text`/`preset`/`from`, `seed`, `mutate`, `params`, `out`, `rfx`) — пишет WAV и отвечает JSON с
  длительностью, пиком, rms, высотой тона и ASCII-огибающей: результат можно проверить без слуха.
* Агент Editor: инструмент `generate_sound` и навык `generate-sound` ([EDITOR_AGENT.md](https://github.com/Nikide/russiano2d/blob/main/docs/EDITOR_AGENT.md)).
* `.rfx`: дескриптор записывается и читается в раскладке rFXGen (заголовок «rFX », версия 200, 96 байт параметров).
  **Раскладка воспроизведена по описанию формата и не сверялась с файлами оригинальной программы.**

## 5. Ограничения

* WAV пишется файлом: `render(spec, path)` и `make` создают файл в каталоге игры (кэш — `sfxgen_cache`); в собранной игре
  без записи на диск генерируйте звуки заранее (`render` в `assets/…`) и играйте как обычные файлы.
* Синтез — 8-битный по духу (sfxr): подходит для эффектов, не для реалистичных звуков и музыки.
* Длина звука не больше ≈6.8 с.
* Громкость пресетов подобрана под 0.5; на выходе мягкий ограничитель, жёсткого среза нет.

## 6. Нативный backend

| Имя | Описание |
|---|---|
| `engine.sfxgen` | Внутренний нативный backend синтезатора (`src/sfxgen.c`: `presets`, `defaults`, `preset`, `text`, `mutate`, `render`) для фасада `$.sfxgen`; игровые сценарии используют `$.sfxgen`. |
