# Аудио

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

### Бекенд (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
```

### audio.PCMStream

```lua
-- создание источника PCM данных
local stream = audio.PCMStream(
    -- частота дискретизации
    sample_rate: integer,
    -- число каналов (1 - моно, 2 - стерео)
    channels: integer,
    -- число бит на сэмпл (8 или 16)
    bits_per_sample: integer,
)

-- подача PCM данных в поток
stream:feed(
    -- PCM данные для подачи в поток
    data: Bytearray
)

-- публикация источника PCM данных для использования системами движка
stream:share(
    -- имя потокового аудио, которое можно будет указать в audio.play_stream
    alias: string
)

-- создание звука из имеющихся в потоке PCM данных
stream:create_sound(
    -- имя создаваемого звука
    name: string
)
```

### Запись звука

```lua
-- запрашивает доступ к записи звука
-- при подтверждении, в callback передаётся токен для использовании в audio.input.fetch
audio.input.request_open(callback: function(string))

-- читает новые PCM данные аудио ввода
audio.input.fetch(
    -- токен, полученный через audio.input.request_open
    access_token: string,
    -- максимальное размер буфера в байтах
    [опционально] max_read_size: integer
)
```

### Пример генерации аудио:

```lua
-- для работы с 16-битными семплами используйте U16view поверх Bytearray
-- пример:
local max_amplitude = 32767
local sample_rate = 44100
local total_samples = sample_rate * 5 -- 5 секунд моно
local bytes = Bytearray(total_samples * 2) -- 5 секунд 16 бит моно
local samples = I16view(bytes)

local frequency_hz = 400
for i=1, total_samples do
    local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz)
    samples[i] = value * max_amplitude
end

local stream_name = "test-stream"
local stream = audio.PCMStream(sample_rate, 1, 16)
stream:feed(bytes)
stream:share(stream_name)

local volume = 1.0
local pitch = 1.0
local channel = "ui"
audio.play_stream_2d(stream_name, volume, pitch, channel)
```
