-
Notifications
You must be signed in to change notification settings - Fork 0
/
system.go
131 lines (108 loc) · 3.69 KB
/
system.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package sound
import (
"runtime"
"github.com/hajimehoshi/ebiten/v2/audio"
resource "github.com/quasilyte/ebitengine-resource"
)
type System struct {
loader *resource.Loader
audioContext *audio.Context
// This small bitset is used to track sounds with id<maxSoundMapID.
// These sounds will be "played" only once during a frame.
// Therefore, doing multiple PlaySound(id) during a single frame
// is more efficient.
soundMap soundMap
groupVolume [8]float64
globalVolume float64
}
type PlayOptions struct {
Volume float64
}
func (sys *System) Init(a *audio.Context, l *resource.Loader) {
sys.loader = l
sys.audioContext = a
sys.globalVolume = 1.0
for i := range sys.groupVolume {
sys.groupVolume[i] = 1.0
}
if runtime.GOOS != "android" {
// Audio player factory has lazy initialization that may lead
// to a ~0.2s delay before the first sound can be played.
// To avoid that delay, we force that factory to initialize
// right now, before the game is started.
dummy := sys.audioContext.NewPlayerFromBytes(nil)
dummy.Rewind()
}
}
func (sys *System) GetContext() *audio.Context {
return sys.audioContext
}
// Update adjusts the audio system state for the next tick of the game.
//
// It needs to be called somewhere near the beginning of [ebiten.Game.Update] method.
func (sys *System) Update() {
sys.soundMap.Reset()
}
// GetGlobalVolume reports the current global volume multiplier.
func (sys *System) GetGlobalVolume() float64 {
return sys.globalVolume
}
// SetGlobalVolume assigns an extra volume multiplier.
// It's used when computing the effective volume level.
// Setting it to 0.5 would make all sounds two times quiter.
//
// Imagine an average game that has separate volume controls (sfx, voice, music)
// plus "master volume" that would be applied on top of that.
// The global volume level is that multiplier.
func (sys *System) SetGlobalVolume(volume float64) {
sys.globalVolume = volume
}
// GetGroupVolume reports the current volume multiplier for the given group.
// The max groupID is 7 (therefore, there could be 8 groups in total).
// Use [SetGroupVolume] to adjust the group's volume multiplier.
func (sys *System) GetGroupVolume(groupID uint) float64 {
if groupID >= uint(len(sys.groupVolume)) {
panic("invalid group ID")
}
return sys.groupVolume[groupID]
}
// SetGroupVolume assigns the volume multiplier for the given group.
// The max groupID is 7 (therefore, there could be 8 groups in total).
// Use [GetGroupVolume] to get the group's current volume multiplier.
//
// A sound multiplier of 0 effectively mutes the group.
func (sys *System) SetGroupVolume(groupID uint, multiplier float64) {
if groupID >= uint(len(sys.groupVolume)) {
panic("invalid group ID")
}
sys.groupVolume[groupID] = multiplier
}
func (sys *System) PlaySoundWithOptions(id resource.AudioID, opts PlayOptions) resource.Audio {
return sys.playSound(id, opts.Volume)
}
// PlaySound is a shorthand for [PlaySoundWithOptions](id, {Volume: 1.0}).
//
// The returned Audio object can be ignored unless you want to check it's
// player field for a readonly operation like IsPlaying.
func (sys *System) PlaySound(id resource.AudioID) resource.Audio {
return sys.PlaySoundWithOptions(id, PlayOptions{
Volume: 1,
})
}
func (sys *System) playSound(id resource.AudioID, vol float64) resource.Audio {
res := sys.loader.LoadWAV(id)
if sys.soundMap.IsSet(uint(id)) {
return res
}
sys.soundMap.Set(uint(id))
finalVolume := sys.calculateVolume(res, vol)
if finalVolume != 0 {
res.Player.SetVolume(finalVolume)
res.Player.Rewind()
res.Player.Play()
}
return res
}
func (sys *System) calculateVolume(a resource.Audio, vol float64) float64 {
return sys.globalVolume * sys.groupVolume[a.Group] * a.Volume * vol
}