craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

Fun With Speech Framework

|

Overview

macOS provides a very powerful speech synthesis framework, easily accessible using built-in tools - say (1) or programmatically via Applications Services APIs. Let’s see how to get started with using the Speech framework.

From the command-line it’s very simple. Just list the available voices and chose one to speak the message. If you’re curious how it would sound in French for example:

$ say --voice="?" | grep fr
Amelie              fr_CA    # Bonjour, je m’appelle Amelie. Je suis une voix canadienne.
Tessa               en_ZA    # Hello, my name is Tessa. I am a South African-English voice.
Thomas              fr_FR    # Bonjour, je m’appelle Thomas. Je suis une voix française.

$ say --voice="Amelie" "The quick brown fox jumps over the lazy dog"

Programmatic approach

To have more flexibility, it’s more interesting to make use of the Speech synthethis APIs. The code below simulates the say command-line tool:

#include <ApplicationServices/ApplicationServices.h>

typedef SInt16 int16;

void checkResult(OSErr error, const char *description) {
    if (error == 0)
        return;
    fprintf(stderr, "Error: %d during %s. exiting.", error, description);
    
    exit(error);
}

int main (int argc, char **argv) {
    SpeechChannel channel;
    VoiceSpec vs;
    SInt16 numVoices, voice;
    CFStringRef message;

    if (argc != 3) {
        printf("[!] Usage: %s <voice number> <message>\n", argv[0]);
        exit(1);
    }

    // List available voices
    checkResult(CountVoices(&numVoices), "CountVoices");
    printf("[*] %d voices available\n", numVoices);
    
    // Get desired voice
    voice = atoi(argv[1]);
    checkResult(GetIndVoice(voice, &vs), "GetVoiceInd");

    // Create a Speech channel
    checkResult(NewSpeechChannel(&vs, &channel), "NewSpeechChannel");

    // Create a string from the message
     message = CFStringCreateWithCString(NULL, argv[2], kCFStringEncodingUTF8);

    // Speak!
    checkResult(SpeakCFString(channel, message, NULL), "SpeakCFString");

    // Release allocated string
    CFRelease(message);
    
    // Wait for speach to finish
    while (SpeechBusy()) 
        sleep(1);
        
    exit(0);
}

The final result should be same:

$ gcc say.c -o mySay -framework ApplicationServices
$ ./mySay 4 "The quick brown fox jumps over the lazy dog"
[*] 47 voices available

References