Звуковите ефекти в малка игра обикновено идват от много места: пакет, купен преди години, безплатна библиотека, няколко звука, записани у дома, един, направен на синтезатор в два през нощта. Всеки е наред сам по себе си. Заедно огнената топка заглушава диалога, вземането на монета е по-силно от експлозията, а вятърът, който се върти под нивото, щраква на всеки четири секунди. Тук показваме как да измервате силата на звука така, както го правят телевизията и стрийминг услугите, как да го приложите към кратките звуци в игрите и как да проверите циклите. Всеки скрипт тук е пуснат с Python и ffmpeg върху истински файлове.
#Защо нормализирането по пик не работи
Повечето редактори предлагат „нормализиране“, което усилва файла, докато най-силната му проба стигне ниво като -1 dBFS. Така всеки файл ползва целия обхват, но не звучи еднакво силно. Ухото реагира на енергията във времето и на честотата, а не на една-единствена най-висока проба.
Измерихме два звука от нашия пакет със заклинания, изравнени до една и съща сила. Ударът на мълния има пик -1,6 dB, а заклинанието с лед -7,7 dB, шест децибела разлика, и звучат приблизително еднакво силно. Нормализирайте ги и двата до пик -1 и заклинанието с лед става с почти 7 LU по-силно от мълнията (LU е единица за сила на звука, около един децибел). Това е ефектът „монетата е по-силна от експлозията“, получен от бутон, който уж трябваше да изравни нещата.
#LUFS и кой LUFS за кратък звук
Силата на звука в LUFS (единици спрямо пълната скала) идва от ITU-R BS.1770, метода зад силата на звука в телевизията и стрийминга. Той претегля честотите приблизително като ухото, така че дълбокият бас тежи по-малко, а средно-високите честоти повече, и измерва енергията във времето. Измервателят на EBU определя три показания:
- Моментно: последните 400 милисекунди.
- Краткосрочно: последните 3 секунди.
- Интегрирано: целият файл, без тихите части.
Интегрираната сила е правилна за музика и говор. За удар от една секунда тя подвежда, защото опашката и тишината след удара свалят средното. При нашите измервания тежък удар с огън показва -23,1 LUFS интегрирано, но -16,0 в най-силния си момент, а заклинание с лед с по-мека опашка показва -18,2 интегрирано и също -16,0 в най-силния момент. Ако ги изравните по интегрираната сила, ударът ще бъде усилен с около пет единици и ще излезе много по-силен от заклинанието. За единичните звуци изравнявайте максималната моментна сила: най-силните 400 милисекунди, което е приблизително това, което играчът чува като „колко силно беше“. За циклите и фоновите звуци, които звучат дълго, правилното показание е интегрираното.
#Измерване с ffmpeg
Филтърът ebur128 на ffmpeg е пълен измервател по EBU, включително истински пик. Този скрипт отпечатва трите числа за всеки файл:
import re
import subprocess
import sys
def measure(path):
"""Integrated and max momentary loudness (LUFS) and true peak (dBTP) of one file."""
log = subprocess.run(
["ffmpeg", "-nostdin", "-nostats", "-i", path,
"-af", "apad=pad_dur=0.5,ebur128=peak=true:framelog=info", "-f", "null", "-"],
capture_output=True, text=True, check=True).stderr
momentary = [float(m) for m in re.findall(r"\bM:\s*(-?[\d.]+)", log)]
summary = log[log.rfind("Summary:"):]
integrated = float(re.search(r"I:\s*(-?[\d.]+)\s*LUFS", summary).group(1))
peak = float(re.search(r"Peak:\s*(-?[\d.]+)\s*dBFS", summary).group(1))
return integrated, max(momentary), peak
def normalize(src, dst, target=-16.0, ceiling=-1.0):
"""One-shot: max momentary loudness to target, unless the true peak would pass the ceiling."""
_, momentary, peak = measure(src)
gain = min(target - momentary, ceiling - peak)
subprocess.run(["ffmpeg", "-nostdin", "-loglevel", "error", "-y", "-i", src,
"-af", f"volume={gain:.2f}dB", "-c:a", "pcm_s24le", dst], check=True)
return gain
if __name__ == "__main__":
for path in sys.argv[1:]:
i, m, tp = measure(path)
print(f"{path}: integrated {i:.1f} LUFS, max momentary {m:.1f} LUFS, true peak {tp:.1f} dBTP")
Две подробности лесно се пропускат. Филтърът apad добавя половин секунда тишина, защото моментният измервател има нужда от цял прозорец от 400 ms и много кратко щракване иначе никога не би се измерило изцяло. А peak=true включва измерването на истинския пик; без него получавате пика на пробите, което е следващият проблем.
normalize() усилва или отслабва файла така, че най-силният му момент да стигне целта, но никога толкова, че истинският пик да мине тавана. Ако таванът надделее, звукът остава по-тих от целта и това е решение за ушите ви: лимитер може да го направи по-силен, с цената на по-мека атака. Изблик розов шум и удар на 60 Hz, които направихме за теста, излязоха точно на -16,0 LUFS моментно; изблика трябваше да усилим с 11,6 dB, а удара с 13,1, защото ниският удар тежи по-малко в претеглянето. Истинските им пикове останаха на -2,9 и -5,9 dB, спокойно под тавана.
#Истинският пик и какво става след износа
Цифровият файл пази проби, но високоговорителят получава гладка вълна, възстановена между тях, и тази вълна може да се издигне над най-високата проба. Такива „пикове между пробите“ се изрязват в преобразувателя или в следващата стъпка. Кодирането със загуби добавя към това: Ogg версията на нашия удар с огън има с 0,1 dB по-висок истински пик от WAV. Дръжте истинския пик на -1 dBTP или по-ниско в основните файлове и мерете отново след преобразуване към Ogg, MP3 или компресирания формат на двигателя.
#Изберете цели и ги спазвайте
Няма един стандарт за файловете със звукови ефекти за игри. EBU R128 задава -23 LUFS за цели телевизионни програми, а игрите често се миксират по-силно, особено на телефони. По-важно от точното число е всеки звук от един вид да е изравнен до една и съща цел и видовете да имат постоянно съотношение. В нашия пакет единичните звуци са изравнени до -16 LUFS максимална моментна сила, циклите на снарядите до -20 LUFS интегрирано, а дългите цикли на аурите до -24, всички с истински пик на -1 dBTP или по-ниско. Така заклинание, ударът му и цикъл под тях звучат в разумно съотношение, без да пипате плъзгач, а миксерът на играта се грижи за останалото.
#Цикли, които не щракат
Цикълът щраква, когато последната и първата проба не се срещат: вълната скача и високоговорителят свири скока като щракване. Тази проверка сравнява скока в точката на цикъла със скоковете вътре във файла:
import array
import subprocess
import sys
def samples(path):
raw = subprocess.run(["ffmpeg", "-nostdin", "-loglevel", "error", "-i", path,
"-f", "f32le", "-ac", "1", "-"], capture_output=True, check=True).stdout
return array.array("f", raw)
def seam_ratio(x):
"""Jump from the last sample to the first, against the 99.9th percentile of jumps inside."""
steps = sorted(abs(b - a) for a, b in zip(x, x[1:]))
typical = steps[int(len(steps) * 0.999)] + 1e-12
return abs(x[0] - x[-1]) / typical
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"{path}: seam {seam_ratio(samples(path)):.2f}")
Стойност 1 или по-малко значи, че точката на цикъла е толкова гладка, колкото всяка друга точка. Тон от 440 Hz, дълъг точно една секунда, който съдържа цял брой периоди, дава 1,00; същият тон, дълъг 0,99 секунди, дава 8,54 и щраква ясно. Нашият цикъл на огнен снаряд дава 0,03 като WAV и 0,04 като Ogg.
Знайте границата на теста: при шумен материал той може да пропусне лош срез по случайност. Срязахме същия огнен цикъл в три произволни точки и получихме стойности между 0,20 и 0,74, макар че тези срезове променят ритъма на звука. Затова пускайте проверката, за да хванете щракванията, и пак слушайте всеки цикъл поне три повторения.
Сигурният начин да направите цикъл е да генерирате или запишете малко повече от нужното и да сгънете излишната опашка върху началото с кросфейд с равна мощност, така че краят да прелива в началото по конструкция. Дръжте циклите в WAV или Ogg Vorbis: нашият двусекунден Ogg цикъл се декодира точно до 96 000 проби, като WAV файла. MP3 файловете носят закъснение и допълване от кодера; някои декодери ги махат по информацията във файла, а други не, така че MP3 цикъл, който е безупречен в един двигател, може да има пауза в друг.
#Пакет, направен така
Нашият звуков пакет Zaklinka следва тези правила: 97 магически звукови ефекта в десет школи, всяка със заклинания, зареждане, три удара и безшевни цикли за снаряд и аура, плюс 17 общи звука за заклинания, като 48 kHz 24-битов WAV и Ogg Vorbis. Всеки файл е изравнен до целите по-горе и измерен със същия измервател на ffmpeg, циклите са проверени в точката на свързване, а описателен файл дава силата и истинския пик на всеки файл. Звуците са направени изцяло с код, без записи и без генеративни модели за звук, и са проверени с измервания и спектрограми, а не със слушане, затова ги чуйте на страницата за преглед, преди да ги изберете. Обявен е като „Очаквайте скоро“.
#Често задавани въпроси
Каква сила на звука трябва да имат звуковите ефекти в играта ми?
Няма един стандарт. Изберете цели за всеки вид звук (например -16 LUFS максимална моментна сила за единичните звуци и по-ниски интегрирани цели за циклите), изравнете всеки файл към тях и дръжте истинския пик на -1 dBTP или по-ниско.
Защо да не ползвам интегрирания LUFS за кратките звуци?
Защото опашката и тишината свалят средното, така че звуците с дълги опашки се усилват твърде много. Максималната моментна сила следва това, което играчът чува като удара.
Трябва ли да меря отново след преобразуване към Ogg?
Да, поне истинския пик. Кодирането със загуби променя леко вълната и може да вдигне пиковете между пробите; в нашия тест Ogg файлът имаше с 0,1 dB по-висок пик.
Защо цикълът ми щраква в двигателя, а в редактора не?
Често заради формата: MP3 добавя закъснение и допълване от кодера, които не всеки декодер маха. Ползвайте WAV или Ogg Vorbis за циклите и проверявайте точката на свързване.
Коментари
Коментари
Напишете първия коментар.
Оставете коментар