package audio import ( "bytes" "encoding/binary" "io" "math" "os" "time" ebiaudio "github.com/hajimehoshi/ebiten/v2/audio" "github.com/hajimehoshi/ebiten/v2/audio/wav" ) const sampleRate = 44100 type Manager struct { context *ebiaudio.Context players []*ebiaudio.Player current *ebiaudio.Player } func NewManager() *Manager { return &Manager{context: ebiaudio.NewContext(sampleRate)} } func (m *Manager) Update() { alive := m.players[:0] for _, p := range m.players { if p.IsPlaying() { alive = append(alive, p) continue } _ = p.Close() } m.players = alive if m.current != nil && !m.current.IsPlaying() { _ = m.current.Close() m.current = nil } } func (m *Manager) PlayTone(freq float64, d time.Duration, volume float64) { m.playPCM(generateTone(freq, d, volume), false) } func (m *Manager) PlayToneInterrupt(freq float64, d time.Duration, volume float64) { m.playPCM(generateTone(freq, d, volume), true) } func (m *Manager) PlayBeep() { m.PlayTone(880, 80*time.Millisecond, 0.22) } func (m *Manager) PlayError() { data := append(generateTone(160, 110*time.Millisecond, 0.28), generateTone(110, 150*time.Millisecond, 0.28)...) m.playPCM(data, false) } func (m *Manager) PlayWAV(path string, interrupt bool) bool { f, err := os.Open(path) if err != nil { return false } defer f.Close() stream, err := wav.DecodeWithSampleRate(sampleRate, f) if err != nil { return false } data, err := io.ReadAll(stream) if err != nil { return false } m.playPCM(data, interrupt) return true } func (m *Manager) StopCurrent() { if m.current == nil { return } m.current.Pause() _ = m.current.Close() m.current = nil } func (m *Manager) playPCM(data []byte, interrupt bool) { if len(data) == 0 { return } if interrupt { m.StopCurrent() } player := m.context.NewPlayerFromBytes(data) player.Play() if interrupt { m.current = player return } m.players = append(m.players, player) } func generateTone(freq float64, d time.Duration, volume float64) []byte { samples := int(float64(sampleRate) * d.Seconds()) buf := bytes.NewBuffer(make([]byte, 0, samples*4)) for i := 0; i < samples; i++ { t := float64(i) / sampleRate env := envelope(i, samples) v := int16(math.Sin(2*math.Pi*freq*t) * volume * env * math.MaxInt16) _ = binary.Write(buf, binary.LittleEndian, v) _ = binary.Write(buf, binary.LittleEndian, v) } return buf.Bytes() } func envelope(i, samples int) float64 { if samples <= 0 { return 0 } attack := sampleRate / 200 release := sampleRate / 100 if i < attack { return float64(i) / float64(attack) } if samples-i < release { return float64(samples-i) / float64(release) } return 1 }