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