125 lines
2.6 KiB
Go
125 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"time"
|
|
)
|
|
|
|
type keywordDetector struct {
|
|
cmd *exec.Cmd
|
|
input io.WriteCloser
|
|
output *os.File
|
|
packet [1 + hop*2]byte
|
|
}
|
|
|
|
func startKeywordDetector(helper, model string) (*keywordDetector, error) {
|
|
cmd := exec.Command(helper, model)
|
|
cmd.Stderr = os.Stderr
|
|
terminateWithClient(cmd)
|
|
input, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
output, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
input.Close()
|
|
return nil, err
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
input.Close()
|
|
output.Close()
|
|
return nil, err
|
|
}
|
|
detector := &keywordDetector{cmd: cmd, input: input, output: output.(*os.File)}
|
|
if _, err := detector.result(5 * time.Second); err != nil {
|
|
detector.close()
|
|
return nil, err
|
|
}
|
|
return detector, nil
|
|
}
|
|
|
|
func (d *keywordDetector) result(timeout time.Duration) (bool, error) {
|
|
if err := d.output.SetReadDeadline(time.Now().Add(timeout)); err != nil {
|
|
return false, err
|
|
}
|
|
var result [1]byte
|
|
if _, err := io.ReadFull(d.output, result[:]); err != nil {
|
|
return false, err
|
|
}
|
|
if result[0] > 1 {
|
|
return false, errors.New("invalid keyword detector response")
|
|
}
|
|
return result[0] == 1, nil
|
|
}
|
|
|
|
func (d *keywordDetector) process(samples []int16) (bool, error) {
|
|
d.packet[0] = 1
|
|
for i, sample := range samples {
|
|
binary.LittleEndian.PutUint16(d.packet[1+i*2:], uint16(sample))
|
|
}
|
|
if _, err := d.input.Write(d.packet[:]); err != nil {
|
|
return false, err
|
|
}
|
|
return d.result(time.Second)
|
|
}
|
|
|
|
func (d *keywordDetector) reset() error {
|
|
if _, err := d.input.Write([]byte{0}); err != nil {
|
|
return err
|
|
}
|
|
_, err := d.result(time.Second)
|
|
return err
|
|
}
|
|
|
|
func (d *keywordDetector) close() {
|
|
d.input.Close()
|
|
d.cmd.Process.Kill()
|
|
d.cmd.Wait()
|
|
}
|
|
|
|
func runKeywordWake(live *liveReader, detector *keywordDetector, activate func() error, once bool) error {
|
|
log.Print("Listening locally for the Алиса keyword model")
|
|
raw := make([]byte, captureBlock)
|
|
muted := false
|
|
for {
|
|
if _, err := io.ReadFull(live, raw); err != nil {
|
|
return err
|
|
}
|
|
if !unmuted() {
|
|
if !muted {
|
|
if err := detector.reset(); err != nil {
|
|
return err
|
|
}
|
|
muted = true
|
|
}
|
|
continue
|
|
}
|
|
muted = false
|
|
detected, err := detector.process(downsample(raw))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !detected {
|
|
continue
|
|
}
|
|
log.Print("wake word detected")
|
|
if err := activate(); err != nil {
|
|
log.Printf("session: %v", err)
|
|
if !errors.Is(err, errMuted) && !errors.Is(err, errNoSpeech) {
|
|
lights.flashError()
|
|
}
|
|
} else if once {
|
|
return nil
|
|
}
|
|
live.discardPending()
|
|
if err := detector.reset(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|