283 lines
14 KiB
Markdown
283 lines
14 KiB
Markdown
# Аудио
|
||
|
||
## Основные понятия
|
||
|
||
### Бекенд (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)
|
||
```
|