- 共享引擎(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 模板
123 lines
3.2 KiB
Python
123 lines
3.2 KiB
Python
import numpy as np
|
|
import pytest
|
|
|
|
pytest.importorskip("mlx.core")
|
|
|
|
import mlx.core as mx
|
|
|
|
from optimized.mlx.models.defs.audio_encoding import (
|
|
SAMPLES_PER_LATENT,
|
|
encode_audio,
|
|
patch_audio,
|
|
)
|
|
|
|
|
|
class GroupingEncoder:
|
|
def __init__(self, stride: int = 16):
|
|
self.stride = stride
|
|
|
|
def __call__(self, patches):
|
|
batch, channels, patch_count = patches.shape
|
|
return mx.mean(
|
|
patches.reshape(
|
|
batch,
|
|
channels,
|
|
patch_count // self.stride,
|
|
self.stride,
|
|
),
|
|
axis=-1,
|
|
)
|
|
|
|
|
|
def test_patch_audio_matches_patched_pretransform_layout():
|
|
audio = mx.array(np.arange(2 * 8, dtype=np.float32).reshape(1, 2, 8))
|
|
|
|
patched = patch_audio(audio, patch_size=4)
|
|
|
|
np.testing.assert_array_equal(
|
|
np.asarray(patched),
|
|
np.array(
|
|
[
|
|
[
|
|
[0, 4],
|
|
[1, 5],
|
|
[2, 6],
|
|
[3, 7],
|
|
[8, 12],
|
|
[9, 13],
|
|
[10, 14],
|
|
[11, 15],
|
|
]
|
|
],
|
|
dtype=np.float32,
|
|
),
|
|
)
|
|
|
|
|
|
def test_encode_audio_pads_to_codec_alignment_and_builds_mask():
|
|
audio = mx.zeros((2, 2, SAMPLES_PER_LATENT + 1))
|
|
|
|
encoded = encode_audio(
|
|
GroupingEncoder(),
|
|
audio,
|
|
valid_sample_lengths=[SAMPLES_PER_LATENT, SAMPLES_PER_LATENT + 1],
|
|
pad_modulo=32,
|
|
)
|
|
|
|
assert encoded.source_samples == SAMPLES_PER_LATENT + 1
|
|
assert encoded.padded_samples == SAMPLES_PER_LATENT * 2
|
|
assert encoded.latents.shape == (2, 512, 2)
|
|
assert encoded.valid_latent_lengths == (1, 2)
|
|
np.testing.assert_array_equal(
|
|
np.asarray(encoded.padding_mask),
|
|
np.array([[True, False], [True, True]]),
|
|
)
|
|
|
|
|
|
def test_chunked_encoding_matches_unchunked_for_even_and_odd_overlaps():
|
|
rng = np.random.default_rng(21)
|
|
audio = mx.array(
|
|
rng.standard_normal((2, 2, SAMPLES_PER_LATENT * 11)).astype(np.float32)
|
|
)
|
|
encoder = GroupingEncoder()
|
|
|
|
unchunked = encode_audio(encoder, audio, pad_modulo=16)
|
|
for overlap in (2, 1):
|
|
chunked = encode_audio(
|
|
encoder,
|
|
audio,
|
|
pad_modulo=16,
|
|
chunked=True,
|
|
chunk_size=4,
|
|
overlap=overlap,
|
|
chunk_batch_size=2,
|
|
)
|
|
|
|
np.testing.assert_allclose(
|
|
np.asarray(chunked.latents),
|
|
np.asarray(unchunked.latents),
|
|
atol=1e-6,
|
|
)
|
|
np.testing.assert_array_equal(
|
|
np.asarray(chunked.padding_mask),
|
|
np.asarray(unchunked.padding_mask),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("kwargs", "message"),
|
|
[
|
|
({"valid_sample_lengths": [1, 2]}, "one value per audio batch item"),
|
|
({"pad_modulo": 17}, "positive multiple of encoder_stride"),
|
|
(
|
|
{"chunked": True, "chunk_size": 3, "overlap": 1, "pad_modulo": 32},
|
|
"incompatible with pad_modulo",
|
|
),
|
|
],
|
|
)
|
|
def test_encode_audio_rejects_invalid_contracts(kwargs, message):
|
|
audio = mx.zeros((1, 2, SAMPLES_PER_LATENT * 4))
|
|
|
|
with pytest.raises(ValueError, match=message):
|
|
encode_audio(GroupingEncoder(), audio, **kwargs)
|