mic-ts
Version:
A simple stream wrapper for arecord (Linux (including Raspbian)) and sox (Mac/Windows). Returns a Passthrough stream object so that stream control like pause(), resume(), pipe(), etc. are all available.
181 lines • 6.02 kB
JavaScript
import { spawn } from "child_process";
import { type } from "os";
import { PassThrough } from "stream";
import { IsSilence } from "./IsSilence.js";
export class Mic {
}
export class MicImpl {
audioProcess = null;
infoStream = new PassThrough();
audioStream;
debug;
arecordEncoding;
arecordEndian;
bitwidth;
arecordFormat;
endian;
channels;
rate;
encoding;
fileType;
device;
constructor(options = {}) {
this.debug = options.debug || false;
this.audioStream = new IsSilence({ debug: this.debug });
const exitOnSilence = options.exitOnSilence || 0;
this.audioStream.setNumSilenceFramesExitThresh(parseInt(`${exitOnSilence}`, 10));
this.endian = options.endian || "little";
this.arecordEndian = this.endian === "big" ? "BE" : "LE";
this.encoding = options.encoding || "signed-integer";
this.arecordEncoding = this.encoding === "unsigned-integer" ? "U" : "S";
this.bitwidth = options.bitwidth || "16";
this.arecordFormat = `${this.arecordEncoding}${this.bitwidth}_${this.arecordEndian}`;
this.channels = options.channels || "1";
this.rate = options.rate || "16000";
this.fileType = options.fileType || "raw";
this.device = options.device || "plughw:1,0";
if (this.debug) {
this.infoStream.on("data", function (data) {
console.log("Received Info: " + data);
});
this.infoStream.on("error", function (error) {
console.log("Error in Info Stream: " + error);
});
}
}
getAudioProcessOptions() {
if (this.debug) {
return {
stdio: ["ignore", "pipe", "pipe"],
};
}
return {
stdio: ["ignore", "pipe", "ignore"],
};
}
start() {
if (this.audioProcess === null) {
let command;
let args;
if (type() === "Windows_NT") {
command = "sox";
args = [
"-b",
this.bitwidth,
"--endian",
this.endian,
"-c",
this.channels,
"-r",
this.rate,
"-e",
this.encoding,
"-t",
"waveaudio",
"default",
"-p",
];
}
else if (type() === "Darwin") {
command = "rec";
args = [
"-b",
this.bitwidth,
"--endian",
this.endian,
"-c",
this.channels,
"-r",
this.rate,
"-e",
this.encoding,
"-t",
this.fileType,
"-",
];
}
else {
command = "arecord";
args = [
"-t",
this.fileType,
"-c",
this.channels,
"-r",
this.rate,
"-f",
this.arecordFormat,
"-D",
this.device,
];
}
this.audioProcess = spawn(command, args, this.getAudioProcessOptions());
this.audioProcess.on("error", (err) => {
let message = `Error starting audio process "${command}": ${err.message}`;
if (err.code === "ENOENT") {
message = `Audio command "${command}" not found. Please install it and ensure it's in your PATH.`;
}
const errorObj = new Error(message);
if (this.audioStream.listenerCount("error") > 0) {
this.audioStream.emit("error", errorObj);
}
else {
console.error(message);
}
});
this.audioProcess.on("exit", (code, sig) => {
if (code != null && sig === null) {
this.audioStream.emit("audioProcessExitComplete");
if (this.debug)
console.log("recording audioProcess has exited with code = %d", code);
}
});
if (this.audioProcess.stdout) {
this.audioProcess.stdout.pipe(this.audioStream);
}
if (this.debug && this.audioProcess.stderr) {
this.audioProcess.stderr.pipe(this.infoStream);
}
this.audioStream.emit("startComplete");
}
else {
if (this.debug) {
throw new Error("Duplicate calls to start(): Microphone already started!");
}
}
}
stop() {
if (this.audioProcess != null) {
this.audioProcess.kill("SIGTERM");
this.audioProcess = null;
this.audioStream.emit("stopComplete");
if (this.debug) {
console.log("Microphone stopped");
}
}
}
pause() {
if (this.audioProcess != null) {
this.audioProcess.kill("SIGSTOP");
this.audioStream.pause();
this.audioStream.emit("pauseComplete");
if (this.debug) {
console.log("Microphone paused");
}
}
}
resume() {
if (this.audioProcess != null) {
this.audioProcess.kill("SIGCONT");
this.audioStream.resume();
this.audioStream.emit("resumeComplete");
if (this.debug) {
console.log("Microphone resumed");
}
}
}
getAudioStream() {
return this.audioStream;
}
}
//# sourceMappingURL=Mic.js.map