glaemscribe 1.2.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/bin/glaemscribe +2 -2
  3. data/glaemresources/charsets/cirth_ds.cst +514 -179
  4. data/glaemresources/charsets/eldamar.cst +210 -0
  5. data/glaemresources/charsets/tengwar_ds_annatar.cst +2776 -348
  6. data/glaemresources/charsets/tengwar_ds_eldamar.cst +2648 -351
  7. data/glaemresources/charsets/tengwar_ds_elfica.cst +2639 -346
  8. data/glaemresources/charsets/tengwar_ds_parmaite.cst +2648 -351
  9. data/glaemresources/charsets/tengwar_ds_sindarin.cst +2642 -348
  10. data/glaemresources/charsets/tengwar_freemono.cst +1 -1
  11. data/glaemresources/charsets/tengwar_guni_annatar.cst +2725 -300
  12. data/glaemresources/charsets/tengwar_guni_eldamar.cst +2589 -295
  13. data/glaemresources/charsets/tengwar_guni_elfica.cst +2592 -298
  14. data/glaemresources/charsets/tengwar_guni_parmaite.cst +2592 -297
  15. data/glaemresources/charsets/tengwar_guni_sindarin.cst +2591 -297
  16. data/glaemresources/charsets/tengwar_telcontar.cst +7 -0
  17. data/glaemresources/modes/blackspeech-tengwar-general_use.glaem +1 -1
  18. data/glaemresources/modes/english-cirth-espeak.glaem +687 -0
  19. data/glaemresources/modes/english-tengwar-espeak.glaem +814 -0
  20. data/glaemresources/modes/japanese-tengwar.glaem +9 -4
  21. data/glaemresources/modes/lang_belta-tengwar-dadef.glaem +248 -0
  22. data/glaemresources/modes/raw-cirth.glaem +154 -0
  23. data/lib/api/charset.rb +124 -57
  24. data/lib/api/charset_parser.rb +39 -26
  25. data/lib/api/mode.rb +35 -10
  26. data/lib/api/mode_parser.rb +21 -12
  27. data/lib/api/post_processor/outspace.rb +44 -0
  28. data/lib/api/post_processor/resolve_virtuals.rb +41 -19
  29. data/lib/api/rule_group.rb +1 -1
  30. data/lib/api/transcription_pre_post_processor.rb +51 -45
  31. data/lib/api/transcription_processor.rb +12 -9
  32. data/lib/glaemscribe.rb +2 -0
  33. data/lib_espeak/espeakng.for.glaemscribe.nowasm.sync.js +25 -11
  34. data/lib_espeak/glaemscribe_tts.js +363 -223
  35. metadata +12 -6
data/lib/api/charset.rb CHANGED
@@ -1,22 +1,22 @@
1
1
  # encoding: UTF-8
2
2
  #
3
3
  # Glǽmscribe (also written Glaemscribe) is a software dedicated to
4
- # the transcription of texts between writing systems, and more
5
- # specifically dedicated to the transcription of J.R.R. Tolkien's
4
+ # the transcription of texts between writing systems, and more
5
+ # specifically dedicated to the transcription of J.R.R. Tolkien's
6
6
  # invented languages to some of his devised writing systems.
7
- #
7
+ #
8
8
  # Copyright (C) 2015 Benjamin Babut (Talagan).
9
- #
9
+ #
10
10
  # This program is free software: you can redistribute it and/or modify
11
11
  # it under the terms of the GNU Affero General Public License as published by
12
12
  # the Free Software Foundation, either version 3 of the License, or
13
13
  # any later version.
14
- #
14
+ #
15
15
  # This program is distributed in the hope that it will be useful,
16
16
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
17
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
18
  # GNU Affero General Public License for more details.
19
- #
19
+ #
20
20
  # You should have received a copy of the GNU Affero General Public License
21
21
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
22
 
@@ -24,53 +24,94 @@ module Glaemscribe
24
24
  module API
25
25
  class Charset
26
26
  attr_reader :name
27
-
27
+
28
28
  attr_accessor :errors
29
29
  attr_reader :chars
30
30
  attr_reader :virtual_chars
31
-
31
+ attr_reader :swaps
32
+
33
+ class Swap
34
+ attr_accessor :line
35
+ attr_accessor :trigger
36
+ attr_accessor :targets
37
+
38
+ def initialize(trigger, target_list)
39
+ @trigger = trigger
40
+ @targets = {}
41
+
42
+ @target_list = target_list
43
+ end
44
+
45
+ def finalize(charset)
46
+ @lookup_table = {}
47
+
48
+ trig = charset.n2c(@trigger)
49
+
50
+ if !trig
51
+ charset.errors << Glaeml::Error.new(@line, "Swap operator triggers #{@trigger} which does not exist in charset.")
52
+ end
53
+
54
+ @target_list.each{ |target_id|
55
+ c = charset.n2c(target_id)
56
+ if !c
57
+ charset.errors << Glaeml::Error.new(@line, "Swap operator targets #{target_id} which does not exist in charset.")
58
+ else
59
+ c.names.each{ |n|
60
+ @targets[n] = c
61
+ }
62
+ end
63
+ }
64
+
65
+ trig
66
+ end
67
+
68
+ def has_target?(tname)
69
+ (@targets[tname] != nil)
70
+ end
71
+ end
72
+
32
73
  class Char
33
74
  attr_accessor :line # Line num in the sourcecode
34
75
  attr_accessor :code # Position in unicode
35
76
  attr_accessor :names # Names
36
77
  attr_accessor :str # How does this char resolve as a string
37
78
  attr_accessor :charset # Pointer to parent charset
38
-
79
+
39
80
  def initialize
40
81
  @names = {}
41
82
  end
42
-
83
+
43
84
  def virtual?
44
85
  false
45
86
  end
46
-
87
+
47
88
  def sequence?
48
89
  false
49
90
  end
50
91
  end
51
-
52
- class VirtualChar # Could have had inheritance here ...
92
+
93
+ class VirtualChar # Could have had inheritance here ...
53
94
  attr_accessor :line
54
95
  attr_accessor :names
55
96
  attr_accessor :classes
56
97
  attr_accessor :charset
57
98
  attr_accessor :reversed
58
99
  attr_accessor :default
59
-
100
+
60
101
  class VirtualClass
61
102
  attr_accessor :target
62
103
  attr_accessor :triggers
63
104
  end
64
-
105
+
65
106
  def initialize
66
107
  @classes = {} # result_char_1 => [trigger_char_1, trigger_char_2 ...] , result_char_1 => ...
67
108
  @lookup_table = {}
68
109
  @reversed = false
69
110
  @default = nil
70
111
  end
71
-
112
+
72
113
  def str
73
-
114
+
74
115
  # Will be called if the virtual char could not be replaced and still exists at the end of the transcription chain
75
116
  if @default
76
117
  @charset[@default].str
@@ -78,14 +119,14 @@ module Glaemscribe
78
119
  VIRTUAL_CHAR_OUTPUT
79
120
  end
80
121
  end
81
-
122
+
82
123
  def finalize
83
124
  @lookup_table = {}
84
125
  @classes.each{ |vc|
85
-
126
+
86
127
  result_char = vc.target
87
128
  trigger_chars = vc.triggers
88
-
129
+
89
130
  trigger_chars.each{ |trigger_char|
90
131
  found = @lookup_table[trigger_char]
91
132
  if found
@@ -93,90 +134,91 @@ module Glaemscribe
93
134
  else
94
135
  rc = @charset[result_char]
95
136
  tc = @charset[trigger_char]
96
-
137
+
97
138
  if rc.nil?
98
139
  @charset.errors << Glaeml::Error.new(@line, "Trigger char #{trigger_char} points to unknown result char #{result_char}.")
99
140
  elsif tc.nil?
100
- @charset.errors << Glaeml::Error.new(@line, "Unknown trigger char #{trigger_char}.")
141
+ @charset.errors << Glaeml::Error.new(@line, "Unknown trigger char #{trigger_char}.")
101
142
  elsif rc.class == VirtualChar
102
143
  @charset.errors << Glaeml::Error.new(@line, "Trigger char #{trigger_char} points to another virtual char #{result_char}. This is not supported!")
103
144
  else
104
145
  tc.names.each{|trigger_char_name| # Don't forget to match all name variants for that trigger char!
105
146
  @lookup_table[trigger_char_name] = rc
106
- }
107
- end
108
- end
147
+ }
148
+ end
149
+ end
109
150
  }
110
151
  }
111
152
  if @default
112
153
  c = @charset[@default]
113
154
  if !c
114
- @charset.errors << Glaeml::Error.new(@line, "Default char #{@default} does not match any real character in the charset.")
155
+ @charset.errors << Glaeml::Error.new(@line, "Default char #{@default} does not match any real character in the charset.")
115
156
  elsif c.virtual?
116
157
  @charset.errors << Glaeml::Error.new(@line, "Default char #{@default} is virtual, it should be real only.")
117
158
  end
118
159
  end
119
160
  end
120
-
161
+
121
162
  def [](trigger_char_name)
122
163
  @lookup_table[trigger_char_name]
123
164
  end
124
-
165
+
125
166
  def virtual?
126
167
  true
127
168
  end
128
-
169
+
129
170
  def sequence?
130
171
  false
131
172
  end
132
173
  end
133
-
174
+
134
175
  class SequenceChar
135
176
  attr_accessor :line # Line of code
136
177
  attr_accessor :names # Names
137
178
  attr_accessor :sequence # The sequence of chars
138
179
  attr_accessor :charset # Pointer to parent charset
139
-
180
+
140
181
  def virtual?
141
182
  false
142
183
  end
143
-
184
+
144
185
  def sequence?
145
186
  true
146
- end
147
-
187
+ end
188
+
148
189
  def str
149
190
  # A sequence char should never arrive unreplaced
150
191
  VIRTUAL_CHAR_OUTPUT
151
192
  end
152
-
153
- def finalize
193
+
194
+ def finalize
154
195
  if @sequence.count == 0
155
- @charset.errors << Glaeml::Error.new(@line, "Sequence for sequence char is empty.")
196
+ @charset.errors << Glaeml::Error.new(@line, "Sequence for sequence char is empty.")
156
197
  end
157
-
198
+
158
199
  @sequence.each{ |symbol|
159
200
  # Check that the sequence is correct
160
201
  found = @charset[symbol]
161
202
  if !found
162
203
  @charset.errors << Glaeml::Error.new(@line, "Sequence char #{symbol} cannot be found in the charset.")
163
204
  end
164
- }
205
+ }
165
206
  end
166
-
207
+
167
208
  end
168
-
209
+
169
210
  def initialize(name)
170
211
  @name = name
171
212
  @chars = []
172
213
  @errors = []
173
214
  @virtual_chars = []
215
+ @swaps = []
174
216
  end
175
-
217
+
176
218
  # Pass integer (utf8 num) and array (of strings)
177
219
  def add_char(line, code, names)
178
220
  return if names.empty? || names.include?("?") # Ignore characters with '?'
179
-
221
+
180
222
  c = Char.new
181
223
  c.line = line
182
224
  c.code = code
@@ -185,10 +227,10 @@ module Glaemscribe
185
227
  c.charset = self
186
228
  @chars << c
187
229
  end
188
-
230
+
189
231
  def add_virtual_char(line, classes, names, reversed = false, default = nil)
190
232
  return if names.empty? || names.include?("?") # Ignore characters with '?'
191
-
233
+
192
234
  c = VirtualChar.new
193
235
  c.line = line
194
236
  c.names = names
@@ -196,25 +238,34 @@ module Glaemscribe
196
238
  c.charset = self
197
239
  c.reversed = reversed
198
240
  c.default = default
199
- @chars << c
241
+ @chars << c
200
242
  end
201
-
243
+
202
244
  def add_sequence_char(line, names, seq)
203
245
  return if names.empty? || names.include?("?") # Ignore characters with '?'
204
-
246
+
205
247
  c = SequenceChar.new
206
248
  c.line = line
207
249
  c.names = names
208
- c.sequence = seq.split.reject{|token| token.empty? }
250
+ c.sequence = seq.split.reject{|token| token.empty? }
209
251
  c.charset = self
210
252
  @chars << c
211
253
  end
212
-
254
+
255
+ def add_swap(line, target, triggers)
256
+ return if target.empty? || triggers.empty?
257
+
258
+ s = Swap.new(target, triggers)
259
+ s.line = line
260
+ @swaps << s
261
+ end
262
+
213
263
  def finalize
214
264
  @errors = []
215
265
  @lookup_table = {}
216
266
  @virtual_chars = [] # A convenient filtered array
217
-
267
+ @swap_lookup = {}
268
+
218
269
  @chars.each { |c|
219
270
  c.names.each { |cname|
220
271
  found = @lookup_table[cname]
@@ -225,27 +276,43 @@ module Glaemscribe
225
276
  end
226
277
  }
227
278
  }
228
-
279
+
229
280
  @chars.each{ |c|
230
281
  if c.class == VirtualChar
231
282
  c.finalize
232
283
  @virtual_chars << c
233
284
  end
234
285
  }
235
-
286
+
236
287
  @chars.each{|c|
237
288
  if c.class == SequenceChar
238
289
  c.finalize
239
290
  end
240
291
  }
241
-
292
+
293
+ @swaps.each{ |s|
294
+ trig = s.finalize(self)
295
+ if trig
296
+ trig.names.each{ |n|
297
+ @swap_lookup[n] = s
298
+ }
299
+ end
300
+ }
242
301
  API::Debug::log("Finalized charset '#{@name}', #{@lookup_table.count} symbols loaded.")
243
302
  end
244
-
303
+
245
304
  def [](symbol)
246
305
  @lookup_table[symbol]
247
306
  end
248
-
307
+
308
+ def n2c(symbol)
309
+ self[symbol]
310
+ end
311
+
312
+ def swap_for_trigger(trigger_name)
313
+ @swap_lookup[trigger_name]
314
+ end
315
+
249
316
  end
250
317
  end
251
- end
318
+ end
@@ -1,37 +1,37 @@
1
1
  # encoding: UTF-8
2
2
  #
3
3
  # Glǽmscribe (also written Glaemscribe) is a software dedicated to
4
- # the transcription of texts between writing systems, and more
5
- # specifically dedicated to the transcription of J.R.R. Tolkien's
4
+ # the transcription of texts between writing systems, and more
5
+ # specifically dedicated to the transcription of J.R.R. Tolkien's
6
6
  # invented languages to some of his devised writing systems.
7
- #
7
+ #
8
8
  # Copyright (C) 2015 Benjamin Babut (Talagan).
9
- #
9
+ #
10
10
  # This program is free software: you can redistribute it and/or modify
11
11
  # it under the terms of the GNU Affero General Public License as published by
12
12
  # the Free Software Foundation, either version 3 of the License, or
13
13
  # any later version.
14
- #
14
+ #
15
15
  # This program is distributed in the hope that it will be useful,
16
16
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
17
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
18
  # GNU Affero General Public License for more details.
19
- #
19
+ #
20
20
  # You should have received a copy of the GNU Affero General Public License
21
21
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
22
 
23
23
  module Glaemscribe
24
24
  module API
25
-
25
+
26
26
  class CharsetParser
27
-
27
+
28
28
  def initialize()
29
29
  @charset = nil
30
30
  end
31
-
31
+
32
32
  def parse(file_path)
33
- @charset = Charset.new(ResourceManager::charset_name_from_file_path(file_path))
34
-
33
+ @charset = Charset.new(ResourceManager::charset_name_from_file_path(file_path))
34
+
35
35
  raw = File.open(file_path,"rb:utf-8").read
36
36
  doc = Glaeml::Parser.new.parse(raw)
37
37
 
@@ -39,49 +39,62 @@ module Glaemscribe
39
39
  @charset.errors = doc.errors
40
40
  return @charset
41
41
  end
42
-
42
+
43
43
  # TODO : verify charset glaeml like we do with modes
44
-
44
+
45
45
  doc.root_node.gpath("char").each { |char_element|
46
46
  code = char_element.args[0].hex
47
47
  names = char_element.args[1..-1].map{|cname| cname.strip }.reject{ |cname| cname.empty? }
48
48
  @charset.add_char(char_element.line,code,names)
49
49
  }
50
-
51
- doc.root_node.gpath("seq").each{ |seq_elemnt|
50
+
51
+ doc.root_node.gpath("seq").each{ |seq_elemnt|
52
52
  names = seq_elemnt.args
53
53
  child_node = seq_elemnt.children.first
54
54
  seq = (child_node && child_node.text?)?(child_node.args.first):("")
55
55
  @charset.add_sequence_char(seq_elemnt.line,names,seq)
56
56
  }
57
-
57
+
58
+ doc.root_node.gpath("swap").each{ |element|
59
+ trigger_one = element.args.first
60
+ text_lines = element.children.select{ |c| c.text? }.map{ |c| c.args.first }
61
+ second_triggers = text_lines.join(" ").split(/\s/).select{ |e| e != '' }
62
+ @charset.add_swap(element.line, trigger_one, second_triggers)
63
+ }
64
+
58
65
  doc.root_node.gpath("virtual").each { |virtual_element|
59
66
  names = virtual_element.args
60
- reversed = false
67
+ reversed = false
61
68
  default = nil
62
69
  classes = []
63
-
70
+
64
71
  virtual_element.gpath("class").each { |class_element|
65
72
  vc = Charset::VirtualChar::VirtualClass.new
66
73
  vc.target = class_element.args[0]
67
- vc.triggers = class_element.args[1..-1].map{|cname| cname.strip }.reject{ |cname| cname.empty? }
74
+ vc.triggers = class_element.args[1..-1].map{|cname| cname.strip }.reject{ |cname| cname.empty? }
75
+
76
+ # Allow triggers to be defined inside the body of the class element
77
+ text_lines = class_element.children.select { |c| c.text? }.map{ |c| c.args.first}
78
+ inner_triggers = text_lines.join(" ").split(/\s/).select{ |e| e != '' }
79
+ vc.triggers += inner_triggers
80
+
68
81
  classes << vc
69
82
  }
70
- virtual_element.gpath("reversed").each { |reversed_element|
83
+ virtual_element.gpath("reversed").each { |reversed_element|
71
84
  reversed = true
72
85
  }
73
- virtual_element.gpath("default").each { |default_element|
86
+ virtual_element.gpath("default").each { |default_element|
74
87
  default = default_element.args[0]
75
88
  }
76
-
89
+
77
90
  @charset.add_virtual_char(virtual_element.line,classes,names,reversed,default)
78
91
  }
79
-
92
+
80
93
  @charset.finalize
81
-
82
- @charset
94
+
95
+ @charset
83
96
  end
84
-
97
+
85
98
  end
86
99
  end
87
100
  end
data/lib/api/mode.rb CHANGED
@@ -22,6 +22,23 @@
22
22
 
23
23
  module Glaemscribe
24
24
  module API
25
+
26
+ class ModeDebugContext
27
+ attr_accessor :preprocessor_output,
28
+ :processor_pathes,
29
+ :processor_output,
30
+ :postprocessor_output,
31
+ :tts_output
32
+
33
+ def initialize
34
+ @preprocessor_output = ""
35
+ @processor_pathes = []
36
+ @processor_output = []
37
+ @postprocessor_output = ""
38
+ @tts_output = ""
39
+ end
40
+ end
41
+
25
42
  class Mode
26
43
 
27
44
  attr_accessor :errors
@@ -46,8 +63,6 @@ module Glaemscribe
46
63
 
47
64
  attr_reader :latest_option_values
48
65
 
49
-
50
-
51
66
  def initialize(name)
52
67
  @name = name
53
68
  @errors = []
@@ -141,13 +156,14 @@ module Glaemscribe
141
156
  @raw_mode = loaded_raw_mode.deep_clone
142
157
  end
143
158
 
144
- def strict_transcribe(content, charset = nil)
159
+ def strict_transcribe(content, charset, debug_context)
145
160
  charset = default_charset if !charset
146
161
  return false, "*** No charset usable for transcription. Failed!" if !charset
147
162
 
148
163
  if has_tts
149
164
  begin
150
165
  content = TTS.ipa(content, @current_tts_voice, (raw_mode != nil) )['ipa']
166
+ debug_context.tts_output += content
151
167
  rescue StandardError => e
152
168
  return false, "TTS pre-transcription failed : #{e}."
153
169
  end
@@ -160,9 +176,16 @@ module Glaemscribe
160
176
  l[-1] = ""
161
177
  restore_lf = true
162
178
  end
179
+
163
180
  l = @pre_processor.apply(l)
164
- l = @processor.apply(l)
181
+ debug_context.preprocessor_output += l + "\n"
182
+
183
+ l = @processor.apply(l, debug_context)
184
+ debug_context.processor_output += l
185
+
165
186
  l = @post_processor.apply(l, charset)
187
+ debug_context.postprocessor_output += l + "\n"
188
+
166
189
  l += "\n" if restore_lf
167
190
  l
168
191
  }.join
@@ -170,32 +193,34 @@ module Glaemscribe
170
193
  end
171
194
 
172
195
  def transcribe(content, charset = nil)
196
+ debug_context = ModeDebugContext.new
173
197
  if raw_mode
174
198
  chunks = content.split(/({{.*?}})/m)
175
199
  ret = ''
176
200
  res = true
177
201
  chunks.each{ |c|
178
202
  if c =~ /{{(.*?)}}/m
179
- succ, r = raw_mode.strict_transcribe($1,charset)
203
+ succ, r = raw_mode.strict_transcribe($1, charset, debug_context)
180
204
 
181
205
  if !succ
182
- return false, r # Propagate error
206
+ return false, r, debug_context # Propagate error
183
207
  end
184
208
 
185
209
  ret += r
186
210
  else
187
- succ, r = strict_transcribe(c,charset)
211
+ succ, r = strict_transcribe(c,charset,debug_context)
188
212
 
189
213
  if !succ
190
- return false, r # Propagate error
214
+ return false, r, debug_context # Propagate error
191
215
  end
192
216
 
193
217
  ret += r
194
218
  end
195
219
  }
196
- return res,ret
220
+ return res, ret, debug_context
197
221
  else
198
- strict_transcribe(content,charset)
222
+ succ, r = strict_transcribe(content, charset, debug_context)
223
+ return succ, r, debug_context
199
224
  end
200
225
  end
201
226
 
@@ -83,7 +83,11 @@ module Glaemscribe
83
83
  doc.root_node.gpath("preprocessor.if").each{ |e| validate_presence_of_args(e, 1) }
84
84
  doc.root_node.gpath("preprocessor.elsif").each{ |e| validate_presence_of_args(e, 1) }
85
85
  doc.root_node.gpath("postprocessor.if").each{ |e| validate_presence_of_args(e, 1) }
86
- doc.root_node.gpath("postprocessor.elsif").each{ |e| validate_presence_of_args(e, 1) }
86
+ doc.root_node.gpath("postprocessor.elsif").each{ |e| validate_presence_of_args(e, 1) }
87
+
88
+ doc.root_node.children.each { |c|
89
+ @mode.errors << Glaeml::Error.new(c.line, "'if' conditions are not allowed in that scope.") if c.name == 'if'
90
+ }
87
91
  end
88
92
 
89
93
  def create_if_cond_for_if_term(line, if_term, cond)
@@ -245,7 +249,7 @@ module Glaemscribe
245
249
  if !operator_class
246
250
  @mode.errors << Glaeml::Error.new(element.line,"Operator #{operator_name} is unknown.")
247
251
  else
248
- term.operators << operator_class.new(element.clone)
252
+ term.operators << operator_class.new(@mode, element.clone)
249
253
  end
250
254
  }
251
255
 
@@ -391,22 +395,27 @@ module Glaemscribe
391
395
  }
392
396
  traverse_if_tree(processor_context, text_procedure, element_procedure )
393
397
  }
394
-
395
-
398
+
396
399
  espeak_option = @mode.options['espeak_voice']
397
400
  if espeak_option
398
401
  # Singleton lazy load the TTS engine
399
402
  # If the mode relies on espeak
400
- TTS::load_engine
401
403
  @mode.has_tts = true
402
404
 
403
- # Check if all voices are supported
404
- espeak_option.values.keys.each { |vname|
405
- voice = TTS::option_name_to_voice(vname)
406
- if !(TTS::voice_list.include? voice)
407
- @mode.errors << Glaeml::Error.new(espeak_option.line, "Option has unhandled voice #{voice}.")
408
- end
409
- }
405
+ begin
406
+ TTS::load_engine
407
+
408
+ # Check if all voices are supported
409
+ espeak_option.values.keys.each { |vname|
410
+ voice = TTS::option_name_to_voice(vname)
411
+ if !(TTS::voice_list.include? voice)
412
+ @mode.errors << Glaeml::Error.new(espeak_option.line, "Option has unhandled voice #{voice}.")
413
+ end
414
+ }
415
+ rescue
416
+ @mode.errors << Glaeml::Error.new(espeak_option.line, "Failed to load TTS engine.")
417
+ end
418
+
410
419
  end
411
420
 
412
421
  @mode.finalize(mode_options) if !@mode.errors.any?