-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
211 lines (163 loc) · 8.28 KB
/
index.html
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
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>wabps</title>
</head>
<body style="margin: 0">
<div id="progress" style="position: absolute; left: 0; right: 0; bottom: 0; margin: auto; z-index: 10; background: rgba(64, 64, 64, 0.9); height: 26px; width: 66px; color: white; text-align: center; font-size: 24px; font-family: monospace;"></div>
<canvas id="output" width="1024" height="600" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto;"></canvas>
<script type="text/javascript">
window.onload = async function () {
const getFrequency = (n, base_frequency, i, octaves) => {
return base_frequency * Math.pow(2, i / (n / octaves))
}
const canvas = document.getElementById("output")
const canvas_ctx = canvas.getContext('2d')
canvas_ctx.fillStyle = "#000000"
canvas_ctx.fillRect(0, 0, canvas.width, canvas.height)
// load audio
const audio_context = new window.AudioContext({
sampleRate: 44100
})
const request = new XMLHttpRequest()
request.open('GET', 'female_french_numbers_1_10.wav', true)
request.responseType = 'arraybuffer'
// analysis parameters
const minimum_frequency = 16.34
const maximum_frequency = audio_context.sampleRate / 2 // linear
const octaves = 10 // logarithmic
const frequency_step = 30 // linear
const analysis_window_size = 256
const magnitude_factor = 1
canvas.height = 439 // linear : Math.round((maximum_frequency - minimum_frequency) / frequency_step)
const float_image_data = new Float32Array(canvas.width * canvas.height)
// load audio sample
request.onload = function () {
const audio_data = request.response
audio_context.decodeAudioData(audio_data, function(audio_buffer) {
// setup canvas width from audio length
canvas.width = Math.round(audio_buffer.length / analysis_window_size)
//const analysis_band = canvas_ctx.createImageData(canvas.width, 1)
//const analysis_band_data = analysis_band.data
const canvas_image_data = canvas_ctx.createImageData(canvas.width, canvas.height)
const canvas_data = canvas_image_data.data
let percent_complete = 0
let y = canvas.height
// now is where the analysis fun begin
// this run the filter on an audio source multiple time, each time with different Q frequency
// the result of each frequency band is drawn onto the canvas
const analyze_frequency_band = (current_frequency) => {
let x = 0
// create offline audio context & connect graph
const offline_audio_ctx = new OfflineAudioContext(1, audio_buffer.length, audio_context.sampleRate)
// create a bandpass filter
const bp_filter = offline_audio_ctx.createBiquadFilter()
bp_filter.type = 'bandpass'
bp_filter.connect(offline_audio_ctx.destination)
// create a second bandpass filter (24db rolloff)
const bp_filter2 = offline_audio_ctx.createBiquadFilter()
bp_filter2.type = 'bandpass'
bp_filter2.connect(bp_filter)
// create a second bandpass filter (48db rolloff)
const bp_filter3 = offline_audio_ctx.createBiquadFilter()
bp_filter3.type = 'bandpass'
bp_filter3.connect(bp_filter2)
// create a second bandpass filter (96db rolloff)
const bp_filter4 = offline_audio_ctx.createBiquadFilter()
bp_filter4.type = 'bandpass'
bp_filter4.connect(bp_filter3)
// run analysis for a single band
const from = current_frequency
const to = getFrequency(canvas.height, minimum_frequency, canvas.height - y + 1, octaves) // linear : current_frequency + frequency_step
const geometric_mean = Math.sqrt(from * to)
bp_filter.frequency.value = geometric_mean
bp_filter.Q.value = geometric_mean / (to - from)
bp_filter2.frequency.value = geometric_mean
bp_filter2.Q.value = geometric_mean / (to - from)
bp_filter3.frequency.value = geometric_mean
bp_filter3.Q.value = geometric_mean / (to - from)
bp_filter4.frequency.value = geometric_mean
bp_filter4.Q.value = geometric_mean / (to - from)
const source_node = offline_audio_ctx.createBufferSource()
source_node.buffer = audio_buffer
source_node.connect(bp_filter) // replace it with bp_filter4 (96db) bp_filter3 (48db) bp_filter2 (24db) bp_filter (12db)
source_node.start(0)
// start frequency band analysis & rendering
offline_audio_ctx.startRendering().then((rendered_buffer) => {
const pcmf32_buffer = rendered_buffer.getChannelData(0)
// draw frequency band
let analysis_data = []
for (let i = 0; i < pcmf32_buffer.length; i += 1) {
analysis_data.push(pcmf32_buffer[i])
if (analysis_data.length >= analysis_window_size) {
let mean_amplitude = 0
for (let j = 0; j < analysis_data.length; j += 1) {
mean_amplitude += Math.abs(analysis_data[j])
}
mean_amplitude /= analysis_data.length
//const color = Math.round(mean_amplitude * 255 * magnitude_factor)
float_image_data[x + y * canvas.width] = mean_amplitude
//const index = x * 4
//analysis_band_data[index + 0] = color
//analysis_band_data[index + 1] = color
//analysis_band_data[index + 2] = color
//analysis_band_data[index + 3] = 255
analysis_data = []
x += 1
}
}
//canvas_ctx.putImageData(analysis_band, 0, y)
// once done instruct to run it again for the next frequency band unless it reach the maximum frequency
if (y > 0) {
// initialize new frequency band position
x = 0
y -= 1
// compute progress %
const percent_complete_new = Math.round((1 - y / canvas.height) * 100)
if (percent_complete_new != percent_complete) {
const progressElement = document.getElementById("progress")
progressElement.innerText = percent_complete_new + '%'
percent_complete = percent_complete_new
}
// linear : current_frequency + frequency_step
setTimeout(analyze_frequency_band, 0, getFrequency(canvas.height, minimum_frequency, canvas.height - y, octaves))
} else { // it ended
// find maximum value so that we can normalize
let max_value = 0
for (let i = 0; i < canvas.width; i += 1) {
for (let j = 0; j < canvas.height; j += 1) {
const index = i + j * canvas.width
const value = float_image_data[index]
if (max_value < value) {
max_value = value
}
}
}
// normalize & draw it
for (let i = 0; i < canvas.width; i += 1) {
for (let j = 0; j < canvas.height; j += 1) {
const index = i + j * canvas.width
const canvas_index = (i + j * canvas.width) * 4
const norm_value = float_image_data[index] / max_value
const value = Math.round(norm_value * magnitude_factor * 255)
canvas_data[canvas_index + 0] = value
canvas_data[canvas_index + 1] = value
canvas_data[canvas_index + 2] = value
canvas_data[canvas_index + 3] = 255
}
}
canvas_ctx.putImageData(canvas_image_data, 0, 0)
}
})
}
analyze_frequency_band(minimum_frequency)
}, function () {
window.alert('Could not load audio file.')
})
}
request.send()
}
</script>
</body>
</html>