trident/audio.go

122 lines
3.1 KiB
Go

/*
* 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 <https://www.gnu.org/licenses/>.
*/
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
}