161 lines
4.8 KiB
Go
161 lines
4.8 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package speech
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type recordingHardware struct {
|
|
writes []string
|
|
cancels []string
|
|
closed int
|
|
fail error
|
|
}
|
|
|
|
func (p *recordingHardware) Write(data []byte, canceled func() bool) error {
|
|
if canceled() {
|
|
return errHardwareCanceled
|
|
}
|
|
if p.fail != nil {
|
|
return p.fail
|
|
}
|
|
p.writes = append(p.writes, string(data))
|
|
return nil
|
|
}
|
|
func (p *recordingHardware) Cancel(data []byte) error {
|
|
p.cancels = append(p.cancels, string(data))
|
|
return nil
|
|
}
|
|
func (p *recordingHardware) Close() { p.closed++ }
|
|
|
|
func TestHardwareProtocolsMatchPythonFixtures(t *testing.T) {
|
|
for _, id := range []string{"dectalk", "dectalk-usb", "litetalk", "doubletalk", "tripletalk"} {
|
|
t.Run(id, func(t *testing.T) {
|
|
port := &recordingHardware{}
|
|
client := new_hardware_client(id, Options{}, port)
|
|
parameters := Parameters{Rate: 100, Pitch: -100, Volume: 0}
|
|
if err := client.Configure(parameters); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Configure(parameters); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Say("Hello\nworld ☃ [:dv g5 10]\x01"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []string{"\x019S", "\x010P", "\x014V", "Hello world [:dv g5 10] \r"}
|
|
cancel := "\x18"
|
|
if strings.HasPrefix(id, "dectalk") {
|
|
want = []string{"[:ra 650]", "[:dv ap 50]", "[:dv g5 73]", "Hello world [ :dv g5 10] \x0b"}
|
|
cancel = "\x03"
|
|
if id == "dectalk-usb" {
|
|
cancel = "\x06"
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(port.writes, want) {
|
|
t.Fatalf("got %q, want %q", port.writes, want)
|
|
}
|
|
if err := client.Cancel(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A flush can discard partially queued settings; the next utterance
|
|
// must reapply them even when their values have not changed.
|
|
port.writes = nil
|
|
if err := client.Say("fresh"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(port.writes) != 4 || !reflect.DeepEqual(port.writes[:3], want[:3]) {
|
|
t.Fatal(port.writes)
|
|
}
|
|
client.Close()
|
|
client.Close()
|
|
if port.closed != 1 || !reflect.DeepEqual(port.cancels, []string{cancel, cancel}) {
|
|
t.Fatal(port)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHardwareRangesAndNativeMidpoints(t *testing.T) {
|
|
for _, test := range []struct {
|
|
value int
|
|
dectalk, lite [3]string
|
|
}{
|
|
{-100, [3]string{"[:ra 75]", "[:dv ap 50]", "[:dv g5 60]"}, [3]string{"\x010S", "\x010P", "\x010V"}},
|
|
{0, [3]string{"[:ra 180]", "[:dv ap 122]", "[:dv g5 73]"}, [3]string{"\x014S", "\x0150P", "\x014V"}},
|
|
{100, [3]string{"[:ra 650]", "[:dv ap 350]", "[:dv g5 86]"}, [3]string{"\x019S", "\x0199P", "\x019V"}},
|
|
} {
|
|
for _, dectalk := range []bool{false, true} {
|
|
client := HardwareClient{dectalk: dectalk, parameters: Parameters{Rate: test.value, Pitch: test.value, Volume: test.value}}
|
|
want := test.lite
|
|
if dectalk {
|
|
want = test.dectalk
|
|
}
|
|
if got := client.commands(); got != want {
|
|
t.Fatal(test.value, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHardwareFailedSettingsAreRetriedAndPitchRestores(t *testing.T) {
|
|
port := &recordingHardware{fail: errors.New("disconnected")}
|
|
client := new_hardware_client("dectalk", Options{}, port)
|
|
if err := client.Configure(Parameters{}); err == nil {
|
|
t.Fatal("failed write accepted")
|
|
}
|
|
port.fail = nil
|
|
if err := client.Configure(Parameters{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Set_pitch(100); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Say("A"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := client.Set_pitch(0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []string{"[:ra 180]", "[:dv ap 122]", "[:dv g5 73]", "[:dv ap 350]", "A\x0b", "[:dv ap 122]"}
|
|
if !reflect.DeepEqual(port.writes, want) {
|
|
t.Fatal(port.writes)
|
|
}
|
|
if err := client.Configure(Parameters{Rate: 101}); err == nil {
|
|
t.Fatal("invalid rate accepted")
|
|
}
|
|
if err := client.Set_pitch(-101); err == nil {
|
|
t.Fatal("invalid pitch accepted")
|
|
}
|
|
}
|
|
|
|
func TestHardwareValidationDoesNotOpenDevices(t *testing.T) {
|
|
for _, id := range []string{"dectalk", "litetalk", "doubletalk", "tripletalk"} {
|
|
options := Options{Hardware: HardwareOptions{Device: "/dev/fixture-synth"}}
|
|
if err := Validate_driver_options(id, options); err != nil {
|
|
t.Fatal(id, err)
|
|
}
|
|
for _, path := range []string{"", "auto", "ttyUSB0", "/dev/ttyUSB0\n"} {
|
|
options.Hardware.Device = path
|
|
if err := Validate_driver_options(id, options); err == nil {
|
|
t.Fatal(id, path)
|
|
}
|
|
}
|
|
options.Hardware = HardwareOptions{Device: "/dev/fixture-synth", BaudRate: 12345}
|
|
if err := Validate_driver_options(id, options); err == nil {
|
|
t.Fatal("unsupported baud accepted")
|
|
}
|
|
}
|
|
if err := Validate_driver_options("dectalk-usb", Options{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Validate_driver_options("dectalk-usb", Options{Voice: "invented"}); err == nil {
|
|
t.Fatal("hardware voice accepted")
|
|
}
|
|
}
|