package game import ( "bytes" "fmt" "io" "math" "github.com/hajimehoshi/ebiten/v2/audio" "github.com/hajimehoshi/ebiten/v2/audio/wav" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/res" ) // Audio spelar korta effekter. nil-säkert: utan ljud (−mute eller fel) är // alla anrop no-ops. type Audio struct { ctx *audio.Context clips map[string][]byte // avkodad PCM (16-bit stereo LE) per klipp loop *audio.Player // pågående bakgrundsloop (bakgrundsprat) } const sampleRate = 44100 // Ljudklipp (filnamn utan .wav i resources/sound). const ( SoundApproach = "melody-approach" SoundDoors = "door-chime" SoundChatter = "chatter" ) // AudibleTiles är hur många rutor bort man hör perrongens ljud. const AudibleTiles = 12.0 // newAudio laddar klippen. Returnerar nil om ljudet är avstängt. func newAudio(mute bool) (*Audio, error) { if mute { return nil, nil } a := &Audio{ctx: audio.NewContext(sampleRate), clips: map[string][]byte{}} for _, name := range []string{SoundApproach, SoundDoors, SoundChatter} { b, err := res.ReadFile("sound", name+".wav") if err != nil { return nil, err } stream, err := wav.DecodeWithoutResampling(bytes.NewReader(b)) if err != nil { return nil, fmt.Errorf("ljud %s: %w", name, err) } pcm, err := io.ReadAll(stream) if err != nil { return nil, err } a.clips[name] = pcm } return a, nil } // Play spelar ett klipp med volym 0..1. func (a *Audio) Play(name string, volume float64) { if a == nil || volume <= 0 { return } pcm, ok := a.clips[name] if !ok { return } p := a.ctx.NewPlayerFromBytes(pcm) p.SetVolume(math.Min(1, volume)) p.Play() } // Loop startar (eller byter) bakgrundsloopen med given volym; 0 stoppar. func (a *Audio) Loop(name string, volume float64) { if a == nil { return } a.StopLoop() pcm, ok := a.clips[name] if !ok || volume <= 0 { return } stream := audio.NewInfiniteLoop(bytes.NewReader(pcm), int64(len(pcm))) p, err := a.ctx.NewPlayer(stream) if err != nil { return } p.SetVolume(math.Min(1, volume)) p.Play() a.loop = p } // StopLoop stoppar bakgrundsloopen. func (a *Audio) StopLoop() { if a == nil || a.loop == nil { return } a.loop.Close() a.loop = nil } // volumeAt ger volym efter avstånd i rutor. func volumeAt(dist float64) float64 { return math.Max(0, 1-dist/AudibleTiles) }