Files
SoundGame Dev cc0f8c008d HTML5 重写:6 款游戏 + 用户系统 + 全局设置 + 5 轮迭代重构
- 新增 web/ 纯音频 HTML5 平台:共享引擎(手势/WebAudio 三层/设置/震动)、
  Node 零依赖服务器(静态 + 注册登录 + 进度存档 API)
- 游戏:剑与声(RPG 新作)、声命线、暗室(重设计)、音序回响(重设计)、
  节拍行者(跟随领奏重构)、盲航(同向闪避重构);声东击西删除
- 全局功能:男声/女声双套 TTS 资产、三档语速、BGM 音量三档、
  震动反馈语言(10 种震型)、服务器进度同步
- 资产:899 ogg + 899 m4a(Qwen3-TTS 双性别语音、Stable Audio 3 音效、
  Mureka BGM 4+3 首),终检零缺失
- 文档:docs/design/ 13 篇设计审查文档、docs/audio/ 资产管线、
  web/server/README.md API 文档
2026-08-29 21:55:44 +08:00

75 lines
2.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""RPG《剑与声》程序合成音效(5 件):hurt flee step coin levelup。
输出 /tmp/rpg_syn/*.wav(44.1kHz 立体声,峰值归一 0.89),随后 ffmpeg 转 ogg+m4a。
音高敏感件用合成保证可控:levelup 为 A 小调五声(A4 C5 D5 E5 A5)上行钟声琶音。
"""
import os
import numpy as np
import soundfile as sf
sr = 44100
OUT = "/tmp/rpg_syn"
os.makedirs(OUT, exist_ok=True)
def norm(y, peak=0.89):
return (y / np.max(np.abs(y)) * peak).astype(np.float32)
def stereo(y):
return np.stack([y, y], axis=1)
def place(y, start_s, sig):
s = int(start_s * sr)
n = min(len(sig), len(y) - s)
y[s:s + n] += sig[:n]
# hurt:低频闷击(60Hz 轰 + 平滑噪声团),与 atk_hit 的金属/肉质听感区分
t = np.arange(int(sr * 0.35)) / sr
thud = np.sin(2 * np.pi * 60 * t) * np.exp(-t * 18)
noise = np.random.default_rng(7).standard_normal(len(t))
noise = np.convolve(noise, np.ones(64) / 64, mode="same") * np.exp(-t * 25) * 0.6
sf.write(f"{OUT}/hurt.wav", stereo(norm(thud + noise)), sr)
# flee:渐远脚步(4 步,幅度逐次衰减、越走越闷)
y = np.zeros(int(sr * 1.2), dtype=np.float32)
for i in range(4):
L = int(0.12 * sr)
tt = np.arange(L) / sr
w = 32 + i * 24
st = np.convolve(np.random.default_rng(i).standard_normal(L), np.ones(w) / w, mode="same")
st *= np.exp(-tt * 30) * (0.9 ** i)
place(y, i * 0.28, st)
sf.write(f"{OUT}/flee.wav", stereo(norm(y)), sr)
# step:单步脚步(短噪声团 + 90Hz 触点)
t = np.arange(int(sr * 0.22)) / sr
n = np.random.default_rng(3).standard_normal(len(t))
body = np.convolve(n, np.ones(24) / 24, mode="same") * np.exp(-t * 35)
toe = np.sin(2 * np.pi * 90 * t) * np.exp(-t * 30) * 0.5
sf.write(f"{OUT}/step.wav", stereo(norm(body + toe)), sr)
# coin:硬币叮当(非谐波高频泛音簇,两次碰撞)
y = np.zeros(int(sr * 0.45), dtype=np.float32)
for start, dec in [(0.0, 28), (0.12, 32)]:
seg = np.arange(int(sr * (0.45 - start))) / sr
sig = sum(a * np.sin(2 * np.pi * f * seg) * np.exp(-seg * dec) * 0.3
for f, a in [(5200, 1.0), (6900, 0.7), (9100, 0.45), (11400, 0.3)])
place(y, start, sig)
sf.write(f"{OUT}/coin.wav", stereo(norm(y)), sr)
# levelup:上行钟声琶音(A 小调五声 A4 C5 D5 E5 A5;钟 = 基音 + 非谐波泛音快衰减)
y = np.zeros(int(sr * 1.4), dtype=np.float32)
for i, f in enumerate([440.0, 523.25, 587.33, 659.25, 880.0]):
seg = np.arange(int(sr * 1.0)) / sr
bell = sum(a * np.sin(2 * np.pi * f * r * seg) * np.exp(-seg * d)
for r, a, d in [(1, 1.0, 4), (2.4, 0.35, 7), (3.9, 0.15, 10)])
place(y, i * 0.18, bell * 0.4)
sf.write(f"{OUT}/levelup.wav", stereo(norm(y)), sr)
print("SYN done:", sorted(os.listdir(OUT)))