UNPKG

ai-code-writer

Version:

An AI code writer application using OpenAI APIs for audio transcription and chat completion.

217 lines (216 loc) 8.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = require("stream"); const node_web_audio_api_1 = require("node-web-audio-api"); const node_microphone_1 = __importDefault(require("node-microphone")); const debug = false; class RecordingHandler { constructor(resolve, reject, config, silenceThreshold, newSilenceThresholdCallback) { this.resolve = resolve; this.reject = reject; this.config = config; this.silenceThreshold = silenceThreshold; this.newSilenceThresholdCallback = newSilenceThresholdCallback; this.chunks = []; this.isStopped = false; this.passThrough = new stream_1.PassThrough(); this.audioContext = new node_web_audio_api_1.AudioContext(); this.silenceDuration = 0; this.audioInputDetected = false; this.isSilenceLevelAdjusted = false; this.silenceLevels = []; this.firstFrameSkipped = false; this.registerEvents(); } startRecording() { return __awaiter(this, void 0, void 0, function* () { this.isSilenceLevelAdjusted = this.silenceThreshold != 0; try { this.microphoneInstance = new node_microphone_1.default(); const micStream = this.microphoneInstance.startRecording(); micStream.on('data', (chunk) => { this.handleData(chunk); }); micStream.on('end', () => this.streamEnded()); micStream.on('error', (error) => this.onError(error)); this.waitTimeout = setTimeout(() => { if (!this.audioInputDetected) { this.chunks = []; this.stopRecording(); } }, this.config.MAX_WAIT_DURATION); } catch (error) { this.reject(error); } }); } stopRecording() { this.isStopped = true; if (this.microphoneInstance) { this.microphoneInstance.stopRecording(); this.microphoneInstance = undefined; } if (!this.passThrough.destroyed) { this.passThrough.end(); } this.audioContext.close().catch((error) => console.error('Error closing audio context:', error)); } handleData(chunk) { this.storeChunk(chunk); if (!this.firstFrameSkipped) { this.skipFirstFrame(); return; } if (this.isStopped) return; if (!this.isSilenceLevelAdjusted) this.adjustSilenceLevel(chunk); this.processAudioChunk(chunk); } storeChunk(chunk) { this.chunks.push(chunk); if (this.passThrough.writable && !this.passThrough.writableEnded && !this.passThrough.writableFinished) { this.passThrough.write(chunk); } } skipFirstFrame() { this.firstFrameSkipped = true; } processAudioChunk(chunk) { const isSilent = this.isSilenceLevelAdjusted && this.isSilentChunk(chunk); if (isSilent) { this.incrementSilenceDuration(chunk); } else { this.resetSilenceDuration(); } if (this.shouldStopRecording()) { this.stopRecording(); } if (debug) this.logDebugInfo(chunk, isSilent); } incrementSilenceDuration(chunk) { this.silenceDuration += chunk.length / this.audioContext.sampleRate * 1000; } resetSilenceDuration() { this.silenceDuration = 0; if (!this.audioInputDetected && this.isSilenceLevelAdjusted) { this.audioInputDetected = true; clearTimeout(this.waitTimeout); } } shouldStopRecording() { return this.silenceDuration >= this.config.MAX_SILENCE_DURATION && this.audioInputDetected; } logDebugInfo(chunk, isSilent) { console.log('Audio:', this.getAudioLevel(chunk), 'Threshold:', this.silenceThreshold, 'isSilent:', isSilent, 'audioInputDetected:', this.audioInputDetected); } adjustSilenceLevel(chunk) { if (this.silenceLevels.length < this.config.SILENCE_DETECTION_WINDOW) { this.silenceLevels.push(this.getAudioLevel(chunk)); } else { this.sanitizeHighLevelStartingValue(); this.silenceThreshold = this.silenceLevels.reduce((a, b) => a + b, 0) / this.silenceLevels.length * this.config.silenceThresholdMultiplier; this.isSilenceLevelAdjusted = true; this.newSilenceThresholdCallback(this.silenceThreshold); } } sanitizeHighLevelStartingValue() { this.silenceLevels.shift(); } isSilentChunk(chunk) { const avg = this.getAudioLevel(chunk); return avg <= this.silenceThreshold; } getAudioLevel(chunk) { const int16Array = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.byteLength / Int16Array.BYTES_PER_ELEMENT); return Math.sqrt(int16Array.reduce((sum, value) => sum + value * value, 0) / int16Array.length); } registerEvents() { this.passThrough.on('end', this.onEnd.bind(this)); this.passThrough.on('error', this.onError.bind(this)); } streamEnded() { return __awaiter(this, void 0, void 0, function* () { if (!this.isStopped) { this.stopRecording(); } else { this.onEnd(); } }); } onEnd() { if (!this.isStopped) { this.stopRecording(); } this.resolve(Buffer.concat(this.chunks)); this.isStopped = true; } onError(error) { if (!this.isStopped) { this.stopRecording(); } this.reject(error); } } class NodeAudioRecorder { constructor(config) { this.config = config; this.silenceThreshold = 0; } startRecording() { return __awaiter(this, void 0, void 0, function* () { if (this.currentRecordingHandler) { this.currentRecordingHandler.stopRecording(); } return new Promise((resolve, reject) => { const handler = new RecordingHandler(resolve, reject, this.config, this.silenceThreshold, this.changeSilenceThreshold.bind(this)); this.currentRecordingHandler = handler; handler.startRecording().catch((error) => { reject(error); }); }); }); } stopRecording() { return __awaiter(this, void 0, void 0, function* () { if (this.currentRecordingHandler) { this.currentRecordingHandler.stopRecording(); this.currentRecordingHandler = undefined; } }); } measureNoiseLevel() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { const handler = new RecordingHandler(() => resolve(), reject, this.config, this.silenceThreshold, this.changeSilenceThreshold.bind(this)); handler.startRecording().catch((error) => { reject(error); }); setTimeout(() => { handler.stopRecording(); }, this.config.NOISE_MEASUREMENT_DURATION); }); }); } changeSilenceThreshold(newValue) { this.silenceThreshold = newValue; } } exports.default = NodeAudioRecorder;