-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstrobe-tuner.js
267 lines (225 loc) · 7.96 KB
/
strobe-tuner.js
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
'use strict'
function StrobeTuner(glCtx) {
var me = this
var log = function(str) {
console.log(str)
}
// Audio related stuff
this.buffer = new Float32Array(StrobeTuner.BUF_SZ)
this.sampleRate = 0
this.baseFrequency = 440.0
this.sampleOffset = 0
this.autoGain = true
this.newData = false
this.pushFromBuffer = function(buf) {
this.sampleRate = buf.sampleRate
if (buf.length >= this.buffer.length) {
log('audio overflow!')
// overwrite the buffer
buf.copyFromChannel(this.buffer, 0, buf.length - this.buffer.length)
} else {
// shift then overwrite the buffer
this.buffer.copyWithin(0, buf.length)
buf.copyFromChannel(this.buffer.subarray(this.buffer.length - buf.length), 0, 0)
}
this.sampleOffset = this.sampleOffset + buf.length
this.sampleOffset = this.sampleOffset - Math.floor(this.sampleOffset*(this.baseFrequency/(10000*this.sampleRate)))/(this.baseFrequency/(10000*this.sampleRate))
this.newData = true
}
this.flushBuffer = function() {
this.newData = false
}
// WebGL related stuff
var setupShader = function(source, type) {
var shader = glCtx.createShader(type)
glCtx.shaderSource(shader, source)
glCtx.compileShader(shader)
if (!glCtx.getShaderParameter(shader, glCtx.COMPILE_STATUS)) {
log('shader compilation error: ' + glCtx.getShaderInfoLog(shader))
glCtx.deleteShader(shader)
shader = null
}
return shader
}
var setupProgram = function(vShader, fShader) {
var program = glCtx.createProgram()
glCtx.attachShader(program, vShader)
glCtx.attachShader(program, fShader)
glCtx.linkProgram(program)
if (!glCtx.getProgramParameter(program, glCtx.LINK_STATUS)) {
log('program compilation error: ' + glCtx.getProgramInfoLog(program))
glCtx.deleteProgram(program)
program = null
}
return program
}
var draw = null
var clearGlState = null
this.brightGain = 100.00
this.brightOffset = 0.00
var initManyPoly = function() {
var inited = false
// set up the initial OpenGL state
glCtx.clearColor(0.0, 0.0, 0.0, 1.0) // start off in complete darkness
glCtx.disable(glCtx.DEPTH_TEST) // draw all polygons
glCtx.enable(glCtx.BLEND) // turn on blending
glCtx.blendFunc(glCtx.ONE, glCtx.ONE) // accumulate values
var vertexSource = [
"attribute mediump vec4 aVertexPosition;",
"attribute mediump vec3 aStrobeCoord;",
"",
"varying mediump vec3 strobeCoord;",
"",
"void main(void) {",
" gl_Position = aVertexPosition;",
" strobeCoord = aStrobeCoord;",
"}"
].join("\n")
var fragmentSource = [
"const lowp float numMultipliers = 8.0;",
"",
"uniform mediump float gain;",
"uniform mediump float offset;",
"",
"varying mediump vec3 strobeCoord;",
"",
"void main(void) {",
" lowp float multiplier = 1.0 + floor(numMultipliers*strobeCoord.x);",
" mediump float pos = fract(multiplier*strobeCoord.y);",
" lowp float v = gain * strobeCoord.z;",
" if (pos > 0.5) {",
" v = offset + v;",
" } else {",
" v = offset - v;",
" }",
" gl_FragColor = vec4(v, v, v, 1.0);",
"}"
].join("\n")
log("vertex shader setup")
var vertexShader = setupShader(vertexSource, glCtx.VERTEX_SHADER)
log("fragment shader setup")
var fragmentShader = setupShader(fragmentSource, glCtx.FRAGMENT_SHADER)
if (vertexShader === null || fragmentShader === null) {
log("shader setup failed")
return
}
log("program setup")
var program = setupProgram(vertexShader, fragmentShader)
if (vertexShader === null || fragmentShader === null) {
log("program setup failed")
return
}
glCtx.useProgram(program)
var vertexPosition = glCtx.getAttribLocation(program, 'aVertexPosition')
glCtx.enableVertexAttribArray(vertexPosition)
var vertexTexCoord = glCtx.getAttribLocation(program, 'aStrobeCoord')
glCtx.enableVertexAttribArray(vertexTexCoord)
var gainUniform = glCtx.getUniformLocation(program, 'gain')
var offsetUniform = glCtx.getUniformLocation(program, 'offset')
var vertexBuf = glCtx.createBuffer()
var vertexCoordBuf = glCtx.createBuffer()
var vertexIndexBuf = glCtx.createBuffer()
inited = true
draw = function() {
if (!inited) {
return
}
if (!me.newData) {
return
}
// console.log(me.buffer.length)
console.log(me.sampleOffset)
// glCtx.enableVertexAttribArray(vertexTexCoord)
var vertexData = new Float32Array(4*(2+3)*me.buffer.length)
var vertexIdxs = new Uint16Array(2*3*me.buffer.length)
var scale = 1.0
if (me.autoGain) {
/*
var bufMax = 0.0001
for (var i = 0; i < me.buffer.length; i++) {
var absVal = Math.abs(me.buffer[i])
if (bufMax < absVal) {
bufMax = absVal
}
}
*/
var bufMax = 0.0
for (var i = 0; i < me.buffer.length; i++) {
bufMax += me.buffer[i]*me.buffer[i]
}
bufMax = Math.sqrt(bufMax/me.buffer.length)
scale = 0.01/bufMax
}
for (var i = 0; i < me.buffer.length; i++) {
var curOffset = (me.sampleOffset - me.buffer.length + i)*me.baseFrequency/me.sampleRate
var phaseOffset = (curOffset + 0.5) - Math.floor(curOffset + 0.5)
var val = me.buffer[i]*scale
vertexData[(2+3)*(4*i+0) + 0] = 1.0
vertexData[(2+3)*(4*i+0) + 1] = 2.0 + phaseOffset
vertexData[(2+3)*(4*i+0) + 2] = 1.0
vertexData[(2+3)*(4*i+0) + 3] = 2.0
vertexData[(2+3)*(4*i+0) + 4] = val
vertexData[(2+3)*(4*i+1) + 0] = 1.0
vertexData[(2+3)*(4*i+1) + 1] = -2.0 + phaseOffset
vertexData[(2+3)*(4*i+1) + 2] = 1.0
vertexData[(2+3)*(4*i+1) + 3] = 0.0
vertexData[(2+3)*(4*i+1) + 4] = val
vertexData[(2+3)*(4*i+2) + 0] = -1.0
vertexData[(2+3)*(4*i+2) + 1] = -2.0 + phaseOffset
vertexData[(2+3)*(4*i+2) + 2] = 0.0
vertexData[(2+3)*(4*i+2) + 3] = 0.0
vertexData[(2+3)*(4*i+2) + 4] = val
vertexData[(2+3)*(4*i+3) + 0] = -1.0
vertexData[(2+3)*(4*i+3) + 1] = 2.0 + phaseOffset
vertexData[(2+3)*(4*i+3) + 2] = 0.0
vertexData[(2+3)*(4*i+3) + 3] = 2.0
vertexData[(2+3)*(4*i+3) + 4] = val
}
for (var i = 0; i < me.buffer.length; i++) {
vertexIdxs[2*3*i + 0] = 4*i + 0;
vertexIdxs[2*3*i + 1] = 4*i + 1;
vertexIdxs[2*3*i + 2] = 4*i + 2;
vertexIdxs[2*3*i + 3] = 4*i + 0;
vertexIdxs[2*3*i + 4] = 4*i + 2;
vertexIdxs[2*3*i + 5] = 4*i + 3;
}
// log(me.buffer.length)
glCtx.bindBuffer(glCtx.ARRAY_BUFFER, vertexBuf)
glCtx.bufferData(glCtx.ARRAY_BUFFER, vertexData, glCtx.DYNAMIC_DRAW)
glCtx.clear(glCtx.COLOR_BUFFER_BIT)
glCtx.vertexAttribPointer(vertexPosition, 2, glCtx.FLOAT, false, 5*4, 0)
glCtx.vertexAttribPointer(vertexTexCoord, 3, glCtx.FLOAT, false, 5*4, 2*4)
glCtx.bindBuffer(glCtx.ELEMENT_ARRAY_BUFFER, vertexIndexBuf)
glCtx.bufferData(glCtx.ELEMENT_ARRAY_BUFFER, vertexIdxs, glCtx.DYNAMIC_DRAW)
glCtx.uniform1f(gainUniform, (me.brightGain/me.buffer.length))
glCtx.uniform1f(offsetUniform, me.brightOffset/me.buffer.length)
glCtx.drawElements(glCtx.TRIANGLES, 2*me.buffer.length, glCtx.UNSIGNED_SHORT, 0)
glCtx.finish()
}
clearGlState = function() {
// TODO
}
}
this.drawStrobe = function() {
if (!this.newData) {
// log('underflow')
return
}
draw()
this.flushBuffer()
}
// used to set the mode
this.resetGlState = function() {
if (clearGlState !== null) {
clearGlState()
}
clearGlState = null
draw = null
}
this.useManyPoly = initManyPoly
// init WebGL context with the default shaders
this.useManyPoly()
}
// about the number of values received in 1/30th of a second
StrobeTuner.BUF_SZ = 2048
StrobeTuner.MAX_BUF_SZ = 16384