I took me quite a while to work out how to populate a list of available voices with the proper names in Mavericks when there is a mix of legacy voices and the newer Nuance “premium” voices.
I had seen other methods that involved text delimiting the names identifiers to extract the name and testing for “premium” and then capitalization, etc. Instead I’ve come up with a simpler solution using NSSpeechSynthesizer and I’d like to share it so no one else has to go through the frustration I did trying to figure this out.
First make a couple global properties at the top of your script for the list of voices (this will hold a list of the actual voice identifier names as the system reads them) and the user selected voice, as well as an outlet for the popup.
property voiceList : {}
property currentVoice : ""
property voiceListPopup : missing value
Now in the “applicationWillFinishLaunching” handler insert the following code to query NSSpeechSynthesis during launch for the list of available voices and then also ask its voice attributes for the human-readable name of each voice to populate the pop-up. Whether legacy or “premium,” each voice already has a proper human-readable name in the “NSVoiceName” key value for attributesForVoice:(voiceIdentity) record (NSDictionary). It took me a while to realize that in ASObj-C I didn’t need the “NS” for the key name. Basically I put the system-provided list of the identifier names of all voices into the global list “voiceList” (for use in other handlers) and I made a new version of that list called voiceListReadable that will only be used to populate the popup during launch.
on applicationWillFinishLaunching:aNotification
-- Insert code here to initialize your application before any files are opened
-- Prepare the available voice popup on launch
set voiceList to current application's NSSpeechSynthesizer's availableVoices() as list
set voiceListReadable to {} as list
repeat with i from 1 to (length of voiceList)
set voiceAttributes to (current application's NSSpeechSynthesizer's attributesForVoice:(item i of voiceList as string))
set readableVoiceName to (voiceAttributes's valueForKey:"VoiceName") as string
set end of voiceListReadable to readableVoiceName
end repeat
-- Populate voice list popup with voice list
voiceListPopup's addItemsWithTitles:voiceListReadable
end applicationWillFinishLaunching:
Now create an action handler to change the currentVoice variable every time the user selects a new one from the popup. I’ve added the “-1” because the index numbers we want don’t include the extra option at the top of the popup list for the default System Voice.
on setNewVoice:sender
if voiceListPopup's indexOfSelectedItem = 1 then
set currentVoice to theSynth's defaultVoice as string
else
set currentVoice to item ((voiceListPopup's indexOfSelectedItem as integer) - 1) of voiceList as string
end if
end setNewVoice:
Wherever you create a NSSpeechSynthesizer instance you just have to tell it to use the currentVoice. This bit assumes you have a push button linked to a “sayHelloButton” action.
on sayHelloButton:sender
set voiceText to "Hello World!" as string
set theSynth to current application's NSSpeechSynthesizer's new()
theSynth's setDelegate:me
theSynth's setVoice:currentVoice
theSynth's startSpeakingString:voiceText
end sayHelloButton:
Finally, add a popup to your nib, put “System Voice” and a divider at the top. The code executed at launch will automatically fill the rest of the list.
CTRL-click on your app delegate object (the blue cube) and connect both the voiceListPopup outlet and the setNewVoice action to the popup button.
Now build and run.
Notice that this works perfectly on “Samantha” (a.k.a. Siri) that I manually downloaded in the System Pref pane for Dictation and Speech, instead of having to decipher com.apple.speech.synthesis.voice.samantha.premium.
For my next project I plan to sort by language and gender, which are also keys in attributesForVoice:
Any help in this endeavour would be greatly appreciated.
Tim Dashwood