-
Notifications
You must be signed in to change notification settings - Fork 11
/
music.go
193 lines (169 loc) · 4.73 KB
/
music.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package music
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"time"
"github.com/hajimehoshi/ebiten/v2/audio"
"github.com/hajimehoshi/ebiten/v2/audio/vorbis"
"github.com/divVerent/aaaaxy/internal/audiowrap"
"github.com/divVerent/aaaaxy/internal/flag"
"github.com/divVerent/aaaaxy/internal/log"
"github.com/divVerent/aaaaxy/internal/vfs"
)
var (
musicVolume = flag.Float64("music_volume", 0.5, "music volume (0..1)")
musicFadeTime = flag.Duration("music_fade_time", 5*time.Second/4, "music fade time")
musicRestoreTime = flag.Duration("music_restore_time", time.Second/2, "music restore time")
)
const (
bytesPerSample = 4
)
type musicJson struct {
PlayStart int64 `json:"play_start"`
ReplayGain float64 `json:"replay_gain"`
LoopStart int64 `json:"loop_start"`
LoopEnd int64 `json:"loop_end"`
}
type sampleCutter struct {
base io.ReadSeeker
closer io.Closer
offset int64
}
var _ io.ReadSeeker = &sampleCutter{}
func (c *sampleCutter) Read(b []byte) (int, error) {
return c.base.Read(b)
}
func (c *sampleCutter) Seek(offset int64, whence int) (int64, error) {
if whence == io.SeekStart {
offset += c.offset
}
o, err := c.base.Seek(offset, whence)
return o - c.offset, err
}
func (c *sampleCutter) Close() error {
return c.closer.Close()
}
func newSampleCutter(base io.ReadSeeker, offset int64, closer io.Closer) (*sampleCutter, error) {
f := &sampleCutter{
base: base,
offset: offset,
closer: closer,
}
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("could not build sample cutter: %w", err)
}
return f, nil
}
var (
prevName string
currentName string
player *audiowrap.Player
prevMusic *audiowrap.FadeHandle
active bool
)
func Enable() {
if !active && player != nil {
player.Play()
}
active = true
}
// Now returns the current music playback time.
func Now() time.Duration {
if player != nil && player.IsPlaying() {
return player.Current()
}
return 0
}
// Switch switches from the currently playing music to the given track.
// Passing an empty string means fading to silence.
func Switch(name string) {
if name == currentName {
return
}
// Fade out the current music.
if player != nil {
// Have a player - so we're switching tracks. Fade out current music.
prevName, prevMusic = currentName, player.FadeOutIn(*musicFadeTime)
player = nil
} else {
// Have no player. Then there are two cases.
if name == prevName && prevMusic != nil {
// Back to last track? See if we can restore it.
restored := prevMusic.RestoreIn(*musicRestoreTime)
if restored != nil {
currentName, player = name, restored
prevName, prevMusic = "", nil
return
}
}
// Otherwise prepare to start playing the new track.
prevName, prevMusic = "", nil
}
// Switch to it.
currentName = name
// If we're playing silence, we're done.
if name == "" {
return
}
// Now load the new track.
config := musicJson{
PlayStart: 0,
LoopStart: 0,
LoopEnd: -1,
ReplayGain: 1,
}
j, err := vfs.Load("music", name+".json")
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Errorf("could not load music json config file for %q: %v", name, err)
return
}
if j != nil {
defer j.Close()
err = json.NewDecoder(j).Decode(&config)
if err != nil {
log.Errorf("could not decode music json config file for %q: %v", name, err)
return
}
}
player, err = audiowrap.NewPlayer(func() (io.ReadCloser, error) {
handle, err := vfs.Load("music", name)
if err != nil {
return nil, fmt.Errorf("could not load music %q: %w", name, err)
}
data, err := vorbis.DecodeWithSampleRate(audiowrap.SampleRate(), handle)
if err != nil {
return nil, fmt.Errorf("could not start decoding music %q: %w", name, err)
}
loopEnd := data.Length()
if config.LoopEnd >= 0 {
loopEnd = config.LoopEnd * bytesPerSample
}
return newSampleCutter(audio.NewInfiniteLoopWithIntro(data, config.LoopStart*bytesPerSample, loopEnd), config.PlayStart*bytesPerSample, handle)
})
if err != nil {
log.Errorf("could not start playing music %q: %v", name, err)
return
}
// We have a valid player.
player.SetVolume(*musicVolume * config.ReplayGain)
if active {
player.Play()
}
}