benjamin-paine
commited on
Update README.md
Browse files
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).
|