Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable cloud reco without hotword detection #103

Open
wants to merge 32 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
05ff996
Add ioBroker to "used in" list.
GermanBluefox May 18, 2019
f777cbf
1.0.2
evancohen Jun 3, 2019
2042ca7
1.0.3
evancohen Jun 3, 2019
efb3cfb
Add edit link to README
evancohen Jun 8, 2019
24094a8
enable cloud detection without hotword detection, single phrase
sdetweil Jun 11, 2020
ba51948
remove blank last line
sdetweil Jun 11, 2020
d6a4f83
missing }
sdetweil Jun 11, 2020
f18e906
add detecvtor on error handler, catch errors from noise transitions
sdetweil Jun 12, 2020
51c79fb
unpipe mic on stop, so detector doesn't fatal throw
sdetweil Jun 15, 2020
7bf71b8
fix ArecordHelper restart to avoid recursion
sdetweil Aug 8, 2020
d3587a5
update google assistant version
sdetweil Mar 13, 2021
f516e46
update version number
sdetweil Mar 13, 2021
a5209c6
use latest snowboy
sdetweil Mar 17, 2021
faf3621
fix package.json synatx error
sdetweil Mar 17, 2021
8676395
add timeout restart delay and move flag for restarting recursion
sdetweil Mar 28, 2021
2a99755
Merge branch 'master' of https://github.com/sdetweil/sonus
sdetweil Mar 28, 2021
122c4af
add call out to waitRunning to insure arecord has stopped before rest…
sdetweil May 15, 2021
9c3c84a
fix package to pre-install cblas
sdetweil Oct 24, 2021
4d63bb0
add python to preinstall
sdetweil Oct 24, 2021
30d2972
Update package.json
sdetweil Apr 6, 2024
c182553
Update package.json
sdetweil Apr 6, 2024
be46749
Update package.json
sdetweil Jul 1, 2024
c1d53c7
import from EXT-Detector
bugsounet Jul 2, 2024
1f3872f
chmod +x postinstall
bugsounet Jul 2, 2024
7afad0f
add binding.gyp
bugsounet Jul 2, 2024
28fa727
update path
bugsounet Jul 2, 2024
f86b67a
Fix package-lock.json
bugsounet Jul 2, 2024
ea95c70
add gitignore (to finalize)
bugsounet Jul 2, 2024
61071f8
correct path // add common.res
bugsounet Jul 2, 2024
e554729
try to fix path
bugsounet Jul 2, 2024
79f9bd4
Merge pull request #1 from bugsounet/master
sdetweil Jul 2, 2024
14093b5
fix paths
sdetweil Jul 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Logs
logs
*.log
.editorconfig
npm-debug.log*

# Runtime data
Expand Down Expand Up @@ -42,3 +43,5 @@ jspm_packages
# Keyfile
keyfile.json
/.idea/
/snowboy/lib/node/index.js
/components/lib/node/binding/Release/node-v127-linux-x64/snowboy.node
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ hotword. Hotword training must occur online through their web service.

## Built [#withsonus](https://twitter.com/hashtag/withsonus?src=github)
- [L.I.S.A. - Home automation project](https://github.com/mylisabox/lisa-box)
- [ioBroker - Home automation project](https://github.com/ioBroker/ioBroker.sonus)

*If you've build a project with Sonus send a PR and include it here!*
*If you've build a project with Sonus [send a PR](https://github.com/evancohen/sonus/edit/master/README.md) and include it here!*

## Authors
Evan Cohen: [@_evnc](https://twitter.com/_evnc)
Expand Down
85 changes: 85 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
'targets': [{
'target_name': 'snowboy',
'sources': [
'snowboy/lib/snowboy.cc'
],
'conditions': [
['OS=="mac"', {
'link_settings': {
'libraries': [
'<(module_root_dir)/snowboy/lib/osx/libsnowboy-detect.a',
]
}
}],
['OS=="linux" and target_arch=="x64"', {
'link_settings': {
'ldflags': [
'-Wl,--no-as-needed',
],
'libraries': [
'<(module_root_dir)/snowboy/lib/x64/libsnowboy-detect.a',
]
}
}],
['OS=="linux" and target_arch=="arm"', {
'link_settings': {
'ldflags': [
'-Wl,--no-as-needed',
],
'libraries': [
'<(module_root_dir)/snowboy/lib/rpi/libsnowboy-detect.a',
]
}
}],
['OS=="linux" and target_arch=="arm64"', {
'link_settings': {
'ldflags': [
'-Wl,--no-as-needed',
],
'libraries': [
'<(module_root_dir)/snowboy/lib/arm64/libsnowboy-detect.a',
]
}
}]
],
'cflags': [
'-std=c++11',
'-fexceptions',
'-Wall',
'-D_GLIBCXX_USE_CXX11_ABI=0'
],
'cflags!': [
'-fno-exceptions'
],
'cflags_cc!': [
'-fno-exceptions'
],
'include_dirs': [
"<!(node -e \"require('nan')\")",
"<!(pwd)/snowboy/include"
],
'libraries': [
'-lcblas'
],
'xcode_settings': {
'MACOSX_DEPLOYMENT_TARGET': '10.11',
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
'OTHER_CFLAGS': [
'-std=c++11',
'-stdlib=libc++'
]
}
},
{
"target_name": "action_after_build",
"type": "none",
"dependencies": [ "<(module_name)" ],
"copies": [
{
"files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
"destination": "<(module_path)"
}
]
}]
}
120 changes: 76 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const record = require('node-record-lpcm16')
const stream = require('stream')
const { Detector, Models } = require('snowboy')
const { waitRunning } = require('waitprocess')

const ERROR = {
NOT_STARTED: "NOT_STARTED",
Expand All @@ -11,7 +11,12 @@ const ERROR = {

const ARECORD_FILE_LIMIT = 1500000000 // 1.5 GB

//const ARECORD_FILE_LIMIT = 1500000

let restarting=false;

const CloudSpeechRecognizer = {}

CloudSpeechRecognizer.init = recognizer => {
const csr = new stream.Writable()
csr.listening = false
Expand Down Expand Up @@ -76,54 +81,52 @@ CloudSpeechRecognizer.startStreaming = (options, audioStream, cloudSpeechRecogni
}

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'
sonus.opts = opts
sonus.csr = csr

// if not doing hotword detection
if(opts.hotwords!=-1){
const { Detector, Models } = require('./snowboy/lib/node/index.js')
Sonus.annyang = require('./lib/annyang-core.js')
const models = new Models()
// 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/sonus/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
// defaults
opts.models = models
opts.resource = opts.resource || 'node_modules/sonus/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)
const detector = sonus.detector = new Detector(opts)

detector.on('silence', () => sonus.emit('silence'))
detector.on('sound', () => sonus.emit('sound'))
detector.on('silence', () => sonus.emit('silence'))
detector.on('sound', () => sonus.emit('sound'))
detector.on('error', () => {})

// 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)
})
// When a hotword is detected pipe the audio stream to speech detection
detector.on('hotword', (index, hotword) => {
sonus.trigger(index, hotword)
})

sonus.trigger = (index, hotword) => {
sonus.trigger = (index, hotword) => {
if (sonus.started) {
try {
let triggerHotword = (index == 0) ? hotword : models.lookup(index)
Expand All @@ -136,6 +139,17 @@ Sonus.init = (options, recognizer) => {
throw ERROR.NOT_STARTED
}
}
}

// 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)
if(sonus.opts.hotwords!==-1)
Sonus.annyang.trigger(transcript)
})


sonus.pause = () => {
record.pause()
Expand All @@ -154,8 +168,10 @@ Sonus.start = sonus => {
if(sonus.recordProgram === "arecord"){
ArecordHelper.init(sonus)
}

sonus.mic.pipe(sonus.detector)
if(sonus.opts.hotwords !== -1)
sonus.mic.pipe(sonus.detector)
else
CloudSpeechRecognizer.startStreaming(sonus.opts, sonus.mic, sonus.csr)
sonus.started = true
}

Expand All @@ -174,25 +190,37 @@ ArecordHelper.init = (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)
if(!restarting){
restarting=true;
ArecordHelper.restart(sonus)
}
}
})
}

ArecordHelper.restart = (sonus) => {
sonus.mic.unpipe(sonus.detector)
record.stop()

// Restart the audio recording
sonus.mic = Recorder(sonus)
// reset the counter before we get another buffer of data over the limit
ArecordHelper.byteCount = 0
ArecordHelper.track(sonus)
sonus.mic.pipe(sonus.detector)
if(sonus.opts.hotwords!==-1)
sonus.mic.unpipe(sonus.detector)
record.stop()
// wait a little, let existing process exit
// make sure the pcm recorder actually has stopped
waitRunning(sonus.recordProgram,false,8*1000).then(()=>{
// Restart the audio recording
sonus.mic = Recorder(sonus)
ArecordHelper.track(sonus)
if(sonus.opts.hotwords!==-1)
sonus.mic.pipe(sonus.detector)
restarting=false
})
}

Sonus.trigger = (sonus, index, hotword) => sonus.trigger(index, hotword)
Expand All @@ -201,6 +229,10 @@ Sonus.pause = () => record.pause()

Sonus.resume = () => record.resume()

Sonus.stop = () => record.stop()
Sonus.stop = (sonus) => {
if(sonus.detector)
sonus.mic.unpipe(sonus.detector)
record.stop()
}

module.exports = Sonus
Loading