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) } }