// Fenrir console screen reader. // Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package sound /* #cgo pkg-config: alsa #include #include 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 }