UNPKG

sonus

Version:

Open source cross platform decentralized always-on speech recognition framework

207 lines (170 loc) 5.61 kB
'use strict' const record = require('node-record-lpcm16') const stream = require('stream') const { Detector, Models } = require('snowboy') const ERROR = { NOT_STARTED: "NOT_STARTED", INVALID_INDEX: "INVALID_INDEX" } const ARECORD_FILE_LIMIT = 1500000000 // 1.5 GB const CloudSpeechRecognizer = {} CloudSpeechRecognizer.init = recognizer => { const csr = new stream.Writable() csr.listening = false csr.recognizer = recognizer return csr } CloudSpeechRecognizer.startStreaming = (options, audioStream, cloudSpeechRecognizer) => { if (cloudSpeechRecognizer.listening) { return } let hasResults = false cloudSpeechRecognizer.listening = true const request = { config: { encoding: 'LINEAR16', sampleRateHertz: 16000, languageCode: options.language, speechContexts: options.speechContexts || null }, singleUtterance: true, interimResults: true, } const recognitionStream = cloudSpeechRecognizer.recognizer .streamingRecognize(request) .on('error', err => { cloudSpeechRecognizer.emit('error', err) stopStream() }) .on('data', data => { if (data.results[0] && data.results[0].alternatives[0]) { hasResults = true; // Emit partial or final results and end the stream if (data.error) { cloudSpeechRecognizer.emit('error', data.error) stopStream() } else if (data.results[0].isFinal) { cloudSpeechRecognizer.emit('final-result', data.results[0].alternatives[0].transcript) stopStream() } else { cloudSpeechRecognizer.emit('partial-result', data.results[0].alternatives[0].transcript) } } else { // Reached transcription time limit if(!hasResults){ cloudSpeechRecognizer.emit('final-result', '') } stopStream() } }) const stopStream = () => { cloudSpeechRecognizer.listening = false audioStream.unpipe(recognitionStream) recognitionStream.end() } audioStream.pipe(recognitionStream) } const Sonus = {} Sonus.annyang = require('./lib/annyang-core.js') Sonus.init = (options, recognizer) => { // don't mutate options const opts = Object.assign({}, options), models = new Models(), sonus = new stream.Writable(), csr = CloudSpeechRecognizer.init(recognizer) sonus.mic = {} sonus.recordProgram = opts.recordProgram sonus.device = opts.device sonus.started = false // If we don't have any hotwords passed in, add the default global model opts.hotwords = opts.hotwords || [1] opts.hotwords.forEach(model => { models.add({ file: model.file || 'node_modules/snowboy/resources/snowboy.umdl', sensitivity: model.sensitivity || '0.5', hotwords: model.hotword || 'default' }) }) // defaults opts.models = models opts.resource = opts.resource || 'node_modules/snowboy/resources/common.res' opts.audioGain = opts.audioGain || 2.0 opts.language = opts.language || 'en-US' //https://cloud.google.com/speech/docs/languages const detector = sonus.detector = new Detector(opts) detector.on('silence', () => sonus.emit('silence')) detector.on('sound', () => sonus.emit('sound')) // When a hotword is detected pipe the audio stream to speech detection detector.on('hotword', (index, hotword) => { sonus.trigger(index, hotword) }) // Handel speech recognition requests csr.on('error', error => sonus.emit('error', { streamingError: error })) csr.on('partial-result', transcript => sonus.emit('partial-result', transcript)) csr.on('final-result', transcript => { sonus.emit('final-result', transcript) Sonus.annyang.trigger(transcript) }) sonus.trigger = (index, hotword) => { if (sonus.started) { try { let triggerHotword = (index == 0) ? hotword : models.lookup(index) sonus.emit('hotword', index, triggerHotword) CloudSpeechRecognizer.startStreaming(opts, sonus.mic, csr) } catch (e) { throw ERROR.INVALID_INDEX } } else { throw ERROR.NOT_STARTED } } sonus.pause = () => { record.pause() } sonus.resume = () => { record.resume() } return sonus } Sonus.start = sonus => { sonus.mic = Recorder(sonus) if(sonus.recordProgram === "arecord"){ ArecordHelper.init(sonus) } sonus.mic.pipe(sonus.detector) sonus.started = true } const Recorder = (sonus) => { return record.start({ threshold: 0, device: sonus.device || null, recordProgram: sonus.recordProgram || "rec", verbose: false }) } const ArecordHelper = {byteCount: 0} ArecordHelper.init = (sonus) => { ArecordHelper.track(sonus) } ArecordHelper.track = (sonus) => { sonus.mic.on('data', data => { ArecordHelper.byteCount += data.length // When we get to arecord wav file limit, reset if(ArecordHelper.byteCount > ARECORD_FILE_LIMIT){ ArecordHelper.restart(sonus) } }) } ArecordHelper.restart = (sonus) => { sonus.mic.unpipe(sonus.detector) record.stop() // Restart the audio recording sonus.mic = Recorder(sonus) ArecordHelper.byteCount = 0 ArecordHelper.track(sonus) sonus.mic.pipe(sonus.detector) } Sonus.trigger = (sonus, index, hotword) => sonus.trigger(index, hotword) Sonus.pause = () => record.pause() Sonus.resume = () => record.resume() Sonus.stop = () => record.stop() module.exports = Sonus