// Package voice provides speech-to-text and text-to-speech capabilities. // It defines provider interfaces allowing pluggable backends (DashScope, Whisper, Edge-TTS, espeak). package voice import "context" // STTProvider converts speech audio to text. type STTProvider interface { // Name returns the provider identifier. Name() string // "dashscope", "whisper-local" // Recognize converts audio data to text. Recognize(ctx context.Context, audioData []byte) (string, error) } // TTSProvider converts text to speech audio. type TTSProvider interface { // Name returns the provider identifier. Name() string // "edge-tts", "espeak", "silent" // Synthesize converts text to audio bytes. Synthesize(ctx context.Context, text string, voice string) ([]byte, error) } // Service manages STT and TTS providers with fallback chains. type Service struct { sttProvider STTProvider ttsProviders []TTSProvider // Ordered by priority (first = preferred) } // Config holds voice service configuration. type Config struct { STTProvider string // "dashscope" | "whisper-local" TTSProviders []string // ["edge-tts", "espeak", "silent"] ordered by priority DashScopeKey string } // New creates a new voice service. func New(cfg Config) *Service { svc := &Service{} // STT: for now, use a simple placeholder if cfg.STTProvider == "dashscope" && cfg.DashScopeKey != "" { svc.sttProvider = &dashscopeSTT{apiKey: cfg.DashScopeKey} } else { svc.sttProvider = &silentSTT{} } // TTS: build fallback chain for _, name := range cfg.TTSProviders { switch name { case "edge-tts": svc.ttsProviders = append(svc.ttsProviders, &edgeTTS{}) case "espeak": svc.ttsProviders = append(svc.ttsProviders, &espeakTTS{}) case "silent": svc.ttsProviders = append(svc.ttsProviders, &silentTTS{}) } } if len(svc.ttsProviders) == 0 { svc.ttsProviders = append(svc.ttsProviders, &silentTTS{}) } return svc } // Recognize performs speech recognition with the configured STT provider. func (s *Service) Recognize(ctx context.Context, audioData []byte) (string, error) { return s.sttProvider.Recognize(ctx, audioData) } // Synthesize performs TTS with automatic fallback through the provider chain. func (s *Service) Synthesize(ctx context.Context, text string, voice string) ([]byte, error) { var lastErr error for _, p := range s.ttsProviders { audio, err := p.Synthesize(ctx, text, voice) if err == nil { return audio, nil } lastErr = err } return nil, lastErr } // --- Provider stubs --- type dashscopeSTT struct{ apiKey string } func (p *dashscopeSTT) Name() string { return "dashscope" } func (p *dashscopeSTT) Recognize(_ context.Context, _ []byte) (string, error) { return "", nil // Placeholder: real implementation requires DashScope SDK } type silentSTT struct{} func (p *silentSTT) Name() string { return "silent" } func (p *silentSTT) Recognize(_ context.Context, _ []byte) (string, error) { return "", nil } type edgeTTS struct{} func (p *edgeTTS) Name() string { return "edge-tts" } func (p *edgeTTS) Synthesize(_ context.Context, text string, _ string) ([]byte, error) { return []byte(text), nil // Placeholder } type espeakTTS struct{} func (p *espeakTTS) Name() string { return "espeak" } func (p *espeakTTS) Synthesize(_ context.Context, text string, _ string) ([]byte, error) { return []byte(text), nil } type silentTTS struct{} func (p *silentTTS) Name() string { return "silent" } func (p *silentTTS) Synthesize(_ context.Context, _ string, _ string) ([]byte, error) { return make([]byte, 0), nil }