131 lines
2.6 KiB
Go
131 lines
2.6 KiB
Go
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
|
|
}
|