/* * Copyright (C) 2021 Arsen Musayelyan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package main import ( "bytes" _ "embed" "encoding/binary" "errors" ds "github.com/asticode/go-astideepspeech" "github.com/gen2brain/malgo" "github.com/youpy/go-wav" "io" "sync" ) // Safe stream using mutex to fix race conditions type SafeStream struct { sync.Mutex *ds.Stream } // Convert raw audio to slice of int16 func convToInt16Slice(r io.Reader) ([]int16, error) { // Create nil output array var out []int16 for { var sample int16 // Attempt to read little endian binary from sample into int16 err := binary.Read(r, binary.LittleEndian, &sample) switch { case errors.Is(err, io.EOF): // If error is EOF, return output with no error return out, nil case err != nil: // If error is something other than EOF, return error return nil, err } // If sample contains audio if sample != 0 { // Add it to output out = append(out, sample) } } } //go:embed activate.wav var activationTone []byte // Play activation tone to audio device func playActivationTone(ctx *malgo.AllocatedContext) error { // Create new reader for embedded activation tone buf := bytes.NewReader(activationTone) // Create new wav reader for activation tone reader wavReader := wav.NewReader(buf) // Get wav file format format, err := wavReader.Format() if err != nil { return err } // Set device configuration options deviceConfig := malgo.DefaultDeviceConfig(malgo.Playback) deviceConfig.Playback.Format = malgo.FormatS16 deviceConfig.Playback.Channels = uint32(format.NumChannels) deviceConfig.SampleRate = format.SampleRate deviceConfig.Alsa.NoMMap = 1 // Create new channel waiting for completion done := make(chan bool) doneVar := false onSamples := func(output, _ []byte, _ uint32) { // Read as much audio into output as will fit n, err := io.ReadFull(wavReader, output) // If error occurred or no bytes read if !doneVar && (err != nil || n == 0) { if *verbose { log.Debug().Msg("Sample output complete") } doneVar = true // Signal completion done <- true } } // Initialize audio device using configuration device, err := malgo.InitDevice(ctx.Context, deviceConfig, malgo.DeviceCallbacks{ Data: onSamples, }) if err != nil { return err } // Uninitialize at end of function defer device.Uninit() // Start audio device err = device.Start() if err != nil { return err } // Wait for completion signal <-done return nil }