-
Notifications
You must be signed in to change notification settings - Fork 16
/
audio.go
279 lines (259 loc) · 9.01 KB
/
audio.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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package sointu
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
)
type (
// AudioBuffer is a buffer of stereo audio samples of variable length, each
// sample represented by [2]float32. [0] is left channel, [1] is right
AudioBuffer [][2]float32
CloserWaiter interface {
io.Closer
Wait()
}
// AudioContext represents the low-level audio drivers. There should be at
// most one AudioContext at a time. The interface is implemented at least by
// oto.OtoContext, but in future we could also mock it.
//
// AudioContext is used to play one or more AudioSources. Playing can be
// stopped by closing the returned io.Closer.
AudioContext interface {
Play(r AudioSource) CloserWaiter
}
// AudioSource is an function for reading audio samples into an AudioBuffer.
// Returns error if the buffer is not filled.
AudioSource func(buf AudioBuffer) error
BufferSource struct {
buffer AudioBuffer
pos int
}
// Synth represents a state of a synthesizer, compiled from a Patch.
Synth interface {
// Render tries to fill a stereo signal buffer with sound from the
// synthesizer, until either the buffer is full or a given number of
// timesteps is advanced. Normally, 1 sample = 1 unit of time, but speed
// modulations may change this. It returns the number of samples filled (in
// stereo samples i.e. number of elements of AudioBuffer filled), the
// number of sync outputs written, the number of time steps time advanced,
// and a possible error.
Render(buffer AudioBuffer, maxtime int) (sample int, time int, err error)
// Update recompiles a patch, but should maintain as much as possible of its
// state as reasonable. For example, filters should keep their state and
// delaylines should keep their content. Every change in the Patch triggers
// an Update and if the Patch would be started fresh every time, it would
// lead to very choppy audio.
Update(patch Patch, bpm int) error
// Trigger triggers a note for a given voice. Called between synth.Renders.
Trigger(voice int, note byte)
// Release releases the currently playing note for a given voice. Called
// between synth.Renders.
Release(voice int)
}
// Synther compiles a given Patch into a Synth, throwing errors if the
// Patch is malformed.
Synther interface {
Synth(patch Patch, bpm int) (Synth, error)
}
)
// Play plays the Song by first compiling the patch with the given Synther,
// returning the stereo audio buffer as a result (and possible errors).
func Play(synther Synther, song Song, progress func(float32)) (AudioBuffer, error) {
err := song.Validate()
if err != nil {
return nil, err
}
synth, err := synther.Synth(song.Patch, song.BPM)
if err != nil {
return nil, fmt.Errorf("sointu.Play failed: %v", err)
}
curVoices := make([]int, len(song.Score.Tracks))
for i := range curVoices {
curVoices[i] = song.Score.FirstVoiceForTrack(i)
}
initialCapacity := song.Score.LengthInRows() * song.SamplesPerRow()
buffer := make(AudioBuffer, 0, initialCapacity)
rowbuffer := make(AudioBuffer, song.SamplesPerRow())
for row := 0; row < song.Score.LengthInRows(); row++ {
patternRow := row % song.Score.RowsPerPattern
pattern := row / song.Score.RowsPerPattern
for t := range song.Score.Tracks {
order := song.Score.Tracks[t].Order
if pattern < 0 || pattern >= len(order) {
continue
}
patternIndex := song.Score.Tracks[t].Order[pattern]
patterns := song.Score.Tracks[t].Patterns
if patternIndex < 0 || int(patternIndex) >= len(patterns) {
continue
}
pattern := patterns[patternIndex]
if patternRow < 0 || patternRow >= len(pattern) {
continue
}
note := pattern[patternRow]
if note > 0 && note <= 1 { // anything but hold causes an action.
continue
}
synth.Release(curVoices[t])
if note > 1 {
curVoices[t]++
first := song.Score.FirstVoiceForTrack(t)
if curVoices[t] >= first+song.Score.Tracks[t].NumVoices {
curVoices[t] = first
}
synth.Trigger(curVoices[t], note)
}
}
tries := 0
for rowtime := 0; rowtime < song.SamplesPerRow(); {
samples, time, err := synth.Render(rowbuffer, song.SamplesPerRow()-rowtime)
if err != nil {
return buffer, fmt.Errorf("render failed: %v", err)
}
rowtime += time
buffer = append(buffer, rowbuffer[:samples]...)
if tries > 100 {
return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow)
}
}
if progress != nil {
progress(float32(row+1) / float32(song.Score.LengthInRows()))
}
}
return buffer, nil
}
// Fill fills the AudioBuffer using a Synth, disregarding all syncs and time
// limits. Note that this will change the state of the Synth.
func (buffer AudioBuffer) Fill(synth Synth) error {
s, _, err := synth.Render(buffer, math.MaxInt32)
if err != nil {
return fmt.Errorf("synth.Render failed: %v", err)
}
if s != len(buffer) {
return errors.New("in AudioBuffer.Fill, synth.Render should have filled the whole buffer but did not")
}
return nil
}
func (b AudioBuffer) Source() AudioSource {
return func(buf AudioBuffer) error {
n := copy(buf, b)
b = b[n:]
if n < len(buf) {
return io.EOF
}
return nil
}
}
// ReadAudio reads audio samples from an AudioSource into an AudioBuffer.
// Returns an error when the buffer is fully consumed.
func (a *BufferSource) ReadAudio(buf AudioBuffer) error {
n := copy(buf, a.buffer[a.pos:])
a.pos += n
if a.pos >= len(a.buffer) {
return io.EOF
}
return nil
}
// Wav converts an AudioBuffer into a valid WAV-file, returned as a []byte
// array.
//
// If pcm16 is set to true, the samples in the WAV-file will be 16-bit signed
// integers; otherwise the samples will be 32-bit floats
func (buffer AudioBuffer) Wav(pcm16 bool) ([]byte, error) {
buf := new(bytes.Buffer)
wavHeader(len(buffer)*2, pcm16, buf)
err := buffer.rawToBuffer(pcm16, buf)
if err != nil {
return nil, fmt.Errorf("Wav failed: %v", err)
}
return buf.Bytes(), nil
}
// Raw converts an AudioBuffer into a raw audio file, returned as a []byte
// array.
//
// If pcm16 is set to true, the samples will be 16-bit signed integers;
// otherwise the samples will be 32-bit floats
func (buffer AudioBuffer) Raw(pcm16 bool) ([]byte, error) {
buf := new(bytes.Buffer)
err := buffer.rawToBuffer(pcm16, buf)
if err != nil {
return nil, fmt.Errorf("Raw failed: %v", err)
}
return buf.Bytes(), nil
}
func (data AudioBuffer) rawToBuffer(pcm16 bool, buf *bytes.Buffer) error {
var err error
if pcm16 {
int16data := make([][2]int16, len(data))
for i, v := range data {
int16data[i][0] = int16(clamp(int(v[0]*math.MaxInt16), math.MinInt16, math.MaxInt16))
int16data[i][1] = int16(clamp(int(v[1]*math.MaxInt16), math.MinInt16, math.MaxInt16))
}
err = binary.Write(buf, binary.LittleEndian, int16data)
} else {
err = binary.Write(buf, binary.LittleEndian, data)
}
if err != nil {
return fmt.Errorf("could not binary write data to binary buffer: %v", err)
}
return nil
}
// wavHeader writes a wave header for either float32 or int16 .wav file into the
// bytes.buffer. It needs to know the length of the buffer and assumes stereo
// sound, so the length in stereo samples (L + R) is bufferlength / 2. If pcm16
// = true, then the header is for int16 audio; pcm16 = false means the header is
// for float32 audio. Assumes 44100 Hz sample rate.
func wavHeader(bufferLength int, pcm16 bool, buf *bytes.Buffer) {
// Refer to: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
numChannels := 2
sampleRate := 44100
var bytesPerSample, chunkSize, fmtChunkSize, waveFormat int
var factChunk bool
if pcm16 {
bytesPerSample = 2
chunkSize = 36 + bytesPerSample*bufferLength
fmtChunkSize = 16
waveFormat = 1 // PCM
factChunk = false
} else {
bytesPerSample = 4
chunkSize = 50 + bytesPerSample*bufferLength
fmtChunkSize = 18
waveFormat = 3 // IEEE float
factChunk = true
}
buf.Write([]byte("RIFF"))
binary.Write(buf, binary.LittleEndian, uint32(chunkSize))
buf.Write([]byte("WAVE"))
buf.Write([]byte("fmt "))
binary.Write(buf, binary.LittleEndian, uint32(fmtChunkSize))
binary.Write(buf, binary.LittleEndian, uint16(waveFormat))
binary.Write(buf, binary.LittleEndian, uint16(numChannels))
binary.Write(buf, binary.LittleEndian, uint32(sampleRate))
binary.Write(buf, binary.LittleEndian, uint32(sampleRate*numChannels*bytesPerSample)) // avgBytesPerSec
binary.Write(buf, binary.LittleEndian, uint16(numChannels*bytesPerSample)) // blockAlign
binary.Write(buf, binary.LittleEndian, uint16(8*bytesPerSample)) // bits per sample
if fmtChunkSize > 16 {
binary.Write(buf, binary.LittleEndian, uint16(0)) // size of extension
}
if factChunk {
buf.Write([]byte("fact"))
binary.Write(buf, binary.LittleEndian, uint32(4)) // fact chunk size
binary.Write(buf, binary.LittleEndian, uint32(bufferLength)) // sample length
}
buf.Write([]byte("data"))
binary.Write(buf, binary.LittleEndian, uint32(bytesPerSample*bufferLength))
}
func clamp(value, min, max int) int {
if value < min {
return min
}
if value > max {
return max
}
return value
}