138 lines
3.3 KiB
Go
138 lines
3.3 KiB
Go
package tts
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/kami/maven/internal/audio"
|
|
"github.com/kami/maven/internal/worker"
|
|
)
|
|
|
|
type fakeSynthesizer struct{}
|
|
|
|
func (fakeSynthesizer) Synthesize(_ context.Context, req worker.SynthesizeReq) (worker.SynthesizeResp, error) {
|
|
if req.Text == "" {
|
|
return worker.SynthesizeResp{}, nil
|
|
}
|
|
pcm := make([]byte, 3200)
|
|
for i := 0; i < len(pcm)/2; i++ {
|
|
v := int16(i)
|
|
pcm[i*2] = byte(v)
|
|
pcm[i*2+1] = byte(v >> 8)
|
|
}
|
|
return worker.SynthesizeResp{Audio: audio.Audio{Format: audio.PCM16kMono, Bytes: pcm}}, nil
|
|
}
|
|
|
|
func TestStubProducesValidAudio(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewStub()
|
|
a, err := s.Synthesize(context.Background(), "hi")
|
|
if err != nil {
|
|
t.Fatalf("Stub.Synthesize: %v", err)
|
|
}
|
|
if !a.Format.IsValid() {
|
|
t.Fatalf("format invalid: %+v", a.Format)
|
|
}
|
|
// 200ms @ 16k ⇒ 3200 samples ⇒ 6400 bytes.
|
|
if len(a.Bytes) != 6400 {
|
|
t.Fatalf("bytes len: %d, want 6400", len(a.Bytes))
|
|
}
|
|
// non-empty audio should not be all zeros (sine wave).
|
|
allZero := true
|
|
for _, b := range a.Bytes {
|
|
if b != 0 {
|
|
allZero = false
|
|
break
|
|
}
|
|
}
|
|
if allZero {
|
|
t.Fatalf("Stub audio is all zeros; should be a tone")
|
|
}
|
|
if a.Duration() < 0.19 || a.Duration() > 0.21 {
|
|
t.Fatalf("Duration: got %v, want ~0.2s", a.Duration())
|
|
}
|
|
}
|
|
|
|
func TestStubDifferentTextDifferentFreq(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewStub()
|
|
a, _ := s.Synthesize(context.Background(), "a") // 'a' = 97
|
|
b, _ := s.Synthesize(context.Background(), "z") // 'z' = 122
|
|
// different first byte ⇒ different modulo ⇒ different tones; bytes differ.
|
|
if samePCM(a.Bytes, b.Bytes) {
|
|
t.Fatalf("expected different tones for 'a' vs 'z'")
|
|
}
|
|
}
|
|
|
|
func TestStubEmptyTextStillTones(t *testing.T) {
|
|
t.Parallel()
|
|
s := NewStub()
|
|
a, err := s.Synthesize(context.Background(), "")
|
|
if err != nil {
|
|
t.Fatalf("Stub.Synthesize: %v", err)
|
|
}
|
|
if len(a.Bytes) == 0 {
|
|
t.Fatalf("empty text should still produce tone (audible), got silence")
|
|
}
|
|
}
|
|
|
|
func TestRemoteForwardsToClient(t *testing.T) {
|
|
t.Parallel()
|
|
srv := worker.NewSynthesizerServer(t.TempDir()+"/tts.sock", fakeSynthesizer{})
|
|
if err := srv.Listen(); err != nil {
|
|
t.Fatalf("listen: %v", err)
|
|
}
|
|
defer srv.Close()
|
|
go srv.Serve()
|
|
|
|
c := worker.Dial(srv.Path())
|
|
defer c.Close()
|
|
r := NewRemote(c, "ru", "")
|
|
|
|
out, err := r.Synthesize(context.Background(), "hello")
|
|
if err != nil {
|
|
t.Fatalf("Remote.Synthesize: %v", err)
|
|
}
|
|
if !out.Format.IsValid() {
|
|
t.Fatalf("invalid format: %+v", out.Format)
|
|
}
|
|
if len(out.Bytes) != 3200 {
|
|
t.Fatalf("bytes len: %d, want 3200", len(out.Bytes))
|
|
}
|
|
}
|
|
|
|
func TestRemoteErrorWraps(t *testing.T) {
|
|
t.Parallel()
|
|
// a transcriber-only server ⇒ ErrUnknownMethod on Synthesize.
|
|
srv := worker.NewServer(t.TempDir()+"/tts.sock", &fakeErrTranscriber{})
|
|
if err := srv.Listen(); err != nil {
|
|
t.Fatalf("listen: %v", err)
|
|
}
|
|
defer srv.Close()
|
|
go srv.Serve()
|
|
c := worker.Dial(srv.Path())
|
|
defer c.Close()
|
|
r := NewRemote(c, "ru", "")
|
|
_, err := r.Synthesize(context.Background(), "hello")
|
|
if err == nil {
|
|
t.Fatalf("want error, got nil")
|
|
}
|
|
}
|
|
|
|
type fakeErrTranscriber struct{}
|
|
|
|
func (fakeErrTranscriber) Transcribe(context.Context, worker.TranscribeReq) (worker.TranscribeResp, error) {
|
|
return worker.TranscribeResp{}, nil
|
|
}
|
|
|
|
func samePCM(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} |