benjamin-paine commited on
Commit
d5a4585
·
verified ·
1 Parent(s): 9b8b70f

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -409
index.html DELETED
@@ -1,409 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Hey, Buddy!</title>
7
- <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.19.0/dist/ort.min.js"></script>
8
- <script src="hey-buddy-0.1.0.min.js"></script>
9
- <style>
10
- body {
11
- display: flex;
12
- flex-flow: column nowrap;
13
- justify-content: center;
14
- align-items: center;
15
- height: 100vh;
16
- width: 100vw;
17
- padding: 0;
18
- margin: 0;
19
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
20
- background-color: rgb(11,15,25);
21
- color: white
22
- }
23
-
24
- h1 {
25
- font-size: 16px;
26
- margin-top: 0;
27
- }
28
-
29
- p {
30
- font-size: 15px;
31
- margin-bottom: 10px;
32
- margin-top: 5px;
33
- }
34
-
35
- strong, em {
36
- color: #16c8ce;
37
- }
38
-
39
- .card {
40
- max-width: 640px;
41
- margin: 0 auto;
42
- padding: 16px;
43
- border: 1px solid rgb(107, 114, 128);
44
- border-radius: 16px;
45
- background-color: rgb(16, 22, 35);
46
- }
47
-
48
- .card p:last-child {
49
- margin-bottom: 0;
50
- }
51
-
52
- .card img {
53
- width: 100%;
54
- max-width: 420px;
55
- margin: 0 auto;
56
- }
57
-
58
- #logo, #links {
59
- display: flex;
60
- flex-flow: row wrap;
61
- justify-content: center;
62
- }
63
-
64
- #links {
65
- gap: 1em;
66
- margin: 1em;
67
- }
68
-
69
- #links img {
70
- height: 20px;
71
- }
72
-
73
- #graphs {
74
- display: flex;
75
- flex-flow: column nowrap;
76
- justify-content: center;
77
- align-items: center;
78
- gap: 1em;
79
- }
80
-
81
- label {
82
- display: block;
83
- }
84
-
85
- #graphs div {
86
- position: relative;
87
- }
88
-
89
- #graphs label {
90
- position: absolute;
91
- right: 0;
92
- top: 0;
93
- max-width: 120px;
94
- text-transform: uppercase;
95
- font-family: monospace;
96
- text-align: right;
97
- padding: 0 4px;
98
- line-height: 20px;
99
- background-image: linear-gradient(to top, rgba(255,255,255,0.1), rgba(255,255,255,0.0));
100
- border: 1px solid rgba(255,255,255,0.1);
101
- border-top: none;
102
- border-right: none;
103
- }
104
-
105
- #graphs .legend {
106
- display: flex;
107
- flex-flow: row wrap;
108
- justify-content: flex-end;
109
- gap: 1px 5px;
110
- text-transform: uppercase;
111
- font-family: monospace;
112
- font-size: 10px;
113
- line-height: 11px;
114
- }
115
-
116
- canvas.graph {
117
- border: 1px solid rgba(255,255,255,0.1);
118
- border-bottom: none;
119
- background-image:
120
- repeating-linear-gradient(to top, rgba(255,255,255,0.05), rgba(255,255,255,0.05) 1px, transparent 1px, transparent 10px),
121
- linear-gradient(to top, rgba(255,255,255,0.1), rgba(255,255,255,0.0));
122
- }
123
-
124
- #recording {
125
- margin-top: 1em;
126
- position: relative;
127
- display: block;
128
- height: 100px;
129
- line-height: 100px;
130
- text-align: center;
131
- font-size: 11px;
132
- background-image: linear-gradient(to top, rgba(255,255,255,0.1), rgba(255,255,255,0.0));
133
- border: 1px solid rgba(255,255,255,0.1);
134
- border-bottom-left-radius: 10px;
135
- border-bottom-right-radius: 10px;
136
- }
137
-
138
- #recording #audio {
139
- display: flex;
140
- flex-flow: row nowrap;
141
- align-items: center;
142
- justify-content: center;
143
- height: 100%;
144
- }
145
-
146
- #recording label {
147
- position: absolute;
148
- right: 0;
149
- top: 0;
150
- max-width: 120px;
151
- text-transform: uppercase;
152
- font-family: monospace;
153
- font-size: 12px;
154
- text-align: right;
155
- padding: 0 4px;
156
- line-height: 20px;
157
- background-image: linear-gradient(to top, rgba(255,255,255,0.1), rgba(255,255,255,0.0));
158
- border: 1px solid rgba(255,255,255,0.1);
159
- border-top: none;
160
- border-right: none;
161
- }
162
- </style>
163
- </head>
164
- <body>
165
- <div class="card">
166
- <section id="logo">
167
- <img src="logo.png" alt="Hey Buddy!" />
168
- </section>
169
- <section id="headline">
170
- <p><strong><em>Hey Buddy!</em></strong> is a library for training wake word models (a.k.a audio keyword spotters) and deploying them to the browser for real-time use on CPU or GPU.</p>
171
- <p>Using a wake-word as a gating mechanism for voice-enabled web applications carries numerous benefits, including reduced power consumption, improved privacy, and enhanced performance in noisy environments over speech-to-text systems.</p>
172
- <p>This space serves as a demonstration of the JavaScript library for front-end applications. Say something like, <em>&ldquo;Hey buddy, how are you?&rdquo;</em> to see the wake word and voice activity detection in action. Your voice command will be isolated as an audio clip, which is then ready to be sent to your application's backend for further processing.</p>
173
- </section>
174
- <section id="links">
175
- <a href="https://github.com/painebenjamin/hey-buddy" target="_blank">
176
- <img src="https://img.shields.io/static/v1?label=painebenjamin&message=hey-buddy&logo=github&color=0b1830" alt="painebenjamin - hey-buddy" />
177
- </a>
178
- <a href="https://huggingface.co/benjamin-paine/hey-buddy" target="_blank">
179
- <img src="https://img.shields.io/static/v1?label=benjamin-paine&message=hey-buddy&logo=huggingface&color=0b1830" alt="painebenjamin - hey-buddy" />
180
- </a>
181
- </section>
182
- <section id="graphs"></section>
183
- <section id="recording">
184
- <label>Recording</label>
185
- <div id="audio">No recording yet</div>
186
- </section>
187
- </div>
188
- </body>
189
- <script>
190
- /** Configuration */
191
- const colors = {
192
- "buddy": [0,119,187],
193
- "hey buddy": [51,187,238],
194
- "hi buddy": [0,153,136],
195
- "sup buddy": [238,119,51],
196
- "yo buddy": [204,51,17],
197
- "okay buddy": [238,51,119],
198
- "speech": [22,200,206],
199
- "frame budget": [25,255,25]
200
- };
201
- const wakeWords = ["buddy", "hey buddy", "hi buddy", "sup buddy", "yo buddy", "okay buddy"];
202
- const canvasSize = { width: 640, height: 100 };
203
- const graphLineWidth = 1;
204
- const options = {
205
- debug: true,
206
- modelPath: wakeWords.map((word) => `/models/${word.replace(' ', '-')}.onnx`)
207
- };
208
-
209
- /** Helper method for conversion */
210
- const float32ToWavBlob = (audioData, sampleRate, numChannels = 1) => {
211
- // Helper to write a string to the DataView
212
- const writeString = (view, offset, string) => {
213
- for (let i = 0; i < string.length; i++) {
214
- view.setUint8(offset + i, string.charCodeAt(i));
215
- }
216
- };
217
-
218
- // Helper to convert Float32Array to Int16Array (16-bit PCM)
219
- const floatTo16BitPCM = (output, offset, input) => {
220
- for (let i = 0; i < input.length; i++, offset += 2) {
221
- let s = Math.max(-1, Math.min(1, input[i])); // Clamping to [-1, 1]
222
- output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); // Convert to 16-bit PCM
223
- }
224
- };
225
-
226
- const byteRate = sampleRate * numChannels * 2; // 16-bit PCM = 2 bytes per sample
227
-
228
- // Calculate sizes
229
- const blockAlign = numChannels * 2; // 2 bytes per sample for 16-bit audio
230
- const wavHeaderSize = 44;
231
- const dataLength = audioData.length * numChannels * 2; // 16-bit PCM data length
232
- const buffer = new ArrayBuffer(wavHeaderSize + dataLength);
233
- const view = new DataView(buffer);
234
-
235
- // Write WAV file headers
236
- writeString(view, 0, 'RIFF'); // ChunkID
237
- view.setUint32(4, 36 + dataLength, true); // ChunkSize
238
- writeString(view, 8, 'WAVE'); // Format
239
- writeString(view, 12, 'fmt '); // Subchunk1ID
240
- view.setUint32(16, 16, true); // Subchunk1Size (PCM = 16)
241
- view.setUint16(20, 1, true); // AudioFormat (PCM = 1)
242
- view.setUint16(22, numChannels, true); // NumChannels
243
- view.setUint32(24, sampleRate, true); // SampleRate
244
- view.setUint32(28, byteRate, true); // ByteRate
245
- view.setUint16(32, blockAlign, true); // BlockAlign
246
- view.setUint16(34, 16, true); // BitsPerSample (16-bit PCM)
247
- writeString(view, 36, 'data'); // Subchunk2ID
248
- view.setUint32(40, dataLength, true); // Subchunk2Size
249
-
250
- // Convert the Float32Array audio samples to 16-bit PCM and write them to the DataView
251
- floatTo16BitPCM(view, wavHeaderSize, audioData);
252
-
253
- // Create and return the Blob
254
- return new Blob([view], { type: 'audio/wav' });
255
- }
256
-
257
- /** Helper method for turning the audio samples into an audio element */
258
- const saveRecording = (audioContainer, audioSamples, sampleRate = 16000) => {
259
- const blob = float32ToWavBlob(audioSamples, sampleRate);
260
- const url = URL.createObjectURL(blob);
261
- audioContainer.innerHTML = `<audio controls src="${url}"></audio>`;
262
- }
263
-
264
- /** DOM elements */
265
- const graphsContainer = document.getElementById("graphs");
266
- const audioContainer = document.getElementById("audio");
267
-
268
- /** Memory for drawing */
269
- const graphs = {};
270
- const history = {};
271
- const current = {};
272
- const active = {};
273
-
274
- console.log(window.location.href);
275
-
276
- /** Instantiate */
277
- let heyBuddy;
278
- try {
279
- heyBuddy = new HeyBuddy(options);
280
- console.log("Made!", heyBuddy);
281
- } catch(e) {
282
- console.error("Couldn't make heybuddy", e);
283
- heyBuddy = new HeyBuddy(options);
284
- }
285
-
286
- /** Add callbacks */
287
-
288
- // When processed, update state for next draw
289
- heyBuddy.onProcessed((result) => {
290
- current["frame budget"] = heyBuddy.frameTimeEma;
291
- current["speech"] = result.speech.probability || 0.0;
292
- active["speech"] = result.speech.active;
293
- for (let wakeWord in result.wakeWords) {
294
- current[wakeWord.replace('-', ' ')] = result.wakeWords[wakeWord].probability || 0.0;
295
- active[wakeWord.replace('-', ' ')] = result.wakeWords[wakeWord].active;
296
- }
297
- if (result.recording) {
298
- audioContainer.innerHTML = "Recording&hellip;";
299
- }
300
- });
301
-
302
- // When recording is complete, replace the audio element
303
- heyBuddy.onRecording((audioSamples) => {
304
- saveRecording(audioContainer, audioSamples);
305
- });
306
-
307
- /** Add graphs */
308
- for (let graphName of ["wake words", "speech", "frame budget"]) {
309
- // Create containers for the graph and its label
310
- const graphContainer = document.createElement("div");
311
- const graphLabel = document.createElement("label");
312
- graphLabel.textContent = graphName;
313
-
314
- // Create a canvas for the graph
315
- const graphCanvas = document.createElement("canvas");
316
- graphCanvas.className = "graph";
317
- graphCanvas.width = canvasSize.width;
318
- graphCanvas.height = canvasSize.height;
319
- graphs[graphName] = graphCanvas;
320
-
321
- // Add the canvas to the container and the container to the document
322
- graphContainer.appendChild(graphCanvas);
323
- graphContainer.appendChild(graphLabel);
324
- graphsContainer.appendChild(graphContainer);
325
-
326
- // If this is the wake-word graph, also add legend
327
- if (graphName === "wake words") {
328
- const graphLegend = document.createElement("div");
329
- graphLegend.className = "legend";
330
- for (let wakeWord of wakeWords) {
331
- const legendItem = document.createElement("div");
332
- const [r,g,b] = colors[wakeWord];
333
- legendItem.style.color = `rgb(${r},${g},${b})`;
334
- legendItem.textContent = wakeWord;
335
- graphLegend.appendChild(legendItem);
336
- }
337
- graphLabel.appendChild(graphLegend);
338
- }
339
- }
340
-
341
- /** Define draw loop */
342
- const draw = () => {
343
- // Draw speech and model graphs
344
- for (let graphName in graphs) {
345
- const isWakeWords = graphName === "wake words";
346
- const isFrameBudget = graphName === "frame budget";
347
- const subGraphs = isWakeWords ? wakeWords : [graphName];
348
-
349
- let isFirst = true;
350
- for (let name of subGraphs) {
351
- // Update history
352
- history[name] = history[name] || [];
353
- if (isFrameBudget) {
354
- history[name].push((current[name] || 0.0) / 120.0); // 120ms budget
355
- } else {
356
- history[name].push(current[name] || 0.0);
357
- }
358
-
359
- // Trim history
360
- if (history[name].length > canvasSize.width) {
361
- history[name] = history[name].slice(history[name].length - canvasSize.width);
362
- }
363
-
364
- // Draw graph
365
- const canvas = graphs[graphName];
366
- const ctx = canvas.getContext("2d");
367
- const [r,g,b] = colors[name];
368
- const opacity = isFrameBudget || active[name] ? 1.0 : 0.5;
369
-
370
- if (isFirst) {
371
- // Clear canvas on first draw
372
- ctx.clearRect(0, 0, canvas.width, canvas.height);
373
- isFirst = false;
374
- }
375
-
376
- ctx.strokeStyle = `rgba(${r},${g},${b},${opacity})`;
377
- ctx.fillStyle = `rgba(${r},${g},${b},${opacity/2})`;
378
- ctx.lineWidth = graphLineWidth;
379
-
380
- // Draw from left to right (the frame shifts right to left)
381
- ctx.beginPath();
382
- let lastX;
383
- for (let i = 0; i < history[name].length; i++) {
384
- const x = i;
385
- const y = canvas.height - history[name][i] * canvas.height;
386
- if (i === 0) {
387
- ctx.moveTo(1, y);
388
- } else {
389
- ctx.lineTo(x, y);
390
- }
391
- lastX = x;
392
- }
393
- // extend downwards to make a polygon
394
- ctx.lineTo(lastX, canvas.height);
395
- ctx.lineTo(0, canvas.height);
396
- ctx.closePath();
397
- ctx.fill();
398
- ctx.stroke();
399
- }
400
- }
401
-
402
- // Request next frame
403
- requestAnimationFrame(draw);
404
- };
405
-
406
- /** Start the loop */
407
- requestAnimationFrame(draw);
408
- </script>
409
- </html>