Skip to content

Commit 638599d

Browse files
author
Bruno Berisso
committed
- Add exceptions to some decoder metods to better handle errors.
- Chenge some 'if' statements for 'guards', mostely in the tests - Use STrue | SFalse instead of 1 | 0 to denote true | false when applicable
1 parent 42904e1 commit 638599d

File tree

6 files changed

+160
-131
lines changed

6 files changed

+160
-131
lines changed

TLSphinx/Config.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public final class Config {
2222
return [strdup(name),strdup(value)]
2323
}
2424

25-
cmdLnConf = cmd_ln_parse_r(nil, ps_args(), CInt(cArgs.count), &cArgs, STrue)
25+
cmdLnConf = cmd_ln_parse_r(nil, ps_args(), CInt(cArgs.count), &cArgs, STrue32)
2626

2727
if cmdLnConf == nil {
2828
return nil

TLSphinx/Decoder.swift

Lines changed: 58 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ fileprivate enum SpeechStateEnum : CustomStringConvertible {
3333

3434
fileprivate extension AVAudioPCMBuffer {
3535

36-
func toDate() -> Data {
36+
func toData() -> Data {
3737
let channels = UnsafeBufferPointer(start: int16ChannelData, count: 1)
38-
let ch0Data = Data(bytes: UnsafeMutablePointer<Int16>(channels[0]), count:Int(frameCapacity * format.streamDescription.pointee.mBytesPerFrame))
38+
let ch0Data = Data(bytes: UnsafeMutablePointer<int16>(channels[0]),
39+
count: Int(frameCapacity * format.streamDescription.pointee.mBytesPerFrame))
3940
return ch0Data
4041
}
4142

@@ -60,16 +61,9 @@ public final class Decoder {
6061
public init?(config: Config) {
6162

6263
speechState = .silence
63-
64-
if config.cmdLnConf != nil{
65-
psDecoder = ps_init(config.cmdLnConf)
66-
67-
if psDecoder == nil {
68-
return nil
69-
}
70-
71-
} else {
72-
psDecoder = nil
64+
psDecoder = config.cmdLnConf.flatMap(ps_init)
65+
66+
if psDecoder == nil {
7367
return nil
7468
}
7569
}
@@ -80,9 +74,11 @@ public final class Decoder {
8074
}
8175

8276
@discardableResult fileprivate func process_raw(_ data: Data) -> CInt {
83-
//Sphinx expect words of 2 bytes but the NSFileHandle read one byte at time so the lenght of the data for sphinx is the half of the real one.
77+
8478
let dataLenght = data.count / 2
85-
let numberOfFrames = ps_process_raw(psDecoder, (data as NSData).bytes.bindMemory(to: int16.self, capacity: data.count), dataLenght, SFalse, SFalse)
79+
let numberOfFrames = data.withUnsafeBytes { (bytes : UnsafePointer<Int16>) -> Int32 in
80+
ps_process_raw(psDecoder, bytes, dataLenght, SFalse32, SFalse32)
81+
}
8682
let hasSpeech = in_speech()
8783

8884
switch (speechState) {
@@ -100,7 +96,7 @@ public final class Decoder {
10096
}
10197

10298
fileprivate func in_speech() -> Bool {
103-
return ps_get_in_speech(psDecoder) == 1
99+
return ps_get_in_speech(psDecoder) == STrue
104100
}
105101

106102
@discardableResult fileprivate func start_utt() -> Bool {
@@ -114,79 +110,75 @@ public final class Decoder {
114110
fileprivate func get_hyp() -> Hypothesis? {
115111
var score: int32 = 0
116112

117-
if let string = ps_get_hyp(psDecoder, &score) {
118-
if let text = String(validatingUTF8: string) {
119-
return Hypothesis(text: text, score: Int(score))
120-
} else {
121-
return nil
122-
}
113+
guard let string = ps_get_hyp(psDecoder, &score) else {
114+
return nil
115+
}
116+
117+
if let text = String(validatingUTF8: string) {
118+
return Hypothesis(text: text, score: Int(score))
123119
} else {
124120
return nil
125121
}
126122
}
127-
128-
fileprivate func hypotesisForSpeechAtPath (_ filePath: String) -> Hypothesis? {
129-
130-
if let fileHandle = FileHandle(forReadingAtPath: filePath) {
131-
132-
start_utt()
133-
134-
let hypothesis = fileHandle.reduceChunks(bufferSize, initial: nil, reducer: { [unowned self] (data: Data, partialHyp: Hypothesis?) -> Hypothesis? in
135-
136-
self.process_raw(data)
137-
138-
var resultantHyp = partialHyp
139-
if self.speechState == .utterance {
140-
141-
self.end_utt()
142-
resultantHyp = partialHyp + self.get_hyp()
143-
self.start_utt()
144-
}
145-
146-
return resultantHyp
147-
})
148-
149-
end_utt()
150-
fileHandle.closeFile()
151-
152-
//Process any pending speech
153-
if speechState == .speech {
154-
return hypothesis + get_hyp()
155-
} else {
156-
return hypothesis
123+
124+
fileprivate func hypotesisForSpeech (inFile fileHandle: FileHandle) -> Hypothesis? {
125+
126+
start_utt()
127+
128+
let hypothesis = fileHandle.reduceChunks(2048, initial: nil, reducer: {
129+
(data: Data, partialHyp: Hypothesis?) -> Hypothesis? in
130+
131+
process_raw(data)
132+
133+
var resultantHyp = partialHyp
134+
if speechState == .utterance {
135+
136+
end_utt()
137+
resultantHyp = partialHyp + get_hyp()
138+
start_utt()
157139
}
158-
140+
141+
return resultantHyp
142+
})
143+
144+
end_utt()
145+
146+
//Process any pending speech
147+
if speechState == .speech {
148+
return hypothesis + get_hyp()
159149
} else {
160-
return nil
150+
return hypothesis
161151
}
162152
}
163-
164-
open func decodeSpeechAtPath (_ filePath: String, complete: @escaping (Hypothesis?) -> ()) {
165-
153+
154+
public func decodeSpeech (atPath filePath: String, complete: @escaping (Hypothesis?) -> ()) throws {
155+
156+
guard let fileHandle = FileHandle(forReadingAtPath: filePath) else {
157+
throw DecodeErrors.CantReadSpeachFile(filePath)
158+
}
159+
166160
DispatchQueue.global().async {
167-
168-
let hypothesis = self.hypotesisForSpeechAtPath(filePath)
169-
161+
let hypothesis = self.hypotesisForSpeech(inFile:fileHandle)
162+
fileHandle.closeFile()
170163
DispatchQueue.main.async {
171164
complete(hypothesis)
172165
}
173166
}
174167
}
175168

176-
open func startDecodingSpeech (_ utteranceComplete: @escaping (Hypothesis?) -> ()) {
169+
public func startDecodingSpeech (_ utteranceComplete: @escaping (Hypothesis?) -> ()) throws {
177170

178171
do {
179172
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryRecord)
180173
} catch let error as NSError {
181174
print("Error setting the shared AVAudioSession: \(error)")
182-
return
175+
throw DecodeErrors.CantSetAudioSession(error)
183176
}
184177

185178
engine = AVAudioEngine()
186179

187180
guard let input = engine.inputNode else {
188-
print("Can't get input node")
189-
return
181+
throw DecodeErrors.NoAudioInputAvailable
190182
}
191183

192184
let formatIn = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 44100, channels: 1, interleaved: false)
@@ -202,9 +194,9 @@ public final class Decoder {
202194
self.end_utt()
203195
let hypothesis = self.get_hyp()
204196

205-
DispatchQueue.main.async(execute: {
197+
DispatchQueue.main.async {
206198
utteranceComplete(hypothesis)
207-
})
199+
}
208200

209201
self.start_utt()
210202
}
@@ -220,6 +212,7 @@ public final class Decoder {
220212
} catch let error as NSError {
221213
end_utt()
222214
print("Can't start AVAudioEngine: \(error)")
215+
throw DecodeErrors.CantStartAudioEngine(error)
223216
}
224217
}
225218

TLSphinx/Globals.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88

99
import Foundation
1010

11-
let STrue: CInt = 1
12-
let SFalse: CInt = 0
11+
let STrue: uint8 = 1
12+
let SFalse: uint8 = 0
13+
14+
let STrue32: CInt = 1
15+
let SFalse32: CInt = 0
1316

1417
extension FileHandle {
1518

TLSphinx/Hypotesis.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ extension Hypothesis : CustomStringConvertible {
2323

2424
}
2525

26-
func +(lhs: Hypothesis, rhs: Hypothesis) -> Hypothesis {
27-
return Hypothesis(text: lhs.text + " " + rhs.text, score: (lhs.score + rhs.score) / 2)
26+
extension Hypothesis {
27+
static func +(lhs: Hypothesis, rhs: Hypothesis) -> Hypothesis {
28+
return Hypothesis(text: lhs.text + " " + rhs.text, score: (lhs.score + rhs.score) / 2)
29+
}
2830
}
2931

3032
func +(lhs: Hypothesis?, rhs: Hypothesis?) -> Hypothesis? {
@@ -37,4 +39,4 @@ func +(lhs: Hypothesis?, rhs: Hypothesis?) -> Hypothesis? {
3739
return rhs
3840
}
3941
}
40-
}
42+
}

TLSphinxTests/Basic.swift

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,49 +13,82 @@ import TLSphinx
1313

1414
class BasicTests: XCTestCase {
1515

16-
func getModelPath() -> String? {
17-
return Bundle(for: BasicTests.self).path(forResource: "en-us", ofType: nil)
16+
func getModelPath() -> NSString? {
17+
return Bundle(for: BasicTests.self).path(forResource: "en-us", ofType: nil) as NSString?
1818
}
1919

2020
func testConfig() {
21-
22-
if let modelPath = getModelPath() {
23-
24-
let hmm = (modelPath as NSString).appendingPathComponent("en-us")
25-
let lm = (modelPath as NSString).appendingPathComponent("en-us.lm.dmp")
26-
let dict = (modelPath as NSString).appendingPathComponent("cmudict-en-us.dict")
27-
28-
let config = Config(args: ("-hmm", hmm), ("-lm", lm), ("-dict", dict))
29-
30-
XCTAssert(config != nil, "Pass")
31-
32-
} else {
21+
22+
guard let modelPath = getModelPath() else {
3323
XCTFail("Can't access pocketsphinx model. Bundle root: \(Bundle.main)")
24+
return
3425
}
26+
27+
let hmm = modelPath.appendingPathComponent("en-us")
28+
let lm = modelPath.appendingPathComponent("en-us.lm.dmp")
29+
let dict = modelPath.appendingPathComponent("cmudict-en-us.dict")
30+
31+
let config = Config(args: ("-hmm", hmm), ("-lm", lm), ("-dict", dict))
32+
XCTAssert(config != nil, "Pass")
3533
}
3634

3735
func testDecoder() {
3836

39-
if let modelPath = getModelPath() {
37+
guard let modelPath = getModelPath() else {
38+
XCTFail("Can't access pocketsphinx model. Bundle root: \(Bundle.main)")
39+
return
40+
}
4041

41-
let hmm = (modelPath as NSString).appendingPathComponent("en-us")
42-
let lm = (modelPath as NSString).appendingPathComponent("en-us.lm.dmp")
43-
let dict = (modelPath as NSString).appendingPathComponent("cmudict-en-us.dict")
42+
let hmm = modelPath.appendingPathComponent("en-us")
43+
let lm = modelPath.appendingPathComponent("en-us.lm.dmp")
44+
let dict = modelPath.appendingPathComponent("cmudict-en-us.dict")
45+
46+
guard let config = Config(args: ("-hmm", hmm), ("-lm", lm), ("-dict", dict)) else {
47+
XCTFail("Can't run test without a valid config")
48+
return
49+
}
50+
51+
let decoder = Decoder(config:config)
52+
XCTAssert(decoder != nil, "Pass")
53+
}
54+
55+
func testSpeechFromFile() {
56+
57+
guard let modelPath = getModelPath() else {
58+
XCTFail("Can't access pocketsphinx model. Bundle root: \(Bundle.main)")
59+
return
60+
}
61+
62+
let hmm = modelPath.appendingPathComponent("en-us")
63+
let lm = modelPath.appendingPathComponent("en-us.lm.dmp")
64+
let dict = modelPath.appendingPathComponent("cmudict-en-us.dict")
65+
66+
guard let config = Config(args: ("-hmm", hmm), ("-lm", lm), ("-dict", dict)) else {
67+
XCTFail("Can't run test without a valid config")
68+
return
69+
}
70+
71+
guard let decoder = Decoder(config:config) else {
72+
XCTFail("Can't run test without a decoder")
73+
return
74+
}
75+
76+
let audioFile = modelPath.appendingPathComponent("goforward.raw")
77+
let expectation = self.expectation(description: "Decode finish")
78+
79+
try! decoder.decodeSpeech(atPath: audioFile) {
4480

45-
if let config = Config(args: ("-hmm", hmm), ("-lm", lm), ("-dict", dict)) {
46-
let decoder = Decoder(config:config)
81+
if let hyp = $0 {
82+
83+
print("Text: \(hyp.text) - Score: \(hyp.score)")
84+
XCTAssert(hyp.text == "go forward ten meters", "Pass")
4785

48-
XCTAssert(decoder != nil, "Pass")
4986
} else {
50-
XCTFail("Can't run test without a valid config")
87+
XCTFail("Fail to decode audio")
5188
}
5289

53-
} else {
54-
XCTFail("Can't access pocketsphinx model. Bundle root: \(Bundle.main)")
90+
expectation.fulfill()
5591
}
56-
}
57-
58-
func testSpeechFromFile() {
5992

6093
if let modelPath = getModelPath() {
6194

0 commit comments

Comments
 (0)