Files
SoundGame/tools/stable-audio-3/tests/test_inference.py
T
SoundGame Dev f0f0a9b50e 初始提交:3 款纯音频游戏 + 共享引擎 + 完整文档与资产管线
- 共享引擎(app/src/main):GestureEngine 6 手势识别、AudioEngine 三层音频
  (ExoPlayer BGM 交叉淡入 / 对白 ducking / SoundPool 低延迟)、Game/GameEngine
  状态机接口、MainActivity 全屏封面 + TalkBack
- 《声命线》storyline:末世互动广播剧,3 幕 8 抉择点 4 结局
- 《声东击西》reflex:听声辨位反应,12 波战斗 + BGM 三档 BPM 变速
- 《暗室》escape:3x3 网格密室逃脱,钥匙→铁柜→保险丝→电箱→大门谜题链
- 文档:setup(从零搭建)/ architecture(架构)/ engine-api(引擎 API)/
  asset-pipeline(资产流水线)/ games/*(三款游戏设计文档)
- 资产管线:Qwen3-TTS 对白 + 程序合成音效/BGM + Mureka BGM 的 gen 脚本与台账,
  全部 ogg 资产入库可复现
- env.d 环境脚本:android/tts 入库;sfx/mureka 含密钥不入库,仅提交 .example.sh 模板
2026-08-29 13:00:34 +08:00

271 lines
8.1 KiB
Python

import pytest
import torch
from tests.conftest import HAS_ACCEL
from tests.utils.audio import assert_audio_valid, sine_wave
DURATION_SEC = 10
STEPS = 8
STEPS_BASE = 50
def test_text_to_audio_base(sa3_base_model, maybe_save_audio):
model = sa3_base_model
sr = model.model_config["sample_rate"]
prompt = "trap drums, hip hop beat, 120bpm"
audio = model.generate(
prompt=prompt,
duration=DURATION_SEC,
steps=STEPS_BASE,
cfg_scale=7.0,
seed=1234,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, DURATION_SEC, sr)
def test_text_to_audio(sa3_model, maybe_save_audio):
model = sa3_model
sr = model.model_config["sample_rate"]
prompt = "trap drums, hip hop beat, 120bpm"
audio = model.generate(
prompt=prompt,
negative_prompt="low-quality",
duration=DURATION_SEC,
steps=STEPS,
seed=1234,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, DURATION_SEC, sr)
def test_inpainting(sa3_model, maybe_save_audio):
model = sa3_model
sr = model.model_config["sample_rate"]
channels = model.model_config.get("io_channels", 2)
inpaint_duration = 10.0
prompt = "big trumpet solo, jazz big band, 90bpm"
base_audio = sine_wave(
inpaint_duration,
sr,
channels=channels,
device=str(model.device),
half=model.model_half,
)
audio = model.generate(
prompt=prompt,
duration=inpaint_duration,
steps=STEPS,
inpaint_audio=(sr, base_audio),
inpaint_mask_start_seconds=2.0,
inpaint_mask_end_seconds=7.0,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, inpaint_duration, sr)
def test_inpainting_multiple_regions(sa3_model, maybe_save_audio):
model = sa3_model
sr = model.model_config["sample_rate"]
channels = model.model_config.get("io_channels", 2)
inpaint_duration = 20.0
prompt = "jazz piano trio, upbeat swing, 120bpm"
base_audio = sine_wave(
inpaint_duration,
sr,
channels=channels,
device=str(model.device),
half=model.model_half,
)
audio = model.generate(
prompt=prompt,
duration=inpaint_duration,
steps=STEPS,
inpaint_audio=(sr, base_audio),
inpaint_mask_start_seconds=[2.0, 12.0],
inpaint_mask_end_seconds=[11.0, 19.0],
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, inpaint_duration, sr)
def test_continuation(sa3_model, maybe_save_audio):
model = sa3_model
sr = model.model_config["sample_rate"]
channels = model.model_config.get("io_channels", 2)
init_duration = 5.0
total_duration = 15.0
prompt = "thunderstorm with heavy rain"
base_audio = sine_wave(
init_duration,
sr,
channels=channels,
device=str(model.device),
half=model.model_half,
)
audio = model.generate(
prompt=prompt,
duration=total_duration,
steps=STEPS,
inpaint_audio=(sr, base_audio),
inpaint_mask_start_seconds=5.0,
inpaint_mask_end_seconds=15.0,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, total_duration, sr)
def test_init_audio(sa3_model, maybe_save_audio):
model = sa3_model
sr = model.model_config["sample_rate"]
channels = model.model_config.get("io_channels", 2)
prompt = "funky bass groove"
init = sine_wave(
DURATION_SEC,
sr,
channels=channels,
device=str(model.device),
half=model.model_half,
)
audio = model.generate(
prompt=prompt,
duration=DURATION_SEC,
steps=STEPS,
init_audio=(sr, init),
init_noise_level=0.8,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, DURATION_SEC, sr)
def test_init_audio_float32_into_half_model(sa3_model, maybe_save_audio):
model = sa3_model
if not model.model_half:
pytest.skip(
"model_half is False — dtype mismatch only occurs on half-precision models"
)
sr = model.model_config["sample_rate"]
channels = model.model_config.get("io_channels", 2)
prompt = "funky bass groove"
# Deliberately float32, regardless of model dtype — this was the bug trigger
init = sine_wave(
DURATION_SEC, sr, channels=channels, device=str(model.device), half=False
)
assert init.dtype == torch.float32
audio = model.generate(
prompt=prompt,
duration=DURATION_SEC,
steps=STEPS,
init_audio=(sr, init),
init_noise_level=0.8,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, DURATION_SEC, sr)
def test_inpaint_audio_float32_into_half_model(sa3_model, maybe_save_audio):
model = sa3_model
if not model.model_half:
pytest.skip(
"model_half is False — dtype mismatch only occurs on half-precision models"
)
sr = model.model_config["sample_rate"]
channels = model.model_config.get("io_channels", 2)
inpaint_duration = 10.0
prompt = "big trumpet solo, jazz big band, 90bpm"
# Deliberately float32, regardless of model dtype — this was the bug trigger
base_audio = sine_wave(
inpaint_duration, sr, channels=channels, device=str(model.device), half=False
)
assert base_audio.dtype == torch.float32
audio = model.generate(
prompt=prompt,
duration=inpaint_duration,
steps=STEPS,
inpaint_audio=(sr, base_audio),
inpaint_mask_start_seconds=2.0,
inpaint_mask_end_seconds=7.0,
)
maybe_save_audio(audio, sr, prompt)
assert_audio_valid(audio, inpaint_duration, sr)
@pytest.mark.skipif(not HAS_ACCEL, reason="Batch inference requires a GPU/accelerator")
def test_batch_inference(sa3_model, maybe_save_audio):
model = sa3_model
sr = model.model_config["sample_rate"]
batch_size = 3
prompts = ["ocean waves", "summer breeze", "city traffic"]
neg_prompts = ["loud background noise", "bad quality", "loud background noise"]
durations = [5, 10, 20]
duration_padding_sec = 6 # Default, just defining here for clarity
audio_same_durations = model.generate(
prompt=prompts,
negative_prompt=neg_prompts,
duration=DURATION_SEC,
steps=STEPS,
batch_size=batch_size,
)
audio_different_durations = model.generate(
prompt=prompts,
negative_prompt=neg_prompts,
duration=durations,
steps=STEPS,
duration_padding_sec=duration_padding_sec,
batch_size=batch_size,
)
assert audio_same_durations.shape[0] == batch_size, (
f"Expected batch dim {batch_size}, got {audio_same_durations.shape[0]}"
)
# Validate each item in the batch individually and optionally save
for i, prompt in enumerate(prompts):
maybe_save_audio(audio_same_durations[i : i + 1], sr, prompt)
assert_audio_valid(audio_same_durations[i : i + 1], DURATION_SEC, sr)
# Check for diversity in outputs (not identical)
diffs = []
for i in range(batch_size):
for j in range(i + 1, batch_size):
diff = torch.mean(
torch.abs(audio_same_durations[i] - audio_same_durations[j])
).item()
diffs.append(diff)
avg_diff = sum(diffs) / len(diffs)
assert avg_diff > 0.01, f"Batch outputs are too similar (avg diff {avg_diff:.4f})"
# Validate different durations
for i, dur in enumerate(durations):
# Set expected_duration to be longer since we are not truncating
d = max(durations) + duration_padding_sec
maybe_save_audio(
audio_different_durations[i : i + 1], sr, f"{prompts[i]}_{dur}s"
)
assert_audio_valid(audio_different_durations[i : i + 1], d, sr)
audio_shared_neg = model.generate(
prompt=prompts,
negative_prompt="low quality, noise",
duration=durations,
steps=STEPS,
duration_padding_sec=duration_padding_sec,
batch_size=batch_size,
)
d = max(durations) + duration_padding_sec
for i in range(batch_size):
assert_audio_valid(audio_shared_neg[i : i + 1], d, sr)