Свои звуки и голос

Пошагово: как заменить звуки, которые идут в поставке, на свои. Ничего из этого не обязательно для первого запуска — проект звучит из коробки. Это нужно, когда персонаж должен звучать своим голосом, а не стандартным.

Что вообще звучит

В проекте два независимых набора звука, и путать их не стоит.

Голосовые референсы персонажаresources/Characters/<персонаж>/Voice/. Это не то, что персонаж говорит. Это образцы, по которым синтезатор понимает, каким голосом и с какой интонацией произносить любой новый текст. На каждую эмоцию свой образец.

Звуки интерфейсаresources/Audio/fx/. Короткие сигналы событий: включение, ошибка, бан слова, запись в базу, критический отказ. Персонаж проигрывает их сам, у агента есть инструмент для этого.

Всё, что лежит в репозитории, сделано самим проектом: сигналы синтезированы скриптом, реплики диктора — синтезом речи. Чужой звук сюда класть нельзя — вырезанное из игр, фильмов и роликов не покрывается лицензией проекта, даже если положить «на время» и никому не показывать.

Требования к файлам

Одинаковые для обоих наборов:

Параметр Значение
Формат WAV, PCM 16 бит
Каналы моно
Частота 44 100 Гц

Проверить, что у файла внутри:

ffprobe -v error -show_entries stream=channels,sample_rate,codec_name -of default=nw=1 mysound.wav

Привести к нужному виду что угодно — mp3, стерео, другая частота:

ffmpeg -i исходник.mp3 -ac 1 -ar 44100 -c:a pcm_s16le результат.wav

Голос персонажа

Шаг 1. Заведите своего персонажа

Не правьте Worshiper — скопируйте его и работайте с копией, тогда эталон останется целым:

cp -r resources/Characters/Worshiper resources/Characters/MyChar

В .env укажите:

NETTYAN_CHARACTER_NAME="MyChar"

Шаг 2. Запишите образцы

На каждую эмоцию — один фрагмент. Что работает:

  • длительность 3–10 секунд; короче двух секунд результат нестабилен, длиннее десяти ничего не улучшает
  • один говорящий, без музыки, без фоновых голосов, без эха
  • ровная громкость, без клиппинга
  • интонация должна соответствовать эмоции: образец для happy произносится радостно, иначе синтез не поймёт, чего от него хотят

Записать можно чем угодно — микрофоном, любым синтезатором речи, своим же simple_tts_server.py.

Шаг 3. Разложите файлы

resources/Characters/MyChar/Voice/
├── neutral.wav
├── happy.wav
├── sad.wav
└── refs_manifest.yml

Шаг 4. Опишите их в манифесте

refs_manifest.yml связывает эмоцию, файл и точную расшифровку:

neutral:
  wav: neutral                      # имя файла без .wav
  text: Чем могу быть полезен?      # ровно то, что в файле произнесено

happy:
  wav: happy
  text: Отличные новости, у нас всё получилось!

Эмоций может быть сколько угодно, добавляются просто новыми блоками. Обязательна одна — neutral: если агент выдаст эмоцию, которой в манифесте нет, подставится она.

Звуки интерфейса

Вариант А. Пересобрать генератором

Сигналы из alarms/ не записаны, а синтезированы. Проще всего поправить генератор и пересобрать:

python tools/make_fx_sounds.py --list   # посмотреть, что создаётся
python tools/make_fx_sounds.py          # пересоздать

Зависимостей не нужно, только стандартная библиотека. Внутри функции build() каждый сигнал собирается из примитивов:

Примитив Что делает
tone(freq, sec, vol, freq_end=) тон; с freq_end — плавный сдвиг частоты
noise(sec, vol) шум
envelope(s, attack, release) сглаживает края, без него слышен щелчок
decay(s, power) затухание — так звучат колокол и щелчки
mix(a, b, ...) наложить друг на друга
seq(a, b, ...) проиграть подряд
silence(sec) пауза

Например, сигнал готовности — две восходящие ноты:

s["alarms/ready.wav"] = seq(
    envelope(tone(660, 0.13, 0.42), 0.005, 0.03),
    envelope(tone(990, 0.22, 0.42), 0.005, 0.08))

Вариант Б. Положить свои файлы

  1. Приведите к WAV моно 44.1 кГц 16 бит (команда выше).
  2. Положите в resources/Audio/fx/alarms/ или resources/Audio/fx/records/.
  3. Пропишите путь в fx_manifest.yml напротив имени события.

Держите сигналы короткими: всё длиннее двух секунд персонаж будет перебивать собственной речью.

Как читается fx_manifest.yml

Одиночный сигнал — имя события и путь:

error: records/error.wav
bell: alarms/bell.wav

Последовательность — несколько сигналов подряд по именам:

starting:
- poweron
- none          # короткая тишина, служит паузой
- powerstarting

Внутри последовательности есть два особых элемента:

wiped_state:
- sequence: fatal_errored   # вложенная последовательность
  delay: 4                  # пауза перед ней, секунды
  wait: True                # дождаться конца, а не запускать параллельно
- bell

Ключ none удалять нельзя — он используется как пауза.

Если что-то пошло не так

Звука нет вообще, но и ошибок нет. Файл, указанный в манифесте, не найден на диске — воспроизведение в таком случае молча пропускается. Сверьте путь: он считается от папки resources/Audio/fx/, а в refs_manifest.yml имя пишется без расширения .wav.

Звук играет не туда. Проверьте FX_SOUND_DEVICE_IN и FX_SOUND_DEVICE_OUT в .env — там должно стоять полное название устройства виртуального аудиокабеля. Осторожно с направлениями: OUTPUT ведёт себя как микрофон, а INPUT как колонки.

Клонированный голос звучит криво, будто не тот человек. Почти всегда расшифровка в text не совпадает с тем, что реально произнесено в файле. Сверьте дословно, включая знаки препинания.

Голос дрожит или обрывается. Образец слишком короткий либо шумный. Возьмите фрагмент на 3–10 секунд с чистой записью.

На стыке сигналов слышен щелчок. Файл начинается или кончается резко. Прогоните через envelope() в генераторе или добавьте в редакторе короткое затухание в начале и конце.

Персонаж перебивает сам себя. Сигнал слишком длинный. Режьте до двух секунд.