mime-chat/src/background/audio.ts
Andrew Gundersen 0f987bc481 enhancements
2021-04-12 08:03:04 -05:00

179 lines
4.1 KiB
TypeScript

/* eslint @typescript-eslint/no-var-requires: "off" */
"use strict";
import { ipcMain } from "electron";
import { backgroundMitt } from '@/modules/emitter';
const portAudio = require('naudiodon');
// Audio in and out stream objects.
let ai: typeof portAudio.AudioIO | boolean = false;
let ao: typeof portAudio.AudioIO | boolean = false;
// Whether activly recording.
let record = false;
const audioContainer = {
input: '',
}
const audioOptions = {
channelCount: 1,
sampleFormat: 16,
sampleRate: 16000,
deviceId: -1,
closeOnError: false,
}
// Toggles record to true to begin capturing chunks.
const onRecordingStart = (_event: any, _payload: any) => {
console.log("AUDIO: Beginning audio capture.")
record = true;
}
// Returns recorded audio to frontend and sets record to false.
const onRecordingEnd = async (_event: any, payload: any) => {
console.log("AUDIO:Sending audio to browser.")
return new Promise((resolve, reject) => {
try {
resolve(audioContainer.input);
record = false;
} catch (e) {
reject()
}
});
};
// Main audio function run by run.ts module.
export function initAudioIO(): void {
console.log("AUDIO:Starting io streams.")
if (!ai) {
// Initialize and start input stream.
ai = new portAudio.AudioIO({ inOptions: audioOptions });
ai.setEncoding("hex");
ai.start();
// On each data chunk...
ai.on('data', (chunk: string) => {
// If recording, we capture the data.
if (record) {
console.log('AUDIO:Recording...')
audioContainer.input += chunk;
}
// Else, we don't capture and also clear audioContainer.
else {
if (audioContainer.input.length) {
audioContainer.input = "";
}
}
});
}
if (!ao) {
// Initialize and start input stream.
ao = new portAudio.AudioIO({ outOptions: audioOptions });
ao.start();
}
// Listen to record.
console.log("AUDIO:Adding recording listeners.")
ipcMain.removeAllListeners("start-recording");
ipcMain.on("start-recording", onRecordingStart);
ipcMain.removeHandler("stop-recording");
ipcMain.handle("stop-recording", onRecordingEnd);
}
// ---Audio playback--------------------------------------------
// Split Buffer into an array of len-sized Buffers.
function bufSplit(buf: Buffer, len: number): Array<Buffer> {
const chunks = [];
let i = 0;
let L = len;
while(i < buf.byteLength) {
chunks.push(buf.slice(i, L));
i = L;
L += len;
}
return chunks;
}
// Audio playback.
export function play(input: string): void {
// Format the audio.
const audio = bufSplit(
Buffer.from(input as string, 'hex'),
8192
);
// Called on end of write.
const callback = () => {
// We stop audio playback anim.
backgroundMitt.emit('ipc-renderer', {
endpoint: 'stop-playback-anim'
});
}
write();
// Iterate through audio array and write buffers to portAudio writable.
function write() {
let chunk: Buffer;
let ok = true;
let i = 0;
do {
chunk = audio[i];
if (i === audio.length - 1) {
// write last chunk.
ao.write(chunk, null, callback);
} else {
// check for backpreassure.
ok = ao.write(chunk, null);
}
i++;
} while (i < audio.length && ok);
if (i < audio.length) {
// Had to stop early!
// Write some more once it drains.
ao.once('drain', write);
}
}
}
// -------------------------------------------------------------
// Get's called on window close.
// export function stopStream() {
// console.log("AUDIO:Stopping audio stream.")
// if (ai) {
// ai.quit()
// }
// if (ao) {
// ao.quit()
// }
// console.log("AUDIO:Audio closed.")
// }