-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathaudio.v
More file actions
200 lines (173 loc) · 3.9 KB
/
audio.v
File metadata and controls
200 lines (173 loc) · 3.9 KB
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
// Copyright (c) 2020-2021 spaceface, spytheman, henrixounez. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module audio
import math
import sokol.audio as saudio
const (
tau = 2 * math.pi
transi = 500
)
[inline]
fn midi2freq(midi byte) f32 {
return int(math.powf(2, f32(midi - 69) / 12) * 440)
}
pub struct Note {
mut:
freq f32
vol f32
step u32
paused bool
}
type NextFn = fn (freq f32, time f64, amp f32) f32
[inline]
fn square(freq f32, time f64, amp f32) f32 {
t := time * freq
f := t - int(t)
return if f < 0.5 { amp / 2 } else { -amp / 2 }
}
// pure triangle wave
[inline]
fn triangle(freq f32, time f64, amp f32) f32 {
t := time * freq
f := t - int(t)
return f32(2 * math.abs(2 * (f - 0.5)) - 1) * amp
}
// pure sawtooth wave
[inline]
fn sawtooth(freq f32, time f64, amp f32) f32 {
t := time * freq
f := t - int(t)
return f32(2 * (f - 0.5)) * amp / 2
}
// pure sine wave
[inline]
fn sine(freq f32, time f64, amp f32) f32 {
return f32(math.sin(audio.tau * time * freq) * amp)
}
// sine wave, imitating an organ
[inline]
fn organ(freq f32, time f64, amp f32) f32 {
return f32(math.sin(audio.tau * time * freq) * amp
+ math.sin(audio.tau * time * freq * 3 / 2) * amp / 5
+ math.sin(audio.tau * time * freq * 2) * amp / 5)
}
[inline]
// triangle wave, imitating an organ
fn torgan(freq f32, time f64, amp f32) f32 {
t := time * freq
return f32(2 * math.abs(2 * (t - int(t) - 0.5)) - 1) * amp
+ f32(2 * math.abs(2 * (t * 3 / 2 - int(t * 3 / 2) - 0.5)) - 1) * amp / 8
+ f32(2 * math.abs(2 * (t / 2 - int(t / 2) - 0.5)) - 1) * amp / 5
}
fn (c &Context) next(mut note Note, time f64) f32 {
if !note.paused {
note.step++
return c.next_fn(note.freq, time, note.damp())
} else if note.step >= 0 {
note.step--
return c.next_fn(note.freq, time, note.damp())
}
return 0
}
fn C.expf(x f32) f32
[inline]
fn (n Note) damp() f32 {
return n.vol * C.expf(-f32(n.step) * 2.5 / saudio.sample_rate())
}
pub struct Context {
mut:
next_fn NextFn
notes [128]Note
t f64
}
const gain = 0.5
[inline]
pub fn (mut ctx Context) play(midi byte, volume f32) {
ctx.notes[midi].paused = false
// bass-boost the lower notes, since high notes inherently sound louder
ctx.notes[midi].vol = volume * (-f32(math.atan(f64(midi) / 32 - 0.5)) + 1.5) * gain
ctx.notes[midi].step = 1
}
[inline]
pub fn (mut ctx Context) pause(midi byte) {
ctx.notes[midi].paused = true
ctx.notes[midi].step = 1000
}
fn clamp(x f64, lowerlimit f64, upperlimit f64) f64 {
if x < lowerlimit {
return lowerlimit
}
if x > upperlimit {
return upperlimit
}
return x
}
fn audio_cb(mut buffer &f32, num_frames int, num_channels int, mut ctx Context) {
mut mc := f32(0.0)
frame_ms := 1.0 / f32(saudio.sample_rate())
unsafe {
for frame in 0 .. num_frames {
for ch in 0 .. num_channels {
idx := frame * num_channels + ch
buffer[idx] = 0.
for i, note in ctx.notes {
if note.step > 0 {
buffer[idx] += ctx.next(mut ctx.notes[i], ctx.t)
}
}
c := buffer[idx]
ac := if c < 0 { -c } else { c }
if mc < ac {
mc = ac
}
}
ctx.t += frame_ms
}
if mc < 1.0 {
return
}
mut normalizing_coef := 1.0 / mc
for idx in 0 .. (num_frames * num_channels) {
buffer[idx] *= normalizing_coef
}
}
}
pub enum WaveKind {
// pure waves
sine
square
triangle
sawtooth
// composite functions
organ
torgan
}
pub struct Config {
wave_kind WaveKind
}
pub fn new_context(cfg Config) &Context {
next_fn := match cfg.wave_kind {
.sine { sine }
.square { square }
.triangle { triangle }
.sawtooth { sawtooth }
.organ { organ }
.torgan { torgan }
}
mut ctx := &Context{
next_fn: next_fn
}
for i, mut note in ctx.notes {
bi := byte(i)
note.freq = midi2freq(bi)
note.paused = true
note.step = 0
}
saudio.setup(
user_data: ctx
stream_userdata_cb: audio_cb
buffer_frames: 128
)
return ctx
}