glaemscribe 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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?