Gold Challenge - I got carried away

Hi all,
as I’ve said in the title, I was carried away by the challenge and I kept building features over features. The VocalTextEdit now has the opportunity to read in 2 languages, increase the size of text from the menu, undo and redo functionality, a check button to allow the voice to tell you how many words are in the text and a pause/resume button.
There is something that keep me banging my head on the wall:
When I choose English voice Alex the pause/resume feature works like a charm but when I use the Italian Alice voice it seems that the didFinishSpeaking got called anyway. I’ve tried swap the code between the 2 voices with no luck. I’m baffled. I’m sure that is something right in front of my eyes but I can’t quite figure out what it is and I hope that someone of you, more experienced coders, will point me towards the solution. To be more precise if I use the Italian voices(female and male) and I pause during the reading after a second of pause the didFinishSpeaking got called and it stops

//
//  ViewController.swift
//  VocalTextEdit
//
//  Created by Stefano Ballirano on 20/03/21.
//

import Cocoa

var numberOfWords = 0


class ViewController: NSViewController, NSSpeechSynthesizerDelegate, NSTextDelegate, NSTextViewDelegate {

var speechSynthesizers = NSSpeechSynthesizer()

@IBOutlet weak var textView: NSTextView!
@IBOutlet weak var voiceChoice: NSPopUpButton!
@IBOutlet weak var speakButton: NSButton!
@IBOutlet weak var stopButton: NSButton!
@IBOutlet weak var progressBar: NSProgressIndicator!
@IBOutlet weak var enableSpeakInfo: NSButton!
@IBOutlet weak var pauseButton: NSButton!

var content: String {
    
    get {
        
        return textView.string
    }
    
    set {
        
        textView.string = newValue
        
    }

}
    
override func viewDidLoad() {
    
    voiceChoice.removeAllItems()
    voiceChoice.addItems(withTitles: ["Inglese", "Italiano"])
    content = textView.string
  
    speakButton.isEnabled = true
    stopButton.isEnabled = false
    progressBar.doubleValue = 0.0
    progressBar.isHidden = true
    progressBar.isIndeterminate = false
    progressBar.controlSize = .large
    progressBar.controlTint = .defaultControlTint
    speechSynthesizers.delegate = self
    self.textView.delegate = self
    enableSpeakInfo.state = .off
    
}

func evaluateNumberOfWords(textView: NSTextView) -> Int {
    
    return textView.string.components(separatedBy: " ").count + 1
    
}

func setSpeakingVoice(speech: NSSpeechSynthesizer, lingua: String) {
    
    if lingua == "Inglese" {
        
        speech.setVoice(NSSpeechSynthesizer.VoiceName.init(rawValue: "com.apple.speech.synthesis.voice.Alex"))
        
    } else if lingua == "Italiano" {
        
        speech.setVoice(NSSpeechSynthesizer.VoiceName.init(rawValue: "com.apple.speech.synthesis.voice.alice.premium"))
        
    }
    
}

@IBAction func speakButtonClicked(sender: NSButton) {
    
    progressBar.isHidden = false
    progressBar.doubleValue = 0.0
    progressBar.isBezeled = true
    speakButton.isEnabled = false
    stopButton.isEnabled = true

    numberOfWords = evaluateNumberOfWords(textView: textView)
            
    if !textView.string.isEmpty {
        
        if voiceChoice.titleOfSelectedItem == "Inglese" {
            
            setSpeakingVoice(speech: speechSynthesizers, lingua: "Inglese")
            
            if enableSpeakInfo.state == .on {
                
                speechSynthesizers.startSpeaking("The text is \(numberOfWords) words long. " + textView.string )
                
            } else {
                
                speechSynthesizers.startSpeaking(textView.string)
                
            }
            
        } else if voiceChoice.titleOfSelectedItem == "Italiano"{
            
        
            setSpeakingVoice(speech: speechSynthesizers, lingua: "Italiano")

            
            if enableSpeakInfo.state == .on {
                
                speechSynthesizers.startSpeaking("Il testo è lungo \(numberOfWords) parole. " + textView.string )
                
            } else {
                
                speechSynthesizers.startSpeaking(textView.string)
                
            }
            
        }

    } else {
        
        if voiceChoice.titleOfSelectedItem == "Inglese" {
            
            setSpeakingVoice(speech: speechSynthesizers, lingua: "Inglese")
            speechSynthesizers.startSpeaking("The document is empty")
        } else {
            
            setSpeakingVoice(speech: speechSynthesizers, lingua: "Italiano")
            speechSynthesizers.startSpeaking("Il documento è vuoto")
        }
        
    }

}

@IBAction func stopButtonClicked(sender: NSButton) {
    
    speechSynthesizers.stopSpeaking()

}

func speechSynthesizer(_ sender: NSSpeechSynthesizer, didFinishSpeaking finishedSpeaking: Bool) {
   
    speakButton.isEnabled = true
    stopButton.isEnabled = false
    progressBar.doubleValue = 100.0
    progressBar.isHidden = true
        
}

func speechSynthesizer(_ sender: NSSpeechSynthesizer, willSpeakWord characterRange: NSRange, of string: String) {
    
    let totalCharacterNumber = Double(string.count)
    let spacesCount = string.components(separatedBy: " ")
    let charactersToSay = Double(characterRange.length)
    progressBar.increment(by: ( (100.0 / totalCharacterNumber)  * charactersToSay)  + ((Double(spacesCount.count - 1) * (100.0 / totalCharacterNumber) / (Double(spacesCount.count)) ) ) )

}

func textDidChange(_ notification: Notification) {
    numberOfWords = evaluateNumberOfWords(textView: textView)

}

@IBAction func increaseFontSize(sender: NSMenuItem) {
    
    let actualFontSize = textView.font?.pointSize
    let font = NSFont(name: "Helvetica", size: actualFontSize! + 1)
    textView.font = font
    
}

@IBAction func decreaseFontSize(sender: NSMenuItem) {
    
    let actualFontSize = textView.font?.pointSize
    let font = NSFont(name: "Helvetica", size: actualFontSize! - 1)
    textView.font = font
    
}
 
@IBAction func buttonActionUndo(_ sender: Any) {
 
   undoManager?.undo()
}
    
@IBAction func buttonActionRedo(_ sender: Any) {
     
   undoManager?.redo()
}

@IBAction func pauseSpeaking(_ sender: NSButton) {
    
    if sender.title == "PAUSE" {

        sender.title = "RESUME"
        
        speechSynthesizers.pauseSpeaking(at: .wordBoundary)
        
    } else {
        
        sender.title = "PAUSE"
        
        speechSynthesizers.continueSpeaking()
        
    }
     
}   

}

What is the default language selection when the program starts, English or Italian? Are you always playing English first before trying Italian? Does the behavior change if you select Italian before using the speech synthesizer for the first time? I’m wondering if the speech synthesizer is having a problem with changing the voice and is only working correctly with the first voice that is used.

Also, have you tried changing the Italian voice to something other than “com.apple.speech.synthesis.voice.alice.premium”? It may be there’s an issue with that particular voice.

Thank you for the answer. I’ve tried both. I’ve done many many tries with different permutations. The order seems not to be the issue. I’ve tried two different Italian voices. Same behavior. I’ve checked with another English voice (Fred) and it worked perfectly. It seems that has something to do with only Italian voices. BTW the default system voice is of course Italian :wink: Maybe it could be a bug?

Have you tried creating a new NSSpeechSynthesizer instance each time you change the voice?