Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,98 @@
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user