ftp.nice.ch/pub/next/unix/audio/saystring.c

This is saystring.c in view mode; [Download] [Up]

/* saystring.c [version 0.8 for NeXT Release 2.1]
 * A command line program to speak a text string through NeXT audio out.
 *
 * This is essentially a program that plays concatenated sound files of words with
 * some special treatment for numbers.  As such, it only can speak words that have
 * corresponding sound files.  Numbers are spoken by concatenating digit sounds.
 * The program is very simple and low overhead, but should be quite usable for
 * applications that needs only limited vocaburary.  This has been written to
 * provide voice alerts for monitoring experimental parameters in a biology lab.
 * (Commands come from remote devices via rsh.)
 *
 * Usage:   saystring "A line to be spoken"
 * Example: saystring "temperature #39.4 degrees is too_high"
 * Example: saystring "our phone number is #6 #4 #2 _ #6 #4 #4 #0"
 *
 * You must prepare sound files yourself for numbers and any other words you use.
 * Sound files should be in /usr/local/lib/saystring_sounds directory, but can be changed
 * by SOUNDDIR define below and recompiling.  All sound files currently have to
 * be in CODEC (8-bit mu-law, ~8kHz sampling) format, but this can be changed as well.
 *
 * To compile: cc -Wall -o saystring saystring.c
 *	will work.  You can use strip, or other options to make the executable smaller.
 *
 * This program is in the Public Domain.  Do anything you want with it.
 *
 * Version 0.8
 * 92-08-22 Izumi Ohzawa
 * This is a sloppy one-night hack.  Only good for single shot uses (Program should
 * terminate for each use) because memory deallocation is pretty much ingnored.
 * Fix malloc/free if you want to incorporate into a long running app as a subroutine.
 * Only handles floats and integers in the range of +/- 9999.9999999...
 * It should not be hard to extend it if you want to.
 */

#import <stdio.h>
#import <string.h>		/* for strlen, rindex, etc */
#import <stdlib.h>
#import <sys/param.h>
#import <sound/sound.h>

/* Change this if you want a different directory to store sound files */
#define	SOUNDDIR	"/usr/local/lib/saystring_sounds"
#define	TAG		1
#define MAXWORDS	200

/*==== Global Vars for sloppy programming ===================================================== */
/* You must prepare the following *.snd files for numbers to be spoken correctly.
 * In addition, you will need sound files for: "minus", "point", "thousand", "hundred".
 */
char *dig09[] = {"zero","one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
char *teens[] = {"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
		 "seventeen", "eighteen", "nineteen" };
char *tens[] = {"", "ten","twenty","thirty","forty", "fifty", "sixty",
		"seventy", "eighty", "ninety" };

char *word[MAXWORDS];
int nwords = 0;
char *p;
char buf[128];

void do_number(void)
{
int minus = 0;		/* non-zero if negative */
int isfrac = 0;		/* non-zero if there is a decimal point */
int ipart, temp, saveipart;
char ch[2];
char *frac;
	ch[1] =0;
	sscanf(p, "%s", buf);		/* get the number into string */
	if(strlen(buf) == 0) return;
	if(atof(buf) < 0.0)		/* say "minus" first ? */
	{
	    minus = 1;			/* indicate negative */
	    word[nwords++] = "minus";
	}
	if((frac = index(buf, '.')))	/* Do we have a decimal point? */
	{
	    *frac = '\0';		/* replace decimal pt with null char */
	    frac++;			/* fraction part */
	    if(strlen(frac))		/* if there is anything after '.', it is a float # */
	        isfrac =1;
	}
	ipart = atoi(buf);		/* integer part. buf==NULL if e.g. #.123, which is OK */
	if(ipart < 0) ipart = -ipart;	/* make positive */
	if(ipart > 9999) ipart = 9999;	/* Our current implementation limit */
	saveipart = ipart;		/* save a copy for 0 */

	/* do thousands */
	if((temp = ipart/1000))
	{
	    word[nwords++] = dig09[temp];
	    word[nwords++] = "thousand";
	}

	/* hundreds */
	ipart -= temp *1000;		/* get rid of thousands */
	if((temp = ipart/100))
	{
	    word[nwords++] = dig09[temp];
	    word[nwords++] = "hundred";
	}

	/* tens */
	ipart -= temp *100;		/* get rid of hundreds */
	if((temp = ipart/10))
	{
	    if(temp ==1)		/* teens */
	    {
		word[nwords++] = teens[ipart-10];
	        goto do_fraction;
	    }
	    else
		word[nwords++] = tens[temp];
	}

	/* ones */
	ipart -= temp*10;		/* get rid of tens */
	if(ipart || saveipart == 0)
	    word[nwords++] = dig09[ipart];

do_fraction:
	if(isfrac)			/* only if there is anything after '.' */
	{
	    word[nwords++] = "point";
	    while(*frac != '\0')	/* just speak digits sequentiall */
	    {
		ch[0] = *frac++;
		temp = atoi(ch);
		word[nwords++] = dig09[temp];
	    }
	}
}


void parse_words(char *str)
{
    p = str;
    while(1)
    {
	while( *p == ' ' && *p != '\0' ) p++;		/* position p to head of word */
	if( *p == '\0') break;				/* EXIT endless loop */
	sscanf(p, "%s", buf);				/* get a word */
	if(buf[0] == '#')
	{
	    p++;					/* char following # */
	    do_number();
	}
	else
	{
	    word[nwords] = (char *)malloc((size_t)(strlen(buf)+1));
	    strcpy(word[nwords++], buf);
	}
	while( *p != ' ' && *p != '\0') p++;
    }
}


void Serror(int r, int num)
{
	fprintf(stderr,"saystring[%d]: %s\n", num, SNDSoundError(r));
}


int AppendSoundFromFile(char *file, SNDSoundStruct *toSound)
{
int r, r2;
SNDSoundStruct *sound2;
char fullpath[MAXPATHLEN];
    sprintf(fullpath, "%s/%s.snd", SOUNDDIR, file);
    r = SNDReadSoundfile(fullpath, &sound2);
    if(r) return(r);
    r = SNDInsertSamples(toSound, sound2, SNDSampleCount(toSound));
    r2 = SNDFree(sound2);
    if(r)
        return(r);
    else
	return(r2);
}


int main(int argc, char *argv[])
{
int r, i;
int priority = 5;
int preempt = 0;
SNDSoundStruct *sound1;

    if(argc != 2) {
	printf("Usage example: saystring \"temperature #41.3 degrees is too_high\"\n");
	printf("      another: saystring \"our fax number is #1 #2 #3 _ #4 #5 #6 #7\"\n");
	printf("   will speak the strings.  All words except for numbers must have\n");
	printf("   a corresponding soundfile in directory %s.\n", SOUNDDIR);
	printf("   The string must be enclosed in quotation marks unless it is\n");
	printf("   a single word.  Numbers must be preceded by a # sign as above.\n");
	exit(2);
    }

    /* Hmmm, it seems that we don't need SNDAcquire(), SNDRelease() calls for this */

    /* Allocate an empty SNDSoundStruct */
    r = SNDAlloc(&sound1, 0, SND_FORMAT_MULAW_8, SND_RATE_CODEC, 1, 4);
    if(r) {
	Serror(r, 2);
	goto do_exit;
    }

    /* Put the words in word[] array. nwords will have the total # of words */
    parse_words(argv[1]);

    /* Concatenate all words into a single SNDSoundStruct */
    for(i=0; i<nwords; i++)
	r = AppendSoundFromFile(word[i], sound1);    /* ignore non-existent sound files */

    r = SNDStartPlaying(sound1, TAG, priority, preempt, SND_NULL_FUN, SND_NULL_FUN);
    if(r) {
	Serror(r, 6);
	goto do_exit;
    }

    SNDWait(TAG);		/* get blocked untill sound finishes */

do_exit:
    exit(0);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.