package v01 import ( "bytes" "fmt" "io" "log" "net/http" "net/url" "sync" "time" "github.com/faiface/beep" "github.com/faiface/beep/effects" "github.com/faiface/beep/speaker" "github.com/faiface/beep/wav" ) var ( ttsLock = &sync.RWMutex{} ) func (v01 *V01) tts(text string) { if err := v01._tts(text); err != nil { log.Printf("failed to tts: %s: %v", text, err) } } func (v01 *V01) _tts(text string) error { if v01.cfg.Feedback.TTSURL == "" { return nil } url, err := url.Parse(v01.cfg.Feedback.TTSURL) if err != nil { return err } if len(url.Path) < 2 { url.Path = "/api/tts" } q := url.Query() if q.Get("voice") == "" { q.Set("voice", "en-us/glados-glow_tts") } if q.Get("lengthScale") == "" { q.Set("lengthScale", "1") } q.Set("text", text) url.RawQuery = q.Encode() resp, err := http.Get(url.String()) if err != nil { return err } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK || resp.Header.Get("Content-Type") != "audio/wav" { return fmt.Errorf("failed to call ttsurl: (%d) %s", resp.StatusCode, b) } decoder, format, err := wav.Decode(bytes.NewReader(b)) if err != nil { return err } ttsLock.Lock() defer ttsLock.Unlock() speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30)) speaker.Play(&effects.Volume{Streamer: beep.ResampleRatio(4, 1, &beep.Ctrl{Streamer: beep.Loop(1, decoder)})}) duration := time.Duration(decoder.Len()) * format.SampleRate.D(1) select { case <-v01.ctx.Done(): case <-time.After(duration): } return nil }