63 lines
1.8 KiB
Go
63 lines
1.8 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package sound
|
|
|
|
/*
|
|
#cgo pkg-config: alsa
|
|
#include <alsa/asoundlib.h>
|
|
#include <stdlib.h>
|
|
|
|
static int fenrir_pcm_open(snd_pcm_t **pcm, const char *device) {
|
|
int result = snd_pcm_open(pcm, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
|
if (result < 0) return result;
|
|
result = snd_pcm_set_params(*pcm, SND_PCM_FORMAT_FLOAT,
|
|
SND_PCM_ACCESS_RW_INTERLEAVED, 2, 48000, 1, 20000);
|
|
if (result < 0) {
|
|
snd_pcm_close(*pcm);
|
|
*pcm = NULL;
|
|
}
|
|
return result;
|
|
}
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
type alsaPCM struct{ handle *C.snd_pcm_t }
|
|
|
|
func open_alsa(s *Service, device string) error {
|
|
name := C.CString(device)
|
|
defer C.free(unsafe.Pointer(name))
|
|
pcm := &alsaPCM{}
|
|
if result := C.fenrir_pcm_open(&pcm.handle, name); result < 0 {
|
|
return fmt.Errorf("open ALSA PCM %q: %s", device, C.GoString(C.snd_strerror(result)))
|
|
}
|
|
s.start_pcm(pcm)
|
|
return nil
|
|
}
|
|
|
|
// ALSA receives interleaved native-endian stereo float samples. All PCM calls
|
|
// after setup belong to the playback worker; Close runs after that worker exits.
|
|
func (p *alsaPCM) write(samples []float32) (int, error) {
|
|
result := C.snd_pcm_writei(p.handle, unsafe.Pointer(&samples[0]), C.snd_pcm_uframes_t(len(samples)/2))
|
|
if result < 0 {
|
|
return 0, syscall.Errno(-result)
|
|
}
|
|
return int(result) * 2, nil
|
|
}
|
|
func (p *alsaPCM) prepare() error { return pcm_error(C.snd_pcm_prepare(p.handle)) }
|
|
func (p *alsaPCM) drain() error { return pcm_error(C.snd_pcm_drain(p.handle)) }
|
|
func (p *alsaPCM) close() { C.snd_pcm_drop(p.handle); C.snd_pcm_close(p.handle) }
|
|
func pcm_error(result C.int) error {
|
|
if result < 0 {
|
|
return syscall.Errno(-result)
|
|
}
|
|
return nil
|
|
}
|