-
Notifications
You must be signed in to change notification settings - Fork 4
/
example.html
328 lines (261 loc) · 11.1 KB
/
example.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
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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head>
<title>Example</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="http://html5-examples.craic.com/js/chroma.js"></script>
<script type="text/javascript" src="js/webkitAudioContextMonkeyPath.js"></script>
<style>
body {
font-family: Helvetica, sans-serif;
font-size: 11pt;
padding: 5px;
margin-left: 150px;
margin-right: 150px;
}
h2 {
font-weight: normal;
font-size: 24pt;
text-align: center;
}
#controls {
text-align: center;
}
#start_button {
font-size: 16pt;
}
#stop_button {
font-size: 16pt;
}
#playback_button {
font-size: 16pt;
}
#reset_button {
font-size: 16pt;
}
#canvas {
margin-left: auto;
margin-right: auto;
display: block;
background-color: black ;
}
</style>
</head>
<body>
<h2>Recording Audio from Microphone with Web Audio</h2>
<p> </p>
<p>This demo uses the <a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html">Web Audio API</a> to capture audio from the
microphone, display a frequency spectrogram and capture the audio in a recording for playback.</p>
<p>Recording is not directly supported in the API, so you need to capture consecutive samples into your own buffer. This can then be
used as an input source during playback.</p>
<p> </p>
<p id="controls">
<button id="start_button">Start Recording</button>
<button id="stop_button">Stop Recording</button>
<button id="playback_button">Playback</button>
<button id="reset_button">Reset</button>
</p>
<canvas id="canvas" width="800" height="256" ></canvas>
<br/>
<p style="text-align:center"><a href='#' id='disable_audio'>Turn off the microphone</a></p>
<p> </p>
<p>See the code for more comments - note that the recording buffer is
a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/Float32Array">Float32Array</a> which you need to grow every
time you add a new sample</p>
<p>In order to play the recording you need to create a BufferSource node and copy the recording to it's buffer every time you play the audio</p>
<p><a href="https://github.com/mattdiamond/Recorderjs">Recorder.js</a> by Matt Diamond is a more packaged approach to recording that allows you to
download the recording as a WAV file. This uses web workers to improve performance, which is a great idea, but it makes the code harder to
follow if you are getting started with web audio.</p>
<p>As of July 2013 the demo only works in Google Chrome. The demo must be hosted on a server - it will not work if you open the page as a file.</p>
<p>To avoid the requests to access the microphone you can use this
<a href="https://html5-examples.herokuapp.com/microphone_input_with_spectrogram.html">alternate https link</a></p>
<p> </p>
<p>This demo was created by <a href="http://craic.com">Robert Jones of Craic Computing</a>
and is freely distributed under the terms of the MIT license.</p>
<p>This spectrogram in this demo was based on the excellent tutorial
<a href="http://www.smartjava.org/content/exploring-html5-web-audio-visualizing-sound">Exploring the HTML5 Web Audio: visualizing sound</a>
by Jos Dirksen.</p>
<script type="text/javascript">
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element){
window.setTimeout(callback, 1000 / 60);
};
})();
// Global Variables for Audio
var audioContext;
var sourceNode;
var analyserNode;
var javascriptNode;
var playbackSourceNode;
var audioStream;
var array = [];
var recording = null; // this is the cumulative buffer for your recording
var audioBufferNode = null;
var audioBuffer = null;
// Global Variables for Drawing
var x = 0;
var canvasWidth = 800;
var canvasHeight = 256;
var ctx;
// Uses the chroma.js library by Gregor Aisch to create a tasteful color gradient
// download from https://github.com/gka/chroma.js
var hot = new chroma.ColorScale({
colors:['#000000', '#ff0000', '#ffff00', '#ffffff'],
positions:[0, .25, .75, 1],
mode:'rgb',
limits:[0, 256]
});
window.craicAudioContext = (function(){
return window.webkitAudioContext || window.AudioContext ;
})();
navigator.getMedia = ( navigator.mozGetUserMedia ||
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.msGetUserMedia);
$(document).ready(function() {
// get the context from the canvas to draw on
ctx = $("#canvas").get()[0].getContext("2d");
clearCanvas();
// Check that the browser can handle web audio
try {
// audioContext = new webkitAudioContext();
audioContext = new craicAudioContext();
}
catch(e) {
alert('Web Audio API is not supported in this browser');
}
// get the input audio stream and set up the nodes
try {
// calls the function setupAudioNodes
// navigator.webkitGetUserMedia({audio:true}, setupAudioNodes, onError);
navigator.getMedia({audio:true}, setupAudioNodes, onError);
} catch (e) {
alert('webkitGetUserMedia threw exception :' + e);
}
// Start recording by setting onaudioprocess to the function that manages the recording buffer
$("body").on('click', "#start_button",function(e) {
e.preventDefault();
// execute every time a new sample has been acquired
javascriptNode.onaudioprocess = function (e) {
addSampleToRecording(e.inputBuffer);
// Analyze the frequencies in this sample and add to the spectorgram
analyserNode.getByteFrequencyData(array);
requestAnimFrame(drawSpectrogram);
}
});
// Stop recording by setting onaudioprocess to null
$("body").on('click', "#stop_button",function(e) {
e.preventDefault();
javascriptNode.onaudioprocess = null;
});
// Play the recording
$("body").on('click', "#playback_button",function(e) {
e.preventDefault();
playRecording();
});
// Reset the recording buffer and the graphics, but keep the nodes connected
$("body").on('click', "#reset_button",function(e) {
e.preventDefault();
recording = null;
clearCanvas();
});
// Disable audio completely
$("body").on('click', "#disable_audio",function(e) {
/* e.preventDefault();
javascriptNode.onaudioprocess = null;
if(audioStream) audioStream.stop();
if(sourceNode) sourceNode.disconnect();*/
});
});
function onError(e) {
console.log(e);
}
// Add this buffer to the recording
// recording is a global
function addSampleToRecording(inputBuffer) {
var currentBuffer = inputBuffer.getChannelData(0);
if (recording == null) {
// handle the first buffer
recording = currentBuffer;
} else {
// allocate a new Float32Array with the updated length
newlen = recording.length + currentBuffer.length;
var newBuffer = new Float32Array(newlen);
newBuffer.set(recording, 0);
newBuffer.set(currentBuffer, recording.length);
recording = newBuffer;
}
}
function playRecording() {
// You need to create the buffer node every time we play the sound
// Are we able to cleanup that memory or does the footprint grow over time ??
if ( recording != null ) {
// create the Buffer from the recording
audioBuffer = audioContext.createBuffer( 1, recording.length, audioContext.sampleRate );
audioBuffer.getChannelData(0).set(recording, 0);
// create the Buffer Node with this Buffer
audioBufferNode = audioContext.createBufferSource();
audioBufferNode.buffer = audioBuffer;
console.log('recording buffer length ' + audioBufferNode.buffer.length.toString());
// connect the node to the destination and play the audio
audioBufferNode.connect(audioContext.destination);
audioBufferNode.noteOn(0);
}
}
function setupAudioNodes(stream) {
var sampleSize = 1024; // number of samples to collect before analyzing FFT
// decreasing this gives a faster sonogram, increasing it slows it down
audioStream = stream;
// The nodes are: sourceNode -> analyserNode -> javascriptNode -> destination
// create an audio buffer source node
sourceNode = audioContext.createMediaStreamSource(audioStream);
// Set up the javascript node - this uses only one channel - i.e. a mono microphone
javascriptNode = audioContext.createJavaScriptNode(sampleSize, 1, 1);
// setup the analyser node
analyserNode = audioContext.createAnalyser();
analyserNode.smoothingTimeConstant = 0.0;
analyserNode.fftSize = 1024; // must be power of two
// connect the nodes together
sourceNode.connect(analyserNode);
analyserNode.connect(javascriptNode);
javascriptNode.connect(audioContext.destination);
// optional - connect input to audio output (speaker)
// This will echo your input back to your speakers - Beware of Feedback !!
// sourceNode.connect(audioContext.destination);
// allocate the array for Frequency Data
array = new Uint8Array(analyserNode.frequencyBinCount);
}
// Draw the Spectrogram from the frequency array
// adapted from http://www.smartjava.org/content/exploring-html5-web-audio-visualizing-sound
function drawSpectrogram() {
for (var i = 0; i < array.length; i += 1) {
// Get the color for each pixel from a color map
var value = array[i];
ctx.beginPath();
ctx.strokeStyle = hot.getColor(value).hex();
// draw a 1 pixel wide rectangle on the canvas
var y = canvasHeight - i;
ctx.moveTo(x, y);
ctx.lineTo(x+1, y);
ctx.closePath();
ctx.stroke();
}
// loop around the canvas when we reach the end
x = x + 1;
if(x >= canvasWidth) {
x = 0;
clearCanvas();
}
}
function clearCanvas() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
x = 0;
}
</script>
</body>
</html>