-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathspeech.coffee
301 lines (278 loc) · 7.66 KB
/
speech.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
$ = jQuery
recognition = null
listening = false
currentPage = 'main'
prettifyRules = null
emptyStringRe = /^\s*$/
# Controls the big button on the top left corner of the page.
# It's an incredibly ugly button, but it does its job. :-)
changeStatus = (message, clickable = false) ->
$('#start')
.text(message)
.prop('disabled', !clickable)
return
# Capitalize the first letter of a sentence.
# Correct commonly misrecognized words.
prettifyText = (text) ->
for rule in prettifyRules
text = text.replace.apply(text, rule)
return text
# Insert text into the main input text area.
# Add spaces around text when necessary.
addTranscription = do ->
endWithSpace = new RegExp('(^|\n| )$')
startWithSpace = new RegExp('^( |\n|$)')
return (text) ->
input = $('#text')
elem = input[0]
startPosition = elem.selectionStart
endPosition = elem.selectionEnd
oldText = input.val()
beforeText = oldText.substr(0, startPosition)
afterText = oldText.substr(endPosition)
text = text.replace(/^ +| +$/, '')
text = " " + text unless endWithSpace.test(beforeText)
text = text + " " unless startWithSpace.test(afterText)
newText = beforeText + text + afterText
input.val(newText)
newPosition = startPosition + text.length
elem.setSelectionRange(newPosition, newPosition)
return
$ ->
if loadData() is false then return
if startRecognizer() is false then return
new LanguagesSelectionPage()
new PrettifyRulesPage()
new SnippetsPage()
attachEventHandlers()
changeStatus("Start", true)
$('#header .edit').fadeTo('slow', 0.5)
return
switchToPage = (name) ->
currentPage = name
document.body.scrollTop = 0
$('body > .page:visible').hide()
$('#' + name + '-page').fadeIn()
# return the page's <div>
attachEventHandlers = ->
$('body').on 'keydown', (event) ->
if currentPage is 'main'
if event.which in [27] # Escape
toggleListening()
# Before copying (Control-C) or cutting (Control-X),
# run 'prettify' if nothing is selected,
# and then stop listening.
if event.which in [67, 88] and event.ctrlKey is true
elem = $('#text')[0]
if elem.selectionStart is elem.selectionEnd
$('#prettify').triggerHandler('click')
if listening then toggleListening()
return
do (button = $("#start")) ->
button.on 'click', (event) ->
toggleListening()
return
return
do (input = $('#text')) ->
$('#prettify').on 'click', (event) ->
input.val(prettifyText(input.val()))
input[0].setSelectionRange(0, input.val().length)
input.focus()
return
return
do (select = $('#snippets')) ->
select.on 'change', (event) ->
addTranscription(select.val())
select.val('')
return
return
return
toggleListening = ->
if $("#start").prop('disabled') is true
return # already starting or stopping
if listening
changeStatus("Stopping")
recognition.stop()
else
changeStatus("Starting")
recognition.lang = $('#language').val()
recognition.start()
return
startRecognizer = ->
unless 'webkitSpeechRecognition' of window
message = """
Your browser does not support the Web Speech API.
Try again using Google Chrome.
"""
alert message
$('body').empty().html(message.replace("\n", '<br>'))
return false
recognition = new webkitSpeechRecognition()
recognition.continuous = true
recognition.interimResults = true
recognition.onstart = (event) ->
$('#error').hide()
changeStatus("Stop", true)
$("#start").addClass('on')
listening = true
return
recognition.onend = (event) ->
changeStatus("Start", true)
$("#start").removeClass('on')
listening = false
$('#interim').text("...")
return
recognition.onerror = (event) ->
console.log event
$('#error').text(event.error).show()
return
recognition.onresult = (event) ->
interim = ""
i = event.resultIndex
while i < event.results.length
result = event.results[i]; i += 1
if result.isFinal then addTranscription(result[0].transcript)
else interim += result[0].transcript
$('#interim').text(interim || "...")
return
return true
loadData = ->
unless 'localStorage' of window
message = """
Your browser does not support the Web Storage API.
"""
alert message
$('body').empty().html(message.replace("\n", '<br>'))
return false
return true
class SingleTextboxPage
constructor: ->
@page = $('#' + @name + '-page')
@textarea = $('textarea', @page)
# Restore data
@parse(@get())
# Attach event handlers
$('#menu-' + @name).on 'click', => @load() and @open()
$('#save-' + @name).on 'click', => @save() and @close()
$('#reset-' + @name).on 'click', => @reset() and @load()
get: -> # Load data from localStorage
localStorage[@name] ? @default
set: (data) -> # Save data to localStorage (and to main page)
if data is @default then return @reset()
if @parse(data) is false then return false
if @validate?() is false then return false
localStorage[@name] = data
return true
open: -> # Show this page
switchToPage(@name)
@textarea.focus()
return
close: -> # Back to main page
switchToPage('main')
return
load: -> # localStorage to DOM
@textarea.val(@get())
return true
save: -> # DOM to localStorage
@set(@textarea.val())
reset: ->
localStorage.removeItem(@name)
@parse(@default)
return true
class LanguagesSelectionPage extends SingleTextboxPage
name: 'langs'
constructor: ->
@default = """
# The first word is the language code, used by the speech recognition engine.
# The rest of the line is just a label for the language selection box.
pt-BR Portuguese
en-US English
# What language code should be used for Esperanto?
eo Esperanto
eo-EO Esperanto
"""
super
validate: ->
if @count() is 0
alert("At least one language must be specified.")
return false
return true
parse: (data) ->
select = $('#language').empty()
for line in data.split(/\r*\n+/)
if /^\s*(#|$)/.test(line)
# Comment or empty
else if mo = line.match(/^\s*(\S+)\s+(\S.*)$/)
$('<option>')
.text(mo[2] + " (" + mo[1] + ")")
.attr('value', mo[1])
.appendTo(select)
else
alert("Invalid line:\n#{ line }")
return false
return true
count: ->
$('#language > option').length
class PrettifyRulesPage extends SingleTextboxPage
name: 'rules'
constructor: ->
@default = """
# Capitalize these words anywhere.
[ /\\b(google|microsoft|portuguese|english|fastville|esperanto|português|inglês)\\b/g, capitalize ]
[ /(free|open|net|dragon)bsd\\b/gi, function(_, b) { return capitalize(b) + 'BSD' } ]
# Capitalize the first letter of each line.
[ /^\\w/gm, capitalize ]
# Capitalize the first letter after .?!
[ /([.?!] )(\\w)/g, function(_, b, a) { return b + capitalize(a) } ]
# Commonly misrecognized words.
[ /\\big\\b/gi, 'e' ]
[ /\\buol\\b/gi, 'ou' ]
"""
super
parse: (data) ->
prettifyRules = []
capitalize = (w) ->
w.substr(0,1).toUpperCase() + w.substr(1).toLowerCase()
for line in data.split(/\r*\n+/)
if /^\s*(#|$)/.test(line)
# Comment or empty
else if /^\s*\[.+\]\s*$/.test(line)
try
obj = eval(line)
catch error
alert("Invalid JavaScript: #{error}:\n#{line}")
return false
if not $.isArray(obj) or obj.length isnt 2
alert("Not an array of length 2:\n#{line}")
return false
prettifyRules.push(obj)
else
alert("Invalid line:\n#{ line }")
return false
return true
class SnippetsPage extends SingleTextboxPage
name: 'snippets'
constructor: ->
@default = """
?
!
.
,
:-)
:-(
"""
super
parse: (data) ->
select = $('#snippets').empty()
$('<option>')
.attr('value', "")
.appendTo(select)
for line in data.split(/\r*\n+/)
if /^\s*(#|$)/.test(line)
# Comment or empty
else
$('<option>')
.text(line)
.attr('value', line)
.appendTo(select)
return true