Files

99 lines
2.9 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package sound
import (
"context"
"encoding/binary"
"log/slog"
"testing"
"time"
)
func TestPositionTonesDecodeAndRiseInPitch(t *testing.T) {
previousCrossings := 0
for _, position := range []float64{0, 0.5, 1} {
data, err := soundData("default", PositionCueName(position))
if err != nil {
t.Fatal(err)
}
if binary.LittleEndian.Uint32(data[24:]) != 22050 || binary.LittleEndian.Uint16(data[22:]) != 1 || len(data) != 44+1323*2 {
t.Fatal("position tone is not 60 ms mono PCM at 22050 Hz")
}
crossings := 0
previousSample := int16(0)
for offset := 44; offset < len(data); offset += 2 {
sample := int16(binary.LittleEndian.Uint16(data[offset:]))
if previousSample < 0 && sample >= 0 {
crossings++
}
previousSample = sample
}
if crossings <= previousCrossings {
t.Fatalf("pitch failed to rise at %g: %d cycles", position, crossings)
}
previousCrossings = crossings
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
err = (NativePlayer{Sink: "test"}).Play(ctx, data, 0.3, -0.5)
cancel()
if err != nil {
t.Fatal(err)
}
}
for _, value := range []string{"NaN", "+Inf", "-1", "1.1", "bad"} {
if _, err := soundData("default", positionCuePrefix+value); err == nil {
t.Fatalf("accepted position %s", value)
}
}
}
func TestPositionSoundUsesObjectSoundPolicy(t *testing.T) {
player := &configurationPlayer{played: make(chan configuredPlayback, 8)}
service := New(slog.New(slog.DiscardHandler), player, "default", 0.3)
if err := service.Start(context.Background()); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := service.Stop(context.Background()); err != nil {
t.Error(err)
}
})
cue := Cue{Name: PositionCueName(0.5), Pan: -0.5}
if !service.Enqueue(cue) {
t.Fatal("position cue was rejected")
}
select {
case playback := <-player.played:
if playback.volume != 0.3 || playback.pan != -0.5 {
t.Fatalf("position playback = %+v", playback)
}
case <-time.After(time.Second):
t.Fatal("position tone did not reach player")
}
service.mutex.Lock()
cached := len(service.cache)
service.mutex.Unlock()
if cached != 0 {
t.Fatal("continuous position values populated the sound cache")
}
for _, configuration := range []Configuration{
{Enabled: false, Theme: "default", Volume: 0.3},
{Enabled: true, Theme: "none", Volume: 0.3},
{Enabled: true, Theme: "default", Volume: 0},
} {
service.Configure(configuration)
if service.Available(cue.Name) || service.Enqueue(cue) {
t.Fatalf("position ignored policy: %+v", configuration)
}
if !service.Available(ProgressCueName(50)) {
t.Fatal("position policy affected progress tones")
}
}
service.Configure(Configuration{Enabled: true, Theme: "custom", Volume: 0.3})
if !service.Available(cue.Name) {
t.Fatal("generated position requires a theme asset")
}
}