benjamin-paine commited on
Commit
82fb427
·
verified ·
1 Parent(s): 0a647f3

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +130 -0
README.md CHANGED
@@ -432,6 +432,136 @@ predictions = thread.get( # same arguments as queue.Queue.get()
432
  first_audio_has_wakeword = predictions[0] # bool by default
433
  ```
434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  # License
436
 
437
  - HeyBuddy source code and pretrained models are released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).
 
432
  first_audio_has_wakeword = predictions[0] # bool by default
433
  ```
434
 
435
+ # Errata
436
+
437
+ These are some potentially useful JavaScript snippets for working with raw audio samples.
438
+
439
+ ```js
440
+ /**
441
+ * Play audio samples using the Web Audio API.
442
+ * @param {Float32Array} audioSamples - The audio samples to play.
443
+ * @param {number} sampleRate - The sample rate of the audio samples.
444
+ */
445
+ function playAudioSamples(audioSamples, sampleRate = 16000) {
446
+ // Create an AudioContext
447
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
448
+
449
+ // Create an AudioBuffer
450
+ const audioBuffer = audioContext.createBuffer(
451
+ 1, // number of channels
452
+ audioSamples.length, // length of the buffer in samples
453
+ sampleRate // sample rate (samples per second)
454
+ );
455
+
456
+ // Fill the AudioBuffer with the Float32Array of audio samples
457
+ audioBuffer.getChannelData(0).set(audioSamples);
458
+
459
+ // Create a BufferSource node
460
+ const source = audioContext.createBufferSource();
461
+ source.buffer = audioBuffer;
462
+
463
+ // Connect the source to the AudioContext's destination (the speakers)
464
+ source.connect(audioContext.destination);
465
+
466
+ // Start playback
467
+ source.start();
468
+ };
469
+
470
+ /**
471
+ * Turns floating-point audio samples to a Wave blob.
472
+ * @param {Float32Array} audioSamples - The audio samples to play.
473
+ * @param {number} sampleRate - The sample rate of the audio samples.
474
+ * @param {number} numChannels - The number of channels in the audio. Defaults to 1 (mono).
475
+ * @return {Blob} A blob of type `audio/wav`
476
+ */
477
+ function samplesToBlob(audioSamples, sampleRate = 16000, numChannels = 1) {
478
+ // Helper to write a string to the DataView
479
+ const writeString = (view, offset, string) => {
480
+ for (let i = 0; i < string.length; i++) {
481
+ view.setUint8(offset + i, string.charCodeAt(i));
482
+ }
483
+ };
484
+
485
+ // Helper to convert Float32Array to Int16Array (16-bit PCM)
486
+ const floatTo16BitPCM = (output, offset, input) => {
487
+ for (let i = 0; i < input.length; i++, offset += 2) {
488
+ let s = Math.max(-1, Math.min(1, input[i])); // Clamping to [-1, 1]
489
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); // Convert to 16-bit PCM
490
+ }
491
+ };
492
+
493
+ const byteRate = sampleRate * numChannels * 2; // 16-bit PCM = 2 bytes per sample
494
+
495
+ // Calculate sizes
496
+ const blockAlign = numChannels * 2; // 2 bytes per sample for 16-bit audio
497
+ const wavHeaderSize = 44;
498
+ const dataLength = audioSamples.length * numChannels * 2; // 16-bit PCM data length
499
+ const buffer = new ArrayBuffer(wavHeaderSize + dataLength);
500
+ const view = new DataView(buffer);
501
+
502
+ // Write WAV file headers
503
+ writeString(view, 0, 'RIFF'); // ChunkID
504
+ view.setUint32(4, 36 + dataLength, true); // ChunkSize
505
+ writeString(view, 8, 'WAVE'); // Format
506
+ writeString(view, 12, 'fmt '); // Subchunk1ID
507
+ view.setUint32(16, 16, true); // Subchunk1Size (PCM = 16)
508
+ view.setUint16(20, 1, true); // AudioFormat (PCM = 1)
509
+ view.setUint16(22, numChannels, true); // NumChannels
510
+ view.setUint32(24, sampleRate, true); // SampleRate
511
+ view.setUint32(28, byteRate, true); // ByteRate
512
+ view.setUint16(32, blockAlign, true); // BlockAlign
513
+ view.setUint16(34, 16, true); // BitsPerSample (16-bit PCM)
514
+ writeString(view, 36, 'data'); // Subchunk2ID
515
+ view.setUint32(40, dataLength, true); // Subchunk2Size
516
+
517
+ // Convert the Float32Array audio samples to 16-bit PCM and write them to the DataView
518
+ floatTo16BitPCM(view, wavHeaderSize, audioSamples);
519
+
520
+ // Create and return the Blob
521
+ return new Blob([view], { type: 'audio/wav' });
522
+ }
523
+
524
+ /**
525
+ * Renders a blob to an audio element with controls.
526
+ * Use `appendChild(result)` to add to the document or a node.
527
+ * @param {Blob} audioBlob - A blob with a valid audio type.
528
+ * @see samplesToBlob
529
+ */
530
+ function blobToAudio(audioBlob) {
531
+ // Create data URL
532
+ const url = URL.createObjectURL(audioBlob);
533
+
534
+ // Create and configure audio element
535
+ const audio = document.createElement("audio");
536
+ audio.controls = true;
537
+ audio.src = url;
538
+ return audio;
539
+ }
540
+
541
+ /**
542
+ * Downloads a blob as a file.
543
+ * @param {Blob} blob - A blob with a type that can be converted to an object URL.
544
+ * @param {string} filename - The file name after downloading.
545
+ */
546
+ function downloadBlob(blob, filename) {
547
+ // Create data URL
548
+ const url = URL.createObjectURL(blob);
549
+
550
+ // Create and configure link element
551
+ const link = document.createElement("a");
552
+ link.href = url;
553
+ link.setAttribute("download", filename);
554
+ link.style.display = "none";
555
+
556
+ // Add the link to the page, click, then remove
557
+ document.body.appendChild(link);
558
+ window.requestAnimationFrame(() => {
559
+ link.dispatchEvent(new MouseEvent("click"));
560
+ document.body.removeChild(link);
561
+ });
562
+ }
563
+ ```
564
+
565
  # License
566
 
567
  - HeyBuddy source code and pretrained models are released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html).