Parent directory

speech.go

1707 bytes
 1package server
 2
 3import (
 4	"context"
 5	"errors"
 6	"sync"
 7
 8	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/stt"
 9	"git.theedgeofrage.com/TheEdgeOfRage/kaiwari/internal/tts"
10)
11
12// remoteInput adapts the uploaded WAV of one spoken turn to game.SpeechInput.
13// Begin is a no-op; End transcribes the bytes stashed with SetWAV.
14type remoteInput struct {
15	asr *stt.ASRClient
16
17	mu  sync.Mutex
18	wav []byte
19}
20
21func (r *remoteInput) Begin(ctx context.Context) error { return nil }
22
23// SetWAV stashes the WAV uploaded for this turn.
24func (r *remoteInput) SetWAV(wav []byte) {
25	r.mu.Lock()
26	r.wav = wav
27	r.mu.Unlock()
28}
29
30func (r *remoteInput) End(ctx context.Context) (string, error) {
31	r.mu.Lock()
32	wav := r.wav
33	r.wav = nil
34	r.mu.Unlock()
35	if len(wav) == 0 {
36		return "", errors.New("speech: no audio uploaded")
37	}
38	raw, err := r.asr.TranscribeBytes(ctx, wav)
39	if err != nil {
40		return "", err
41	}
42	return raw, nil
43}
44
45// bufferedOutput adapts the TTS client to game.SpeechOutput without playback:
46// Speak synthesizes into memory and TakeLast drains the last clip.
47type bufferedOutput struct {
48	client *tts.Client
49
50	mu   sync.Mutex
51	last []byte
52}
53
54func (b *bufferedOutput) Speak(ctx context.Context, kana, gender string) error {
55	wav, err := b.client.Speech(ctx, kana, tts.VoiceForGender(gender))
56	if err != nil {
57		return err
58	}
59	b.mu.Lock()
60	cp := make([]byte, len(wav))
61	copy(cp, wav)
62	b.last = cp
63	b.mu.Unlock()
64	return nil
65}
66
67// TakeLast returns the last synthesized clip and clears it.
68func (b *bufferedOutput) TakeLast() []byte {
69	b.mu.Lock()
70	wav := b.last
71	b.last = nil
72	b.mu.Unlock()
73	return wav
74}
75
76// Replay is a no-op: replay is client-side on iOS.
77func (b *bufferedOutput) Replay() error { return nil }