UNPKG

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
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