package main
import (
"io"
"log"
"os"
"sync"
"unsafe"
"github.com/xlab/portaudio-go/portaudio"
"github.com/youpy/go-wav"
)
const (
samplesPerChannel = 2048
)
func main() {
if err := portaudio.Initialize(); paError(err) {
log.Fatalln("PortAudio init error:", paErrorText(err))
}
defer func() {
if err := portaudio.Terminate(); paError(err) {
log.Println("PortAudio term error:", paErrorText(err))
}
}()
play("./t.wav")
play("./t.wav")
}
func play(filePath string) {
file, err := os.Open(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader := wav.NewReader(file)
info, err := reader.Format()
if err != nil {
log.Fatal(err)
}
samplesOut := make(chan [][]float32)
go func() {
var frame [][]float32
for {
samples, err := reader.ReadSamples()
if err == io.EOF {
samplesOut <- frame
close(samplesOut)
break
}
for _, sample := range samples {
frame = append(frame, []float32{float32(reader.FloatValue(sample, 0))})
if len(frame)%samplesPerChannel == 0 {
samplesOut <- frame
frame = [][]float32{}
}
}
}
}()
var wg sync.WaitGroup
var stream *portaudio.Stream
callback := paCallback(&wg, int(info.NumChannels), samplesOut)
if err := portaudio.OpenDefaultStream(&stream, 0, int32(info.NumChannels), portaudio.PaFloat32, float64(info.SampleRate), samplesPerChannel, callback, nil); paError(err) {
log.Fatalln("PortAudio error:", paErrorText(err))
}
defer func() {
if err := portaudio.CloseStream(stream); paError(err) {
log.Println("[WARN] PortAudio error:", paErrorText(err))
}
}()
if err := portaudio.StartStream(stream); paError(err) {
log.Fatalln("PortAudio error:", paErrorText(err))
}
defer func() {
if err := portaudio.StopStream(stream); paError(err) {
log.Fatalln("[WARN] PortAudio error:", paErrorText(err))
}
}()
wg.Wait()
}
func paError(err portaudio.Error) bool {
return portaudio.ErrorCode(err) != portaudio.PaNoError
}
func paErrorText(err portaudio.Error) string {
return portaudio.GetErrorText(err)
}
func paCallback(wg *sync.WaitGroup, channels int, samples <-chan [][]float32) portaudio.StreamCallback {
wg.Add(1)
return func(_ unsafe.Pointer, output unsafe.Pointer, sampleCount uint, _ *portaudio.StreamCallbackTimeInfo, _ portaudio.StreamCallbackFlags, _ unsafe.Pointer) int32 {
const (
statusContinue = int32(portaudio.PaContinue)
statusComplete = int32(portaudio.PaComplete)
)
frame, ok := <-samples
if !ok {
wg.Done()
return statusComplete
}
if len(frame) > int(sampleCount) {
frame = frame[:sampleCount]
}
var idx int
out := (*(*[1 << 32]float32)(unsafe.Pointer(output)))[:int(sampleCount)*channels]
for _, sample := range frame {
if len(sample) > channels {
sample = sample[:channels]
}
for i := range sample {
out[idx] = sample[i]
idx++
}
}
return statusContinue
}
}