Fangjun Kuang
Committed by GitHub

Refactor Swift API (#2493)

@@ -198,81 +198,64 @@ func sherpaOnnxOnlineRecognizerConfig( @@ -198,81 +198,64 @@ func sherpaOnnxOnlineRecognizerConfig(
198 /// 198 ///
199 class SherpaOnnxOnlineRecongitionResult { 199 class SherpaOnnxOnlineRecongitionResult {
200 /// A pointer to the underlying counterpart in C 200 /// A pointer to the underlying counterpart in C
201 - let result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>! 201 + private let result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>
202 202
203 - /// Return the actual recognition result.  
204 - /// For English models, it contains words separated by spaces.  
205 - /// For Chinese models, it contains Chinese words.  
206 - var text: String {  
207 - return String(cString: result.pointee.text)  
208 - } 203 + private lazy var _text: String = {
  204 + guard let cstr = result.pointee.text else { return "" }
  205 + return String(cString: cstr)
  206 + }()
209 207
210 - var count: Int32 {  
211 - return result.pointee.count 208 + private lazy var _tokens: [String] = {
  209 + guard let tokensPointer = result.pointee.tokens_arr else { return [] }
  210 + return (0..<count).compactMap { index in
  211 + guard let ptr = tokensPointer[index] else { return nil }
  212 + return String(cString: ptr)
212 } 213 }
  214 + }()
213 215
214 - var tokens: [String] {  
215 - if let tokensPointer = result.pointee.tokens_arr {  
216 - var tokens: [String] = []  
217 - for index in 0..<count {  
218 - if let tokenPointer = tokensPointer[Int(index)] {  
219 - let token = String(cString: tokenPointer)  
220 - tokens.append(token)  
221 - }  
222 - }  
223 - return tokens  
224 - } else {  
225 - let tokens: [String] = []  
226 - return tokens  
227 - }  
228 - } 216 + private lazy var _timestamps: [Float] = {
  217 + guard let timestampsPointer = result.pointee.timestamps else { return [] }
  218 + return (0..<count).map { index in timestampsPointer[index] }
  219 + }()
229 220
230 - var timestamps: [Float] {  
231 - if let p = result.pointee.timestamps {  
232 - var timestamps: [Float] = []  
233 - for index in 0..<count {  
234 - timestamps.append(p[Int(index)])  
235 - }  
236 - return timestamps  
237 - } else {  
238 - let timestamps: [Float] = []  
239 - return timestamps  
240 - }  
241 - }  
242 -  
243 - init(result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>!) { 221 + init(result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>) {
244 self.result = result 222 self.result = result
245 } 223 }
246 224
247 deinit { 225 deinit {
248 - if let result {  
249 SherpaOnnxDestroyOnlineRecognizerResult(result) 226 SherpaOnnxDestroyOnlineRecognizerResult(result)
250 } 227 }
251 - } 228 +
  229 + /// Return the actual recognition result.
  230 + /// For English models, it contains words separated by spaces.
  231 + /// For Chinese models, it contains Chinese words.
  232 + var text: String { _text }
  233 +
  234 + var count: Int { Int(result.pointee.count) }
  235 +
  236 + var tokens: [String] { _tokens }
  237 +
  238 + var timestamps: [Float] { _timestamps }
252 } 239 }
253 240
254 class SherpaOnnxRecognizer { 241 class SherpaOnnxRecognizer {
255 /// A pointer to the underlying counterpart in C 242 /// A pointer to the underlying counterpart in C
256 - let recognizer: OpaquePointer!  
257 - var stream: OpaquePointer! 243 + private let recognizer: OpaquePointer
  244 + private var stream: OpaquePointer
  245 + private let lock = NSLock() // for thread-safe stream replacement
258 246
259 /// Constructor taking a model config 247 /// Constructor taking a model config
260 init( 248 init(
261 - config: UnsafePointer<SherpaOnnxOnlineRecognizerConfig>! 249 + config: UnsafePointer<SherpaOnnxOnlineRecognizerConfig>
262 ) { 250 ) {
263 - recognizer = SherpaOnnxCreateOnlineRecognizer(config)  
264 - stream = SherpaOnnxCreateOnlineStream(recognizer) 251 + self.recognizer = SherpaOnnxCreateOnlineRecognizer(config)
  252 + self.stream = SherpaOnnxCreateOnlineStream(recognizer)
265 } 253 }
266 254
267 deinit { 255 deinit {
268 - if let stream {  
269 SherpaOnnxDestroyOnlineStream(stream) 256 SherpaOnnxDestroyOnlineStream(stream)
270 - }  
271 -  
272 - if let recognizer {  
273 SherpaOnnxDestroyOnlineRecognizer(recognizer) 257 SherpaOnnxDestroyOnlineRecognizer(recognizer)
274 } 258 }
275 - }  
276 259
277 /// Decode wave samples. 260 /// Decode wave samples.
278 /// 261 ///
@@ -280,12 +263,12 @@ class SherpaOnnxRecognizer { @@ -280,12 +263,12 @@ class SherpaOnnxRecognizer {
280 /// - samples: Audio samples normalized to the range [-1, 1] 263 /// - samples: Audio samples normalized to the range [-1, 1]
281 /// - sampleRate: Sample rate of the input audio samples. Must match 264 /// - sampleRate: Sample rate of the input audio samples. Must match
282 /// the one expected by the model. 265 /// the one expected by the model.
283 - func acceptWaveform(samples: [Float], sampleRate: Int = 16000) { 266 + func acceptWaveform(samples: [Float], sampleRate: Int = 16_000) {
284 SherpaOnnxOnlineStreamAcceptWaveform(stream, Int32(sampleRate), samples, Int32(samples.count)) 267 SherpaOnnxOnlineStreamAcceptWaveform(stream, Int32(sampleRate), samples, Int32(samples.count))
285 } 268 }
286 269
287 func isReady() -> Bool { 270 func isReady() -> Bool {
288 - return SherpaOnnxIsOnlineStreamReady(recognizer, stream) == 1 ? true : false 271 + return SherpaOnnxIsOnlineStreamReady(recognizer, stream) != 0
289 } 272 }
290 273
291 /// If there are enough number of feature frames, it invokes the neural 274 /// If there are enough number of feature frames, it invokes the neural
@@ -296,8 +279,9 @@ class SherpaOnnxRecognizer { @@ -296,8 +279,9 @@ class SherpaOnnxRecognizer {
296 279
297 /// Get the decoding results so far 280 /// Get the decoding results so far
298 func getResult() -> SherpaOnnxOnlineRecongitionResult { 281 func getResult() -> SherpaOnnxOnlineRecongitionResult {
299 - let result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>? = SherpaOnnxGetOnlineStreamResult(  
300 - recognizer, stream) 282 + guard let result = SherpaOnnxGetOnlineStreamResult(recognizer, stream) else {
  283 + fatalError("SherpaOnnxGetOnlineStreamResult returned nil")
  284 + }
301 return SherpaOnnxOnlineRecongitionResult(result: result) 285 return SherpaOnnxOnlineRecongitionResult(result: result)
302 } 286 }
303 287
@@ -313,12 +297,14 @@ class SherpaOnnxRecognizer { @@ -313,12 +297,14 @@ class SherpaOnnxRecognizer {
313 } 297 }
314 298
315 words.withCString { cString in 299 words.withCString { cString in
316 - let newStream = SherpaOnnxCreateOnlineStreamWithHotwords(recognizer, cString) 300 + guard let newStream = SherpaOnnxCreateOnlineStreamWithHotwords(recognizer, cString) else {
  301 + fatalError("SherpaOnnxCreateOnlineStreamWithHotwords returned nil")
  302 + }
  303 + lock.lock()
317 // lock while release and replace stream 304 // lock while release and replace stream
318 - objc_sync_enter(self)  
319 SherpaOnnxDestroyOnlineStream(stream) 305 SherpaOnnxDestroyOnlineStream(stream)
320 stream = newStream 306 stream = newStream
321 - objc_sync_exit(self) 307 + lock.unlock()
322 } 308 }
323 } 309 }
324 310
@@ -330,7 +316,7 @@ class SherpaOnnxRecognizer { @@ -330,7 +316,7 @@ class SherpaOnnxRecognizer {
330 316
331 /// Return true is an endpoint has been detected. 317 /// Return true is an endpoint has been detected.
332 func isEndpoint() -> Bool { 318 func isEndpoint() -> Bool {
333 - return SherpaOnnxOnlineStreamIsEndpoint(recognizer, stream) == 1 ? true : false 319 + return SherpaOnnxOnlineStreamIsEndpoint(recognizer, stream) != 0
334 } 320 }
335 } 321 }
336 322
@@ -541,31 +527,39 @@ func sherpaOnnxOfflineRecognizerConfig( @@ -541,31 +527,39 @@ func sherpaOnnxOfflineRecognizerConfig(
541 527
542 class SherpaOnnxOfflineRecongitionResult { 528 class SherpaOnnxOfflineRecongitionResult {
543 /// A pointer to the underlying counterpart in C 529 /// A pointer to the underlying counterpart in C
544 - let result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>! 530 + let result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>
  531 +
  532 + private lazy var _text: String = {
  533 + guard let cstr = result.pointee.text else { return "" }
  534 + return String(cString: cstr)
  535 + }()
  536 +
  537 + private lazy var _timestamps: [Float] = {
  538 + guard let p = result.pointee.timestamps else { return [] }
  539 + return (0..<result.pointee.count).map { p[Int($0)] }
  540 + }()
  541 +
  542 + private lazy var _lang: String = {
  543 + guard let cstr = result.pointee.lang else { return "" }
  544 + return String(cString: cstr)
  545 + }()
  546 +
  547 + private lazy var _emotion: String = {
  548 + guard let cstr = result.pointee.emotion else { return "" }
  549 + return String(cString: cstr)
  550 + }()
  551 +
  552 + private lazy var _event: String = {
  553 + guard let cstr = result.pointee.event else { return "" }
  554 + return String(cString: cstr)
  555 + }()
545 556
546 /// Return the actual recognition result. 557 /// Return the actual recognition result.
547 /// For English models, it contains words separated by spaces. 558 /// For English models, it contains words separated by spaces.
548 /// For Chinese models, it contains Chinese words. 559 /// For Chinese models, it contains Chinese words.
549 - var text: String {  
550 - return String(cString: result.pointee.text)  
551 - }  
552 -  
553 - var count: Int32 {  
554 - return result.pointee.count  
555 - }  
556 -  
557 - var timestamps: [Float] {  
558 - if let p = result.pointee.timestamps {  
559 - var timestamps: [Float] = []  
560 - for index in 0..<count {  
561 - timestamps.append(p[Int(index)])  
562 - }  
563 - return timestamps  
564 - } else {  
565 - let timestamps: [Float] = []  
566 - return timestamps  
567 - }  
568 - } 560 + var text: String { _text }
  561 + var count: Int { Int(result.pointee.count) }
  562 + var timestamps: [Float] { _timestamps }
569 563
570 // For SenseVoice models, it can be zh, en, ja, yue, ko 564 // For SenseVoice models, it can be zh, en, ja, yue, ko
571 // where zh is for Chinese 565 // where zh is for Chinese
@@ -573,46 +567,39 @@ class SherpaOnnxOfflineRecongitionResult { @@ -573,46 +567,39 @@ class SherpaOnnxOfflineRecongitionResult {
573 // ja is for Japanese 567 // ja is for Japanese
574 // yue is for Cantonese 568 // yue is for Cantonese
575 // ko is for Korean 569 // ko is for Korean
576 - var lang: String {  
577 - return String(cString: result.pointee.lang)  
578 - } 570 + var lang: String { _lang }
579 571
580 // for SenseVoice models 572 // for SenseVoice models
581 - var emotion: String {  
582 - return String(cString: result.pointee.emotion)  
583 - } 573 + var emotion: String { _emotion }
584 574
585 // for SenseVoice models 575 // for SenseVoice models
586 - var event: String {  
587 - return String(cString: result.pointee.event)  
588 - } 576 + var event: String { _event }
589 577
590 - init(result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>!) { 578 + init(result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>) {
591 self.result = result 579 self.result = result
592 } 580 }
593 581
594 deinit { 582 deinit {
595 - if let result {  
596 SherpaOnnxDestroyOfflineRecognizerResult(result) 583 SherpaOnnxDestroyOfflineRecognizerResult(result)
597 } 584 }
598 - }  
599 } 585 }
600 586
601 class SherpaOnnxOfflineRecognizer { 587 class SherpaOnnxOfflineRecognizer {
602 /// A pointer to the underlying counterpart in C 588 /// A pointer to the underlying counterpart in C
603 - let recognizer: OpaquePointer! 589 + private let recognizer: OpaquePointer
604 590
605 init( 591 init(
606 - config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>! 592 + config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>
607 ) { 593 ) {
608 - recognizer = SherpaOnnxCreateOfflineRecognizer(config) 594 + guard let ptr = SherpaOnnxCreateOfflineRecognizer(config) else {
  595 + fatalError("Failed to create SherpaOnnxOfflineRecognizer")
  596 + }
  597 + self.recognizer = ptr
609 } 598 }
610 599
611 deinit { 600 deinit {
612 - if let recognizer {  
613 SherpaOnnxDestroyOfflineRecognizer(recognizer) 601 SherpaOnnxDestroyOfflineRecognizer(recognizer)
614 } 602 }
615 - }  
616 603
617 /// Decode wave samples. 604 /// Decode wave samples.
618 /// 605 ///
@@ -620,23 +607,25 @@ class SherpaOnnxOfflineRecognizer { @@ -620,23 +607,25 @@ class SherpaOnnxOfflineRecognizer {
620 /// - samples: Audio samples normalized to the range [-1, 1] 607 /// - samples: Audio samples normalized to the range [-1, 1]
621 /// - sampleRate: Sample rate of the input audio samples. Must match 608 /// - sampleRate: Sample rate of the input audio samples. Must match
622 /// the one expected by the model. 609 /// the one expected by the model.
623 - func decode(samples: [Float], sampleRate: Int = 16000) -> SherpaOnnxOfflineRecongitionResult {  
624 - let stream: OpaquePointer! = SherpaOnnxCreateOfflineStream(recognizer) 610 + func decode(samples: [Float], sampleRate: Int = 16_000) -> SherpaOnnxOfflineRecongitionResult {
  611 + guard let stream = SherpaOnnxCreateOfflineStream(recognizer) else {
  612 + fatalError("Failed to create offline stream")
  613 + }
  614 +
  615 + defer { SherpaOnnxDestroyOfflineStream(stream) }
625 616
626 SherpaOnnxAcceptWaveformOffline(stream, Int32(sampleRate), samples, Int32(samples.count)) 617 SherpaOnnxAcceptWaveformOffline(stream, Int32(sampleRate), samples, Int32(samples.count))
627 618
628 SherpaOnnxDecodeOfflineStream(recognizer, stream) 619 SherpaOnnxDecodeOfflineStream(recognizer, stream)
629 620
630 - let result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>? =  
631 - SherpaOnnxGetOfflineStreamResult(  
632 - stream)  
633 -  
634 - SherpaOnnxDestroyOfflineStream(stream) 621 + guard let resultPtr = SherpaOnnxGetOfflineStreamResult(stream) else {
  622 + fatalError("Failed to get offline recognition result")
  623 + }
635 624
636 - return SherpaOnnxOfflineRecongitionResult(result: result) 625 + return SherpaOnnxOfflineRecongitionResult(result: resultPtr)
637 } 626 }
638 627
639 - func setConfig(config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>!) { 628 + func setConfig(config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>) {
640 SherpaOnnxOfflineRecognizerSetConfig(recognizer, config) 629 SherpaOnnxOfflineRecognizerSetConfig(recognizer, config)
641 } 630 }
642 } 631 }
@@ -696,37 +685,38 @@ func sherpaOnnxVadModelConfig( @@ -696,37 +685,38 @@ func sherpaOnnxVadModelConfig(
696 } 685 }
697 686
698 class SherpaOnnxCircularBufferWrapper { 687 class SherpaOnnxCircularBufferWrapper {
699 - let buffer: OpaquePointer! 688 + private let buffer: OpaquePointer
700 689
701 init(capacity: Int) { 690 init(capacity: Int) {
702 - buffer = SherpaOnnxCreateCircularBuffer(Int32(capacity)) 691 + guard let ptr = SherpaOnnxCreateCircularBuffer(Int32(capacity)) else {
  692 + fatalError("Failed to create SherpaOnnxCircularBuffer")
  693 + }
  694 + self.buffer = ptr
703 } 695 }
704 696
705 deinit { 697 deinit {
706 - if let buffer {  
707 SherpaOnnxDestroyCircularBuffer(buffer) 698 SherpaOnnxDestroyCircularBuffer(buffer)
708 } 699 }
709 - }  
710 700
711 func push(samples: [Float]) { 701 func push(samples: [Float]) {
  702 + guard !samples.isEmpty else { return }
712 SherpaOnnxCircularBufferPush(buffer, samples, Int32(samples.count)) 703 SherpaOnnxCircularBufferPush(buffer, samples, Int32(samples.count))
713 } 704 }
714 705
715 func get(startIndex: Int, n: Int) -> [Float] { 706 func get(startIndex: Int, n: Int) -> [Float] {
716 - let p: UnsafePointer<Float>! = SherpaOnnxCircularBufferGet(buffer, Int32(startIndex), Int32(n)) 707 + guard startIndex >= 0 else { return [] }
  708 + guard n > 0 else { return [] }
717 709
718 - var samples: [Float] = []  
719 -  
720 - for index in 0..<n {  
721 - samples.append(p[Int(index)]) 710 + guard let ptr = SherpaOnnxCircularBufferGet(buffer, Int32(startIndex), Int32(n)) else {
  711 + return []
722 } 712 }
  713 + defer { SherpaOnnxCircularBufferFree(ptr) }
723 714
724 - SherpaOnnxCircularBufferFree(p)  
725 -  
726 - return samples 715 + return Array(UnsafeBufferPointer(start: ptr, count: n))
727 } 716 }
728 717
729 func pop(n: Int) { 718 func pop(n: Int) {
  719 + guard n > 0 else { return }
730 SherpaOnnxCircularBufferPop(buffer, Int32(n)) 720 SherpaOnnxCircularBufferPop(buffer, Int32(n))
731 } 721 }
732 722
@@ -740,48 +730,43 @@ class SherpaOnnxCircularBufferWrapper { @@ -740,48 +730,43 @@ class SherpaOnnxCircularBufferWrapper {
740 } 730 }
741 731
742 class SherpaOnnxSpeechSegmentWrapper { 732 class SherpaOnnxSpeechSegmentWrapper {
743 - let p: UnsafePointer<SherpaOnnxSpeechSegment>! 733 + private let p: UnsafePointer<SherpaOnnxSpeechSegment>
744 734
745 - init(p: UnsafePointer<SherpaOnnxSpeechSegment>!) { 735 + init(p: UnsafePointer<SherpaOnnxSpeechSegment>) {
746 self.p = p 736 self.p = p
747 } 737 }
748 738
749 deinit { 739 deinit {
750 - if let p {  
751 SherpaOnnxDestroySpeechSegment(p) 740 SherpaOnnxDestroySpeechSegment(p)
752 } 741 }
753 - }  
754 742
755 var start: Int { 743 var start: Int {
756 - return Int(p.pointee.start) 744 + Int(p.pointee.start)
757 } 745 }
758 746
759 var n: Int { 747 var n: Int {
760 - return Int(p.pointee.n) 748 + Int(p.pointee.n)
761 } 749 }
762 750
763 - var samples: [Float] {  
764 - var samples: [Float] = []  
765 - for index in 0..<n {  
766 - samples.append(p.pointee.samples[Int(index)])  
767 - }  
768 - return samples  
769 - } 751 + lazy var samples: [Float] = {
  752 + Array(UnsafeBufferPointer(start: p.pointee.samples, count: n))
  753 + }()
770 } 754 }
771 755
772 class SherpaOnnxVoiceActivityDetectorWrapper { 756 class SherpaOnnxVoiceActivityDetectorWrapper {
773 /// A pointer to the underlying counterpart in C 757 /// A pointer to the underlying counterpart in C
774 - let vad: OpaquePointer! 758 + private let vad: OpaquePointer
775 759
776 - init(config: UnsafePointer<SherpaOnnxVadModelConfig>!, buffer_size_in_seconds: Float) {  
777 - vad = SherpaOnnxCreateVoiceActivityDetector(config, buffer_size_in_seconds) 760 + init(config: UnsafePointer<SherpaOnnxVadModelConfig>, buffer_size_in_seconds: Float) {
  761 + guard let vad = SherpaOnnxCreateVoiceActivityDetector(config, buffer_size_in_seconds) else {
  762 + fatalError("SherpaOnnxCreateVoiceActivityDetector returned nil")
  763 + }
  764 + self.vad = vad
778 } 765 }
779 766
780 deinit { 767 deinit {
781 - if let vad {  
782 SherpaOnnxDestroyVoiceActivityDetector(vad) 768 SherpaOnnxDestroyVoiceActivityDetector(vad)
783 } 769 }
784 - }  
785 770
786 func acceptWaveform(samples: [Float]) { 771 func acceptWaveform(samples: [Float]) {
787 SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples, Int32(samples.count)) 772 SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples, Int32(samples.count))
@@ -804,7 +789,9 @@ class SherpaOnnxVoiceActivityDetectorWrapper { @@ -804,7 +789,9 @@ class SherpaOnnxVoiceActivityDetectorWrapper {
804 } 789 }
805 790
806 func front() -> SherpaOnnxSpeechSegmentWrapper { 791 func front() -> SherpaOnnxSpeechSegmentWrapper {
807 - let p: UnsafePointer<SherpaOnnxSpeechSegment>? = SherpaOnnxVoiceActivityDetectorFront(vad) 792 + guard let p = SherpaOnnxVoiceActivityDetectorFront(vad) else {
  793 + fatalError("SherpaOnnxVoiceActivityDetectorFront returned nil")
  794 + }
808 return SherpaOnnxSpeechSegmentWrapper(p: p) 795 return SherpaOnnxSpeechSegmentWrapper(p: p)
809 } 796 }
810 797
@@ -94,9 +94,9 @@ class SpeechSegment: CustomStringConvertible { @@ -94,9 +94,9 @@ class SpeechSegment: CustomStringConvertible {
94 func run() { 94 func run() {
95 var recognizer: SherpaOnnxOfflineRecognizer 95 var recognizer: SherpaOnnxOfflineRecognizer
96 var modelConfig: SherpaOnnxOfflineModelConfig 96 var modelConfig: SherpaOnnxOfflineModelConfig
97 - var modelType = "whisper" 97 + let modelType = "whisper"
98 // modelType = "paraformer" 98 // modelType = "paraformer"
99 - var filePath = "/Users/fangjun/Desktop/Obama.wav" // English 99 + let filePath = "/Users/fangjun/Desktop/Obama.wav" // English
100 // filePath = "/Users/fangjun/Desktop/lei-jun.wav" // Chinese 100 // filePath = "/Users/fangjun/Desktop/lei-jun.wav" // Chinese
101 // please go to https://huggingface.co/csukuangfj/vad 101 // please go to https://huggingface.co/csukuangfj/vad
102 // to download the above two files 102 // to download the above two files
@@ -192,7 +192,7 @@ func run() { @@ -192,7 +192,7 @@ func run() {
192 let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount) 192 let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)
193 193
194 try! audioFile.read(into: audioFileBuffer!) 194 try! audioFile.read(into: audioFileBuffer!)
195 - var array: [Float]! = audioFileBuffer?.array() 195 + let array: [Float]! = audioFileBuffer?.array()
196 196
197 var segments: [SpeechSegment] = [] 197 var segments: [SpeechSegment] = []
198 198