//go:build linux package audio import ( "fmt" "os" "os/exec" "path/filepath" ) // pwEngine plays pre-rendered WAVs by spawning pw-play per effect type pwEngine struct { dir string files [EffectCount]string } // NewEngine synthesizes effect WAVs into a temp dir. Falls back to a silent // engine when pw-play is unavailable — game remains playable without audio func NewEngine() Engine { if _, err := exec.LookPath("pw-play"); err != nil { return nullEngine{} } dir, err := os.MkdirTemp("", "symph-sfx-") if err != nil { return nullEngine{} } e := &pwEngine{dir: dir} for fx := EffectNone + 1; fx < EffectCount; fx++ { path := filepath.Join(dir, fmt.Sprintf("fx-%d.wav", fx)) if err := os.WriteFile(path, wavBytes(synth(effectSegs[fx])), 0o644); err != nil { os.RemoveAll(dir) return nullEngine{} } e.files[fx] = path } return e } // Play spawns pw-play detached; process reaped asynchronously func (e *pwEngine) Play(fx Effect) { if fx == EffectNone || fx >= EffectCount || e.files[fx] == "" { return } cmd := exec.Command("pw-play", "--volume", "0.7", e.files[fx]) if err := cmd.Start(); err == nil { go cmd.Wait() } } func (e *pwEngine) Close() { os.RemoveAll(e.dir) }