# Аудио

## Основные понятия

### Бекенд (Backend)

Вариант внутренней реализации звуковой подсистемы, управляющий выводом звука.
На данный момент в движке существует два:
- NoAudio - заглушка, используемая при невозможности инициализации OpenAL, либо, при отключенной через файл настроек, аудиосистеме: `[audio] enabled=false`. Данный бекенд загружает PCM данные только по требованию, не создает спикеров при попытке воспроизведения аудио.
- ALAudio - основной вариант. Вывод звука через OpenAL.

### Канал (Channel)

Определяет категорию источников аудио для регулирования громкости, наложения эффектов, паузы.
На данный момент существует следующий набор каналов:
- master - управляет громкостью остальных каналов. Не следует указывать как целевой канал при воспроизведении аудио.
- ui - звуки интерфейса
- regular - звуки игрового мира, ставятся на паузу вместе с игрой.
- ambient - то же, что и regular, но предназначается для фоновых звуков: погода и иной эмбиент.
- music - канал для воспроизведения музыки. Как правило, потокового аудио.

Каналы управляются самим движком.
### Спикер (Speaker)

Одноразовый контроллер проигрываемого аудио: звука или потока. Спикер уничтожается после остановки через вызов метода **stop** или при окончании аудио (поток также не удерживает спикер от уничтожения).
Контроллер продолжает жить при паузе. 

> [!NOTE]
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.

Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
### Звук (Sound)

Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.

### Источник PCM (PCMStream)

Поток, используемый потоком как источник PCM-данных. Реализация зависит не от бекенда звуковой системы, а от формата файла. Реализация потокового аудио из сетевого соединения делается через реализацию данного интерфейса.

### Поток (Stream)

Потоковое аудио. Не загружается полностью в память, поэтому не требует предзагрузки через `preload.json`. Не может воспроизводиться через несколько спикеров одновременно.

## Поддержка форматов

На данный момент реализована поддержка двух форматов.
- WAV: поддерживаются 8 и 16 bit (24 bit не поддерживается OpenAL)
- OGG: реализовано через библиотеку libvorbis


## Дополнительно

> [!WARNING]
> При воспроизведении через OpenAL стерео звуки не будут учитывать расположение источников относительно игрока. Звуки, которые должны учитывать расположение, должны быть в моно.

## API аудио в скриптинге

### Воспроизведение аудио

Работа с аудио производится с библиотекой `audio`.

```lua
audio.play_stream(
    -- путь к аудио-файлу (без точки входа, но с указанием расширения)
    name: string, 
    -- позиция источника аудио в мире
    x: number, y: number, z: number,
    -- громкость аудио (от 0.0 до 1.0)
    volume: number
    -- скорость воспроизведения (положительное число)
    pitch: number,
    -- [опционально] имя канала: regular/ambient/music/ui (по-умолчанию - regular)
    channel: string,
    -- [опционально] зацикливание потока (по-умолчанию - false)
    loop: bool
) -> int
```

Воспроизводит потоковое аудио из указанного файла, на указанной позиции в мире. Возвращает id спикера.

```lua
audio.play_stream_2d(
    -- путь к аудио-файлу (без точки входа, но с указанием расширения)
    name: string, 
    -- громкость аудио (от 0.0 до 1.0)
    volume: number
    -- скорость воспроизведения (положительное число)
    pitch: number,
    -- [опционально] имя канала: regular/ambient/music/ui (по-умолчанию - regular)
    channel: string,
    -- [опционально] зацикливание потока (по-умолчанию - false)
    loop: bool
) -> int
```

Воспроизводит потоковое аудио из указанного файла. Возвращает id спикера.


```lua
audio.play_sound(
    -- название загруженного звука без префикса пака, "sounds/", номера варианта и расширения 
    -- пример "steps/stone" для проигрывания звука, загруженного из "sounds/steps/stone.ogg" или любого из его вариантов
    -- вариант звука выбирается случайно
    name: string, 
    -- позиция источника аудио в мире
    x: number, y: number, z: number,
    -- громкость аудио (от 0.0 до 1.0)
    volume: number
    -- скорость воспроизведения (положительное число)
    pitch: number,
    -- [опционально] имя канала: regular/ambient/music/ui (по-умолчанию - regular)
    channel: string,
    -- [опционально] зацикливание потока (по-умолчанию - false)
    loop: bool
) -> int
```

Воспроизводит звук на указанной позиции в мире. Возвращает id спикера.

```lua
audio.play_sound_2d(
    -- название загруженного звука без префикса пака, "sounds/", номера варианта и расширения 
    -- пример "steps/stone" для проигрывания звука, загруженного из "sounds/steps/stone.ogg" или любого из его вариантов
    -- вариант звука выбирается случайно
    name: string, 
    -- громкость аудио (от 0.0 до 1.0)
    volume: number
    -- скорость воспроизведения (положительное число)
    pitch: number,
    -- [опционально] имя канала: regular/ambient/music/ui (по-умолчанию - regular)
    channel: string,
    -- [опционально] зацикливание потока (по-умолчанию - false)
    loop: bool
) -> int
```

Воспроизводит звук. Возвращает id спикера.

### Взаимодействие со спикером.

При обращении к несуществующим спикером ничего происходить не будет.

```lua
-- остановить воспроизведение спикера
audio.stop(speakerid: integer)

-- поставить спикер на паузу
audio.pause(speakerid: integer)

-- снять спикер с паузы
audio.resume(speakerid: integer)

-- установить зацикливание аудио
audio.set_loop(speakerid: integer, state: bool)

-- проверить, зациклено ли аудио (false если не существует)
audio.is_loop(speakerid: integer) -> bool

-- получить громкость спикера (0.0 если не существует)
audio.get_volume(speakerid: integer) -> number

-- установить громкость спикера
audio.set_volume(speakerid: integer, volume: number)

-- получить скорость воспроизведения (1.0 если не существует)
audio.get_pitch(speakerid: integer) -> number

-- установить скорость воспроизведения
audio.set_pitch(speakerid: integer, pitch: number)

-- получить временную позицию аудио в секундах (0.0 если не существует)
audio.get_time(speakerid: integer) -> number

-- установить временную позицию аудио в секундах
audio.set_time(speakerid: integer, time: number)

-- получить позицию источника звука в мире (nil если не существует)
audio.get_position(speakerid: integer) -> number, number, number

-- установить позицию источника звука в мире
audio.set_position(speakerid: integer, x: number, y: number, z: number)

-- получить скорость движения источника звука в мире (nil если не существует)
-- (используется OpenAL для имитации эффекта Доплера)
audio.get_velocity(speakerid: integer) -> number, number, number

-- установить скорость движения источника звука в мире
-- (используется OpenAL для имитации эффекта Доплера)
audio.set_velocity(speakerid: integer, x: number, y: number, z: number)

-- получить длительность аудио в секуднах, проигрываемого источником
-- возвращает 0, если не спикер не существует
-- так же возвращает 0, если длительность неизвестна (пример: радио)
audio.get_duration(speakerid: integer) -> number
```

### Другие функции

```lua
-- получить текущее число живых спикеров
audio.count_speakers() -> integer

-- получить текущее число проигрываемых аудио-потоков
audio.count_streams() -> integer
```
