Files
ngzz-mc/internal/audio/audio_test.go
2026-09-19 14:21:08 +08:00

155 lines
3.8 KiB
Go
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.
package audio
import (
"math"
"os"
"path/filepath"
"testing"
)
func TestAttenuation(t *testing.T) {
if a := attenuation(0, 16); a != 1 {
t.Fatalf("零距离衰减期望 1,实际 %v", a)
}
if a := attenuation(8, 16); a != 0.5 {
t.Fatalf("8 格衰减期望 0.5,实际 %v", a)
}
if a := attenuation(16, 16); a != 0 {
t.Fatalf("16 格衰减期望 0,实际 %v", a)
}
if a := attenuation(30, 16); a != 0 {
t.Fatalf("超距衰减期望 0,实际 %v", a)
}
}
func TestPlayParams(t *testing.T) {
if p := PlayParams("block.stone.hit"); p.Volume != 0.25 || p.Pitch != 0.5 {
t.Fatalf("hit 参数 %+v", p)
}
if p := PlayParams("block.stone.break"); p.Volume != 1 || p.Pitch != 0.8 {
t.Fatalf("break 参数 %+v", p)
}
if p := PlayParams("block.grass.step"); p.Volume != 0.15 || p.Pitch != 1 {
t.Fatalf("step 参数 %+v", p)
}
p := PlayParams("entity.sheep.hurt")
if p.Volume != 1 || p.Pitch < 0.8 || p.Pitch > 1.2 {
t.Fatalf("hurt 音调应在 0.8–1.2,实际 %+v", p)
}
}
func TestBurstStreamer(t *testing.T) {
b := sineBurst(440, 0.05)
samples := make([][2]float64, 1024)
total := 0
for {
n, ok := b.Stream(samples)
total += n
if ok {
break
}
}
if total != b.Len() {
t.Fatalf("流长度期望 %d,实际 %d", b.Len(), total)
}
if total < 1000 {
t.Fatalf("采样数异常: %d", total)
}
}
func TestManagerSilent(t *testing.T) {
m := New()
defer m.Close()
m.SetVolumes(1, 1, 1, 1)
m.SetListener(0, 0, 0, 0)
for i := 0; i < 100; i++ {
m.Play("block.stone.break", float64(i), 0, 0)
}
}
func TestLoadSoundsFields(t *testing.T) {
dir := t.TempDir()
ogg := filepath.Join(dir, "stone1.ogg")
if err := os.WriteFile(ogg, []byte("not-ogg"), 0o644); err != nil {
t.Fatal(err)
}
m := New()
defer m.Close()
js := []byte(`{
"block.stone.break":{"sounds":[{"name":"dig/stone1","weight":2}],"subtitle":"subtitles.block.generic.break"},
"block.alias":{"sounds":[{"name":"block.stone.break","type":"event"}]}
}`)
m.LoadSounds(js, func(name string) (string, bool) {
if name == "dig/stone1" {
return ogg, true
}
return "", false
})
if m.EventCount() != 2 {
t.Fatalf("事件数=%d", m.EventCount())
}
if !m.HasOgg("block.stone.break") || !m.HasOgg("block.alias") {
t.Fatal("应解析到 ogg 与 event 重定向")
}
if g := m.Subtitle("block.stone.break"); g != "subtitles.block.generic.break" {
t.Fatalf("subtitle=%q", g)
}
}
func TestLoadSoundsMissingOgg(t *testing.T) {
m := New()
defer m.Close()
js := []byte(`{"block.stone.break":{"sounds":["dig/stone1"]}}`)
m.LoadSounds(js, func(string) (string, bool) { return "", false })
if m.EventCount() != 0 {
t.Fatal("无 ogg 时事件表应为空(回退合成器)")
}
m.Play("block.grass.step", 0, 0, 0)
}
func TestCaptionsEnqueue(t *testing.T) {
m := New()
defer m.Close()
m.SetListener(0, 0, 0, 0)
m.Play("entity.sheep.ambient", 1, 0, 0)
m.Play("entity.sheep.ambient", 1, 0, 0)
m.Play("block.stone.break", 2, 0, 0)
m.Play("block.stone.break", 40, 0, 0) // 超距
got := m.Captions()
if len(got) != 2 {
t.Fatalf("字幕行数=%d %+v", len(got), got)
}
}
func TestStereoPan(t *testing.T) {
if p := stereoPan(0, 0, -5, 0); math.Abs(p) > 1e-9 {
t.Fatalf("正前方 pan=%v", p)
}
if p := stereoPan(0, 0, 0, 0); p != 0 {
t.Fatalf("听者自身 pan=%v", p)
}
if p := stereoPan(5, 0, 0, 0); p < 0.99 {
t.Fatalf("右侧 pan=%v", p)
}
if p := stereoPan(-5, 0, 0, 0); p > -0.99 {
t.Fatalf("左侧 pan=%v", p)
}
// yaw=0 朝 -Z,源在 +X 应右声道更大
l, r := (1-1.0)/2, (1+1.0)/2
src := sineBurst(440, 0.02)
p := &panStreamer{Streamer: src, l: l, r: r}
samples := make([][2]float64, 64)
n, _ := p.Stream(samples)
if n == 0 {
t.Fatal("应有采样")
}
var sl, sr float64
for i := 0; i < n; i++ {
sl += math.Abs(samples[i][0])
sr += math.Abs(samples[i][1])
}
if sr <= sl {
t.Fatalf("右侧声源应右声道更大 L=%v R=%v", sl, sr)
}
}