@@ -0,0 +1,4 @@ |
||
1 |
+/node_modules |
|
2 |
+/lib |
|
3 |
+.DS_Store |
|
4 |
+sample.wav |
@@ -0,0 +1,77 @@ |
||
1 |
+var Speakable = require('./node_modules/speakable'); |
|
2 |
+var speakable = new Speakable({key: 'AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw'}, {lang: 'en-US'}); |
|
3 |
+ |
|
4 |
+var say = require('say'); |
|
5 |
+ |
|
6 |
+var play = require('./node_modules/play'); |
|
7 |
+ |
|
8 |
+var io = require('socket.io-client'); |
|
9 |
+var socket = io.connect('http://localhost:9090'); |
|
10 |
+ |
|
11 |
+var dialogue_messages = []; |
|
12 |
+var talking = false; |
|
13 |
+ |
|
14 |
+socket.on('connect', function(){ |
|
15 |
+ console.log('Connected to server') |
|
16 |
+ speakable.recordVoice(); |
|
17 |
+}); |
|
18 |
+ |
|
19 |
+socket.on('message', function(data){ |
|
20 |
+ dialogue_messages.push(data) |
|
21 |
+ if(!talking){ |
|
22 |
+ talking = true |
|
23 |
+ speak(); |
|
24 |
+ } |
|
25 |
+ |
|
26 |
+}); |
|
27 |
+ |
|
28 |
+socket.on('disconnect', function(){ |
|
29 |
+ speakable.resetVoice(); |
|
30 |
+}); |
|
31 |
+ |
|
32 |
+ |
|
33 |
+ |
|
34 |
+speakable.on('speechStart', function() { |
|
35 |
+ console.log('onSpeechStart'); |
|
36 |
+}); |
|
37 |
+ |
|
38 |
+speakable.on('speechStop', function() { |
|
39 |
+ console.log('onSpeechStop'); |
|
40 |
+ play.sound('./computerbeep_15.mp3'); |
|
41 |
+}); |
|
42 |
+ |
|
43 |
+speakable.on('speechReady', function() { |
|
44 |
+ console.log('onSpeechReady'); |
|
45 |
+}); |
|
46 |
+ |
|
47 |
+speakable.on('error', function(err) { |
|
48 |
+ console.log('onError:'); |
|
49 |
+ console.log(err); |
|
50 |
+ speakable.recordVoice(); |
|
51 |
+}); |
|
52 |
+ |
|
53 |
+speakable.on('speechResult', function(recognizedWords) { |
|
54 |
+ console.log('onSpeechResult:') |
|
55 |
+ console.log(recognizedWords); |
|
56 |
+ if(recognizedWords != ""){ |
|
57 |
+ socket.emit('message', recognizedWords); |
|
58 |
+ } else { |
|
59 |
+ speakable.recordVoice(); |
|
60 |
+ } |
|
61 |
+}); |
|
62 |
+ |
|
63 |
+var speak = function() { |
|
64 |
+ say.speak(dialogue_messages[0], 'Alex', 1.0, function(err){ |
|
65 |
+ if (err) { |
|
66 |
+ return console.error(err); |
|
67 |
+ } |
|
68 |
+ dialogue_messages.shift(); |
|
69 |
+ if(dialogue_messages.length > 0) { |
|
70 |
+ speak(); |
|
71 |
+ } else { |
|
72 |
+ talking = false; |
|
73 |
+ speakable.resetVoice(); |
|
74 |
+ speakable.recordVoice(); |
|
75 |
+ } |
|
76 |
+ }); |
|
77 |
+} |
@@ -0,0 +1,131 @@ |
||
1 |
+// Wit.ai API |
|
2 |
+ |
|
3 |
+var EventEmitter = require('events').EventEmitter, |
|
4 |
+ util = require('util'), |
|
5 |
+ spawn = require('child_process').spawn, |
|
6 |
+ http = require('follow-redirects').http; |
|
7 |
+ https = require('follow-redirects').https; |
|
8 |
+ |
|
9 |
+var Speakable = function Speakable(credentials, options) { |
|
10 |
+ EventEmitter.call(this); |
|
11 |
+ |
|
12 |
+ options = options || {} |
|
13 |
+ |
|
14 |
+ this.recBuffer = []; |
|
15 |
+ this.recRunning = false; |
|
16 |
+ this.apiResult = {}; |
|
17 |
+ this.apiLang = options.lang || "en-US"; |
|
18 |
+ this.apiKey = credentials.key |
|
19 |
+ this.cmd = (options.sox_path) || 'sox'; |
|
20 |
+ this.cmdArgs = [ |
|
21 |
+ '-q', |
|
22 |
+ '-v', '0.98', |
|
23 |
+ '-b','16', |
|
24 |
+ '-d','-t','mp3','-', |
|
25 |
+ 'rate','8000','channels','1', |
|
26 |
+ 'silence','1','0.1',(options.threshold || '0.1')+'%2','1','1.0',(options.threshold || '0.1')+'%2' |
|
27 |
+ ]; |
|
28 |
+}; |
|
29 |
+ |
|
30 |
+util.inherits(Speakable, EventEmitter); |
|
31 |
+module.exports = Speakable; |
|
32 |
+ |
|
33 |
+Speakable.prototype.postVoiceData = function() { |
|
34 |
+ var self = this; |
|
35 |
+ |
|
36 |
+ var options = { |
|
37 |
+ hostname: 'api.wit.ai', |
|
38 |
+ path: '/speech', |
|
39 |
+ maxRedirects: 10, |
|
40 |
+ method: 'POST', |
|
41 |
+ headers: { |
|
42 |
+ "Authorization": "Bearer Q6NTRBF6CVKOGJUQFZGHASQSLKRAX4SX", |
|
43 |
+ 'Content-Type': 'audio/mpeg3' |
|
44 |
+ } |
|
45 |
+ }; |
|
46 |
+ |
|
47 |
+ var req = https.request(options, function(res) { |
|
48 |
+ self.recBuffer = []; |
|
49 |
+ res.setEncoding('utf8'); |
|
50 |
+ res.on('data', function (chunk) { |
|
51 |
+ console.log(chunk); |
|
52 |
+ self.apiResult = JSON.parse(chunk); |
|
53 |
+ }); |
|
54 |
+ |
|
55 |
+ if(res.statusCode !== 200) { |
|
56 |
+ return self.emit( |
|
57 |
+ 'error', |
|
58 |
+ 'Non-200 answer from Wit.ai Speech API (' + res.statusCode + ') \n' + JSON.stringify(res.headers) |
|
59 |
+ ); |
|
60 |
+ } |
|
61 |
+ res.on('end', function() { |
|
62 |
+ self.parseResult(); |
|
63 |
+ }); |
|
64 |
+ }); |
|
65 |
+ |
|
66 |
+ req.on('error', function(e) { |
|
67 |
+ self.emit('error', e); |
|
68 |
+ }); |
|
69 |
+ |
|
70 |
+ // write data to request body |
|
71 |
+ console.log('Posting voice data...'); |
|
72 |
+ for(var i in self.recBuffer) { |
|
73 |
+ if(self.recBuffer.hasOwnProperty(i)) { |
|
74 |
+ req.write(new Buffer(self.recBuffer[i],'binary')); |
|
75 |
+ console.log(new Buffer(self.recBuffer[i],'binary')) |
|
76 |
+ } |
|
77 |
+ } |
|
78 |
+ req.end(); |
|
79 |
+}; |
|
80 |
+ |
|
81 |
+Speakable.prototype.recordVoice = function() { |
|
82 |
+ var self = this; |
|
83 |
+ |
|
84 |
+ var rec = spawn(self.cmd, self.cmdArgs, { stdio: 'pipe' }); |
|
85 |
+ |
|
86 |
+ // Process stdout |
|
87 |
+ |
|
88 |
+ rec.stdout.on('readable', function() { |
|
89 |
+ self.emit('speechReady'); |
|
90 |
+ }); |
|
91 |
+ |
|
92 |
+ rec.stdout.setEncoding('binary'); |
|
93 |
+ rec.stdout.on('data', function(data) { |
|
94 |
+ if(! self.recRunning) { |
|
95 |
+ self.emit('speechStart'); |
|
96 |
+ self.recRunning = true; |
|
97 |
+ } |
|
98 |
+ self.recBuffer.push(data); |
|
99 |
+ }); |
|
100 |
+ |
|
101 |
+ // Process stdin |
|
102 |
+ |
|
103 |
+ rec.stderr.setEncoding('utf8'); |
|
104 |
+ rec.stderr.on('data', function(data) { |
|
105 |
+ console.log(data) |
|
106 |
+ }); |
|
107 |
+ |
|
108 |
+ rec.on('close', function(code) { |
|
109 |
+ self.recRunning = false; |
|
110 |
+ if(code) { |
|
111 |
+ self.emit('error', 'sox exited with code ' + code); |
|
112 |
+ } |
|
113 |
+ self.emit('speechStop'); |
|
114 |
+ self.postVoiceData(); |
|
115 |
+ }); |
|
116 |
+}; |
|
117 |
+ |
|
118 |
+Speakable.prototype.resetVoice = function() { |
|
119 |
+ var self = this; |
|
120 |
+ self.recBuffer = []; |
|
121 |
+} |
|
122 |
+ |
|
123 |
+Speakable.prototype.parseResult = function() { |
|
124 |
+ var recognizedWords = [], apiResult = this.apiResult.result; |
|
125 |
+ if(apiResult && apiResult.length > 0 && apiResult[0].alternative && apiResult[0].alternative[0]) { |
|
126 |
+ recognizedWords = apiResult[0].alternative[0].transcript.split(' '); |
|
127 |
+ this.emit('speechResult', recognizedWords); |
|
128 |
+ } else { |
|
129 |
+ this.emit('speechResult', []); |
|
130 |
+ } |
|
131 |
+} |
@@ -0,0 +1,18 @@ |
||
1 |
+{ |
|
2 |
+ "name": "speakable-test", |
|
3 |
+ "version": "1.0.0", |
|
4 |
+ "description": "", |
|
5 |
+ "main": "index.js", |
|
6 |
+ "scripts": { |
|
7 |
+ "test": "echo \"Error: no test specified\" && exit 1" |
|
8 |
+ }, |
|
9 |
+ "author": "James Peret", |
|
10 |
+ "license": "ISC", |
|
11 |
+ "dependencies": { |
|
12 |
+ "follow-redirects": "0.0.7", |
|
13 |
+ "play": "^0.5.0", |
|
14 |
+ "say": "^0.9.0", |
|
15 |
+ "socket.io-client": "^1.4.5", |
|
16 |
+ "speakable": "^0.3.0" |
|
17 |
+ } |
|
18 |
+} |