# Звук — `$.sound`

Эффекты, позиционное звучание, музыка. Позиционность приблизительная:
SDL_mixer умеет панораму (pan −1..1) и громкость, поэтому «где звучит»
вычисляется относительно камеры. Для 2D этого достаточно: источник слева — в
левом ухе.

```js
$.sound.play('shot.wav', { volume: 0.8, pitch: 1.1 });
$.sound.playAt('boom.wav', 500, 200, { radius: 600 });
$.sound.music('theme.ogg', { volume: 0.4, loop: true });
$.sound.crossfade('battle.ogg', 1.5);
```

---

## 1. Эффекты

| Вызов | Смысл |
|---|---|
| `play(file, opts?)` | проиграть ( `volume`, `pitch`, `loop`, `pan`, `priority` ) |
| `seek(handle, seconds)` / `position(handle)` / `durationOf(handle)` | перемотка и позиция |
| `priorityOf(handle)` / `busy()` | с каким приоритетом играет канал и сколько каналов занято |
| `playAt(file, x, y, opts?)` | позиционно от камеры |
| `stopAll()` / `stop(handle)` / `playing(handle)` / `count()` | управление каналами |
| `channel(ch)` | что звучит на канале сейчас: `{ playing, volume, pan, pitch, effect, position, duration }` |
| `activeChannels()` | сколько каналов занято |
| `preload(file)` / `duration(file)` | подготовка и длительность |
| `volume(value?)` / `sfxVolume(value?)` / `mute(on?)` | общая и эффектовая громкость |

## 2. Музыка

| Вызов | Смысл |
|---|---|
| `music(file, opts?)` / `stopMusic()` / `musicPlaying()` | запуск и стоп |
| `musicVolume(value?)` / `musicPitch(value?)` | громкость и тон |
| `pauseMusic(on?)` | пауза музыки (эффекты продолжают) |
| `crossfade(file, seconds)` | переход между треками |

## 2.1. Каналы, приоритеты и перемотка

```js
const h = $.sound.play('shot.wav', { priority: 5 });

$.sound.seek(h, 0.4);      // перемотать: 0.4 с от начала
$.sound.position(h);       // текущая позиция в секундах
$.sound.durationOf(h);     // длительность звука
$.sound.busy();            // { active, free, total } — обычно total 16
```

**Приоритет решает, кого вытеснить.** Каналов всего 16; когда все заняты,
движок глушит **самый неважный** звук и только если новый **не менее важен**,
иначе `play` возвращает `-1` (звук не играет). До этого жертвой **всегда** был
канал 0 — важная реплика глушилась первым же шагом по траве.

Больше число — важнее. Обычный шум шагов — `0`, попадание — `3`, реплика сюжета —
`8`. Слабый звук при полной занятости лучше не проиграть, чем заглушить то, что
игрок должен слышать.

`seek` возвращает `false`, если канал не играет; `position` в этом случае `-1`.
Перемотка в конец доигрывает звук — канал освобождается сам.

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

| Чего нет | Что делать |
|---|---|
| Реверба и эффектов по зонам | отдельная подсистема `$.audio.room` (см. [acoustics.md](highlevel/acoustics)) |
| Честного 3D-звука | только панорама и громкость; высота не передаётся |
| Микширования в JS | всё делает SDL_mixer; свои эффекты — `$.audio` низкого уровня |
| Сжатия в рантайме | файлы берутся как есть (WAV/OGG/MP3) |

## 4. Связанное

* [soundbank.md](highlevel/soundbank) — варианты одного звука (шаги, попадания);
* [acoustics.md](highlevel/acoustics) — реверберация помещений;
* `$.steps`/`$.barks` — шаги по материалу и реплики NPC.
