Files
SoundGame/tools/stable-audio-3/stable_audio_3/inference/audio_utils.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

98 lines
3.0 KiB
Python

import numpy as np
import torch
from torch import nn
from ..data.utils import PadCrop
from torchaudio import transforms as T
def numpy_audio_to_tensor(audio):
"""Convert a numpy audio array to a float32 torch tensor with shape [channels, samples]."""
if audio.dtype == np.float32:
t = torch.from_numpy(audio)
elif audio.dtype == np.int16:
t = torch.from_numpy(audio).float().div(32767)
elif audio.dtype == np.int32:
t = torch.from_numpy(audio).float().div(2147483647)
else:
raise ValueError(f"Unsupported audio dtype: {audio.dtype}")
if t.dim() == 1:
t = t.unsqueeze(0) # [n] -> [1, n]
elif t.dim() == 2:
t = t.transpose(0, 1) # [n, channels] -> [channels, n]
return t
def preprocess_audio(sr_audio, sample_rate, sample_size, half=False):
"""Normalize a (sr, numpy_or_tensor) tuple into (sample_rate, tensor) ready for the model."""
in_sr, audio = sr_audio
if isinstance(audio, np.ndarray):
audio = numpy_audio_to_tensor(audio)
elif audio.dim() == 1:
audio = audio.unsqueeze(0)
elif audio.dim() == 2:
audio = audio.transpose(0, 1) # [n, 2] -> [2, n]
if half:
audio = audio.to(torch.float16)
if in_sr != sample_rate:
resample_tf = T.Resample(in_sr, sample_rate).to(audio.device).to(audio.dtype)
audio = resample_tf(audio)
if audio.shape[-1] > sample_size:
audio = audio[:, :sample_size]
return (sample_rate, audio)
def set_audio_channels(audio, target_channels):
# Add channel dim if it's missing
if audio.dim() == 2:
audio = audio.unsqueeze(1)
if target_channels == 1:
# Convert to mono
audio = audio.mean(1, keepdim=True)
elif target_channels == 2:
# Convert to stereo
if audio.shape[1] == 1:
audio = audio.repeat(1, 2, 1)
elif audio.shape[1] > 2:
audio = audio[:, :2, :]
return audio
def prepare_audio(audio, in_sr, target_sr, target_length, target_channels, device):
audio = audio.to(device)
if in_sr != target_sr:
resample_tf = T.Resample(in_sr, target_sr).to(device)
audio = resample_tf(audio)
audio = PadCrop(target_length, randomize=False)(audio)
# Add batch dimension
if audio.dim() == 1:
audio = audio.unsqueeze(0).unsqueeze(0)
elif audio.dim() == 2:
audio = audio.unsqueeze(0)
audio = set_audio_channels(audio, target_channels)
return audio
class PadCrop(nn.Module):
def __init__(self, n_samples, randomize=True):
super().__init__()
self.n_samples = n_samples
self.randomize = randomize
def __call__(self, signal):
n, s = signal.shape
start = 0 if (not self.randomize) else torch.randint(0, max(0, s - self.n_samples) + 1, []).item()
end = start + self.n_samples
output = signal.new_zeros([n, self.n_samples])
output[:, :min(s, self.n_samples)] = signal[:, start:end]
return output