1 /*
2  * Copyright 1993 Greg Renda and Network Computing Devices, Inc.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation, and that the name Network Computing Devices, Inc. not be
9  * used in advertising or publicity pertaining to distribution of this
10  * software without specific, written prior permission.
11  *
12  * THIS SOFTWARE IS PROVIDED `AS-IS'.  NETWORK COMPUTING DEVICES, INC.,
13  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT
14  * LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
15  * PARTICULAR PURPOSE, OR NONINFRINGEMENT.  IN NO EVENT SHALL NETWORK
16  * COMPUTING DEVICES, INC., BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING
17  * SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF USE, DATA,
18  * OR PROFITS, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF
19  * WHETHER IN AN ACTION IN CONTRACT, TORT OR NEGLIGENCE, ARISING OUT OF OR IN
20  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  *
22  * $NCDId: @(#)audial.c,v 1.14 1995/12/06 01:09:15 greg Exp $
23  *
24  * Author:	Greg Renda <greg@ncd.com>
25  *		recognition by Kevin Martin
26  */
27 
28 /* audial.c - touch tone dialer and recognizer */
29 
30 #include "config.h"
31 
32 #include <stdio.h>
33 #include <sys/types.h>
34 
35 #if !defined(ISC40) && !defined(WIN32)
36 #include <sys/file.h>
37 #endif /* !ISC40 */
38 
39 #if defined(HAVE_STDLIB_H)
40 # include <stdlib.h>
41 #endif
42 
43 #if defined(HAVE_MALLOC_H)
44 # include <malloc.h>
45 #endif
46 
47 #include <audio/Aos.h>			/* for string and other os stuff */
48 #include <audio/Afuncs.h> 		/* for bcopy et. al. */
49 #include <audio/audiolib.h>
50 
51 #if defined(SYSV) || defined(SVR4)
52 #define signal sigset
53 #endif
54 
55 #define USAGE "\
56 usage: audial [-options] [dialing_string]\n\
57 general options:\n\
58     -a audiosvr    audio server\n\
59 dialing options:\n\
60     -s spacing	   spacing between digits in milliseconds (default 100)\n\
61     -p pause	   duration of pause \",\" in milliseconds (default 400)\n\
62     -d duration	   duration of digit in milliseconds (default 100)\n\
63     -v volume      output volume in percent (default 100)\n\
64     legal digits in the dialing_string are: 0123456789abcd*#,\n\
65     all others are ignored\n\
66 recognition options:\n\
67     -r             enable recognition mode\n\
68     -m             use microphone line level\n\
69     -g gain        input gain in percent (default 95)\n\
70     -t time        how long to listen in seconds (default forever)\n\
71 "
72 
73 #define SAMPLE_RATE		4000
74 #define WAVE_FORM		AuWaveFormSine
75 #define DEFAULT_SPACE 		100
76 #define DEFAULT_PAUSE 		400
77 #define DEFAULT_DURATION 	100
78 #define DEFAULT_VOLUME 		100
79 #define	DEFAULT_GAIN		95
80 #define HIGHWATER_MARK		75
81 
82 #define EXPORT_SIZE		(SAMPLE_RATE * 1)
83 
84 #define PAUSE_DIGIT		16
85 #define	DEVICE_ELEMENT		17
86 
87 #ifdef NEEDUSLEEP
88 #ifdef VMS
89 #define MAXINT 0xFFFFFFFF
90 /*
91  * VMS uses a quadword to hold the binary time in nsecs; in addition, the
92  * time that gets passed to SYS$SCHDWK (Scheduled Wakeup) must be negative to
93  * indicate a relative time. OBH-4/16/91
94  */
95 static void
usleep(unsigned int usecs)96 usleep(unsigned int usecs)
97 {
98     double          s1;
99     AuUint32   quad[2];
100 
101     s1 = (double) usecs *-100.0 * 100.0;
102 
103     quad[1] = (AuUint32) MAXINT;
104     s1 += ((double) quad[1]);
105     quad[0] = (AuUint32) s1 + 1;
106     sys$schdwk(0, 0, &quad[0], 0);
107     sys$hiber();
108 }
109 
110 #else
111 
112 #ifdef SYSV
113 #ifndef WIN32
114 #include <poll.h>
115 
116 int
usleep(unsigned int usec)117 usleep(unsigned int usec)
118 {
119     struct pollfd   f;
120     return poll(&f, (unsigned long) 0, usec / 1000);
121 }
122 #else /* WIN32 */
123 #include <winsock.h>
124 
usleep(unsigned int usecs)125 static void usleep(unsigned int usecs)
126 {
127     Sleep(usecs / 1000);
128 }
129 #endif /* WIN32 */
130 
131 #else
132 
133 
134 #include <signal.h>
135 #include <sys/time.h>
136 
137 static void
stopme(int sig)138 stopme(int sig)
139 {
140     signal(SIGALRM, SIG_DFL);
141 }
142 
143 static void
usleep(unsigned int usecs)144 usleep(unsigned int usecs)
145 {
146     void            stopme();
147     struct itimerval ntval,
148                     otval;
149 
150     ntval.it_interval.tv_sec = 0;
151     ntval.it_interval.tv_usec = 0;
152     ntval.it_value.tv_sec = 0;
153     ntval.it_value.tv_usec = usecs;
154     signal(SIGALRM, stopme);
155     setitimer(ITIMER_REAL, &ntval, &otval);
156     pause();
157 }
158 
159 #endif						/* SYSV else not */
160 #endif						/* VMS else not */
161 #endif						/* NEEDUSLEEP */
162 
163 static void
fatalError(const char * message,const char * arg1)164 fatalError(const char *message, const char *arg1)
165 {
166     fprintf(stderr, message, arg1);
167     fprintf(stderr, "\n");
168     exit(1);
169 }
170 
171 #define xlate(_n) ((_n) >= '0' && (_n) <= '9' ? (_n) - '0' :		      \
172 		   ((_n) >= 'A' && (_n) <= 'D' ? (_n) - 'A' + 10 :	      \
173 		    ((_n) >= 'a' && (_n) <= 'd' ? (_n) - 'a' + 10 :	      \
174 		     ((_n) == '*' ? (_n) - '*' + 10 + 4 :		      \
175 		      ((_n) == '#' ? (_n) - '#' + 10 + 4 + 1:		      \
176 		       ((_n) == ',' ? (_n) - ',' + 10 + 4 + 1 + 1:	      \
177 			-1))))))
178 
179 static struct
180 {
181     int             tone1,
182                     tone2;
183 }               map[] =
184 {
185     6, 10,					/* zero */
186     0, 8,					/* one */
187     0, 10,					/* two */
188     0, 12,					/* three */
189     2, 8,					/* four */
190     2, 10,					/* five */
191     2, 12,					/* six */
192     4, 8,					/* seven */
193     4, 10,					/* eight */
194     4, 12,					/* nine */
195     0, 14,					/* A */
196     2, 14,					/* B */
197     4, 14,					/* C */
198     6, 14,					/* D */
199     6, 8,					/* star */
200     6, 12,					/* pound */
201 };
202 
203 #define TONE(_e, _v, _f)						       \
204 {									       \
205     AuMakeElementImportWaveForm(&elements[_e], SAMPLE_RATE, WAVE_FORM,	       \
206 				AuUnlimitedSamples, _f, 1, tone_act);	       \
207     AuMakeElementMultiplyConstant(&elements[(_e) + 1], _e, _v);		       \
208     (_e) += 2;								       \
209 }
210 
211 static          AuFlowID
createDTMFflow(AuServer * aud,AuDeviceID outputDevice,int volume,int duration)212 createDTMFflow(AuServer *aud, AuDeviceID outputDevice, int volume, int duration)
213 {
214     AuFlowID        flow;
215     AuElement       elements[18];
216     AuElementAction dev_act[2],
217                     tone_act[1];
218     unsigned short  inputs[8];
219     int             elNum = 0,
220                     i;
221     AuFixedPoint    v = AuFixedPointFromFraction(1, 200 / volume);
222 
223     flow = AuCreateFlow(aud, NULL);
224 
225     AuMakeNoopAction(&tone_act[0], AuStateAny, AuStateAny, AuReasonAny);
226 
227     TONE(elNum, v, 697);
228     TONE(elNum, v, 770);
229     TONE(elNum, v, 852);
230     TONE(elNum, v, 941);
231     TONE(elNum, v, 1209);
232     TONE(elNum, v, 1336);
233     TONE(elNum, v, 1477);
234     TONE(elNum, v, 1633);
235 
236     for (i = 0; i < 8; i++)
237 	inputs[i] = i * 2 + 1;
238 
239     AuMakeElementSum(&elements[elNum], 8, inputs);
240 
241     AuMakeChangeStateAction(&dev_act[0], AuStateStop, AuStateAny, AuReasonAny,
242 			    flow, AuElementAll, AuStateStop);
243     AuMakeSendNotifyAction(&dev_act[1], AuStateStop, AuStateAny, AuReasonAny);
244 
245     AuMakeElementExportDevice(&elements[elNum + 1], elNum,
246 			      outputDevice, SAMPLE_RATE,
247 			      SAMPLE_RATE / 1000 * duration, 2, dev_act);
248 
249     /* set up the flow */
250     AuSetElements(aud, flow, AuTrue, 18, elements, NULL);
251 
252     return flow;
253 }
254 
255 static void
dial(AuServer * aud,AuFlowID flow,char * dialString,int pause,int spacing)256 dial(AuServer *aud, AuFlowID flow, char *dialString, int pause, int spacing)
257 {
258     AuEvent         event;
259     AuElementState  states[3];
260     int             digit,
261                     pauseTime;
262     AuBool          done;
263 
264     while ((digit = *dialString++))
265 	if ((digit = xlate(digit)) != -1)
266 	{
267 	    if (digit != PAUSE_DIGIT)
268 	    {
269 		/* start up the components */
270 		AuMakeElementState(&states[0], flow, map[digit].tone1,
271 				   AuStateStart);
272 		AuMakeElementState(&states[1], flow, map[digit].tone2,
273 				   AuStateStart);
274 		AuMakeElementState(&states[2], flow, DEVICE_ELEMENT,
275 				   AuStateStart);
276 		AuSetElementStates(aud, 3, states, NULL);
277 
278 		done = AuFalse;
279 
280 		while (!done)
281 		{
282 		    AuNextEvent(aud, AuTrue, &event);	/* dequeue the event */
283 
284 		    done = ((event.type == AuEventTypeElementNotify) &&
285 			    (event.auelementnotify.kind ==
286 			     AuElementNotifyKindState) &&
287 			 (event.auelementnotify.cur_state == AuStateStop) &&
288 		     (event.auelementnotify.element_num == DEVICE_ELEMENT));
289 		}
290 
291 		pauseTime = spacing;
292 	    }
293 	    else
294 		pauseTime = pause;
295 
296 	    usleep(pauseTime * 1000);
297 	}
298 }
299 
300 static void
doDial(AuServer * aud,char * dialString,int volume,int pause,int spacing,int duration)301 doDial(AuServer *aud, char *dialString, int volume, int pause, int spacing,
302        int duration)
303 {
304     AuDeviceID      outputDevice = AuNone;
305     AuFlowID        flow;
306     int             i;
307 
308     /* make sure the server supports wave form elements */
309     for (i = 0; i < AuServerNumElementTypes(aud); i++)
310 	if (AuServerElementType(aud, i) == AuElementTypeImportWaveForm)
311 	    break;
312 
313     if (i == AuServerNumElementTypes(aud))
314 	fatalError("audio server does not support the wave form element type", NULL);
315 
316     /* make sure the server supports sine waves */
317     for (i = 0; i < AuServerNumWaveForms(aud); i++)
318 	if (AuServerWaveForm(aud, i) == AuWaveFormSine)
319 	    break;
320 
321     if (i == AuServerNumWaveForms(aud))
322 	fatalError("audio server does not support sine waves", NULL);
323 
324     /* look for an appropriate output device */
325     for (i = 0; i < AuServerNumDevices(aud); i++)
326 	if ((AuDeviceKind(AuServerDevice(aud, i)) ==
327 	     AuComponentKindPhysicalOutput) &&
328 	    AuDeviceNumTracks(AuServerDevice(aud, i)) == 1)
329 	{
330 	    outputDevice = AuDeviceIdentifier(AuServerDevice(aud, i));
331 	    break;
332 	}
333 
334     if (outputDevice == AuNone)
335 	fatalError("Couldn't find an appropriate output device", NULL);
336 
337     flow = createDTMFflow(aud, outputDevice, volume, duration);
338 
339     dial(aud, flow, dialString, pause, spacing);
340 }
341 
342 #define NBINS 		4
343 #define SLICE_MSECS 	50
344 #define THRESHHOLD 	3
345 
346 static void
recognize(unsigned char * p,int n)347 recognize(unsigned char *p, int n)
348 {
349     static int      freqs[8] = {697, 770, 852, 941, 1209, 1336, 1477, 1633},
350                     sums[8][NBINS],
351                     binphases[8],
352                     phases[8],
353                     hits[8];
354     static char     tone,
355                     last_tone = ' ';
356     int             i,
357                     j,
358                     count,
359                     avg_sum,
360                     threshhold;
361     unsigned int    c;
362 
363     if (!p)			/* initialize */
364     {
365 	for (i = 0; i < 8; i++)
366 	{
367 	    freqs[i] *= NBINS;
368 	    phases[i] = -freqs[i];
369 	    binphases[i] = 0;
370 	}
371 
372 	return;
373     }
374 
375     do
376     {
377 	for (i = 0; i < 8; i++)
378 	    for (j = 0; j < NBINS; j++)
379 		sums[i][j] = 0;
380 
381 	count = (SAMPLE_RATE * SLICE_MSECS) / 1000;
382 	while (count > 0 && n--)
383 	{
384 	    c = *p++;
385 	    count--;
386 	    for (i = 0; i < 8; i++)
387 	    {
388 		while (phases[i] < 0)
389 		{
390 		    phases[i] += 2 * SAMPLE_RATE;
391 		    sums[i][binphases[i]] += ((int) (c & 0xff)) - 0x80;
392 		    binphases[i]++;
393 		    if (binphases[i] == NBINS)
394 			binphases[i] = 0;
395 		}
396 		phases[i] -= 2 * freqs[i];
397 	    }
398 	}
399 
400 	avg_sum = 0;
401 	for (i = 0; i < 8; i++)
402 	    for (j = 0; j < NBINS; j++)
403 		avg_sum += abs(sums[i][j]);
404 	avg_sum /= 8 * NBINS;
405 	threshhold = THRESHHOLD * avg_sum;
406 
407 	for (i = 0; i < 8; i++)
408 	{
409 	    hits[i] = 0;
410 	    for (j = 0; j < NBINS; j++)
411 	    {
412 		if (abs(sums[i][j]) > threshhold)
413 		    hits[i] = 1;
414 	    }
415 	}
416 
417 	if (hits[0] && hits[4])
418 	    tone = '1';
419 	else if (hits[0] && hits[5])
420 	    tone = '2';
421 	else if (hits[0] && hits[6])
422 	    tone = '3';
423 	else if (hits[0] && hits[7])
424 	    tone = 'a';
425 	else if (hits[1] && hits[4])
426 	    tone = '4';
427 	else if (hits[1] && hits[5])
428 	    tone = '5';
429 	else if (hits[1] && hits[6])
430 	    tone = '6';
431 	else if (hits[1] && hits[7])
432 	    tone = 'b';
433 	else if (hits[2] && hits[4])
434 	    tone = '7';
435 	else if (hits[2] && hits[5])
436 	    tone = '8';
437 	else if (hits[2] && hits[6])
438 	    tone = '9';
439 	else if (hits[2] && hits[7])
440 	    tone = 'c';
441 	else if (hits[3] && hits[4])
442 	    tone = '*';
443 	else if (hits[3] && hits[5])
444 	    tone = '0';
445 	else if (hits[3] && hits[6])
446 	    tone = '#';
447 	else if (hits[3] && hits[7])
448 	    tone = 'd';
449 	else
450 	    tone = ' ';
451 
452 	if (tone != ' ' && tone != last_tone)
453 	{
454 	    putchar(tone);
455 	    fflush(stdout);
456 	}
457 
458 	last_tone = tone;
459     } while (n > 0);
460 }
461 
462 static void
doRecognize(AuServer * aud,AuBool mic,int gain,int time)463 doRecognize(AuServer *aud, AuBool mic, int gain, int time)
464 {
465     AuElementAction actions[1];
466     AuEvent         event;
467     AuDeviceID      inputDevice = AuNone;
468     AuDeviceAttributes *da;
469     AuFixedPoint    oldGain;
470     AuFlowID        flow;
471     AuElement       elements[2];
472     AuElementState  states[1];
473     AuInt32         oldMode;
474     int             i,
475                     mask = 0;
476     char           *buf;
477 
478     recognize(NULL, 0);		/* initialize */
479 
480     /* look for a one track input device */
481     for (i = 0; i < AuServerNumDevices(aud); i++)
482     {
483 	da = AuServerDevice(aud, i);
484 
485 	if (AuDeviceKind(da) == AuComponentKindPhysicalInput &&
486 	    AuDeviceNumTracks(da) == 1)
487 	{
488 	    inputDevice = AuDeviceIdentifier(da);
489 	    break;
490 	}
491     }
492 
493     if (inputDevice == AuNone)
494 	fatalError("Can't find an appropriate input device", NULL);
495 
496     if (!(buf = (char *) malloc(EXPORT_SIZE *
497 				AuSizeofFormat(AuFormatLinearUnsigned8))))
498 	fatalError("malloc error in doRecognize()", NULL);
499 
500     /* save old gain and line mode values */
501     da = AuGetDeviceAttributes(aud, inputDevice, NULL);
502     oldGain = AuDeviceGain(da);
503     oldMode = AuDeviceLineMode(da);
504 
505     /* set new gain and line mode values */
506     AuDeviceGain(da) = AuFixedPointFromSum(gain, 0);
507     AuDeviceLineMode(da) = mic ? AuDeviceLineModeHigh : AuDeviceLineModeLow;
508 
509     if (AuDeviceChangableMask(da) & AuCompDeviceLineModeMask)
510 	mask |= AuCompDeviceLineModeMask;
511 
512     if (AuDeviceChangableMask(da) & AuCompDeviceGainMask)
513 	mask |= AuCompDeviceGainMask;
514 
515     if (mask)
516 	AuSetDeviceAttributes(aud, inputDevice, mask, da, NULL);
517 
518     flow = AuCreateFlow(aud, NULL);
519 
520     AuMakeSendNotifyAction(&actions[0], AuStateStop, AuStateAny, AuReasonAny);
521 
522     AuMakeElementImportDevice(&elements[0], SAMPLE_RATE, inputDevice,
523 			      time ? SAMPLE_RATE * time : AuUnlimitedSamples,
524 			      1, actions);
525     AuMakeElementExportClient(&elements[1], 0, SAMPLE_RATE,
526 			    AuFormatLinearUnsigned8, 1, AuTrue, EXPORT_SIZE,
527 			      EXPORT_SIZE * HIGHWATER_MARK / 100, 0, NULL);
528 
529     /* set up the flow */
530     AuSetElements(aud, flow, AuTrue, 2, elements, NULL);
531 
532     /* start up the components */
533     AuMakeElementState(&states[0], flow, AuElementAll, AuStateStart);
534     AuSetElementStates(aud, 1, states, NULL);
535 
536     while (1)
537     {
538 	AuNextEvent(aud, AuTrue, &event);	/* dequeue the event */
539 
540 	if (event.type == AuEventTypeElementNotify)
541 	    if (event.auelementnotify.kind == AuElementNotifyKindHighWater ||
542 		(event.auelementnotify.kind == AuElementNotifyKindState &&
543 		 event.auelementnotify.cur_state == AuStatePause))
544 	    {
545 		AuUint32   n;
546 
547 		n = AuReadElement(aud, flow, 1, event.auelementnotify.num_bytes,
548 				  buf, NULL);
549 		recognize(buf, n);
550 	    }
551 	    else if (event.auelementnotify.kind == AuElementNotifyKindState &&
552 		     event.auelementnotify.cur_state == AuStateStop)
553 		break;
554     }
555 
556     free(buf);
557 
558     /* restore gain and line mode values */
559     AuDeviceGain(da) = oldGain;
560     AuDeviceLineMode(da) = oldMode;
561 
562     if (mask)
563 	AuSetDeviceAttributes(aud, inputDevice, mask, da, NULL);
564     AuFlush(aud);
565     AuFreeDeviceAttributes(aud, 1, da);
566 }
567 
568 int
main(int argc,char ** argv)569 main(int argc, char **argv)
570 {
571     int             i,
572                     time = 0,
573                     gain = DEFAULT_GAIN,
574                     pause = DEFAULT_PAUSE,
575                     spacing = DEFAULT_SPACE,
576                     duration = DEFAULT_DURATION,
577                     volume = DEFAULT_VOLUME;
578     char           *serverName = NULL,
579                    *dialString = NULL;
580     AuBool          usage = AuFalse,
581                     recognize = AuFalse,
582                     mic = AuFalse;
583     AuServer       *aud;
584 
585     for (i = 1; i < argc && !usage; i++)
586     {
587 	char           *arg = argv[i];
588 
589 	if (arg[0] == '-')
590 	{
591 	    switch (arg[1])
592 	    {
593 		case 'a':
594 		    if (++i >= argc)
595 			usage = AuTrue;
596 		    else
597 			serverName = argv[i];
598 		    continue;
599 		case 'p':
600 		    if (++i >= argc)
601 			usage = AuTrue;
602 		    else
603 			pause = atoi(argv[i]);
604 		    continue;
605 		case 's':
606 		    if (++i >= argc)
607 			usage = AuTrue;
608 		    else
609 			spacing = atoi(argv[i]);
610 		    continue;
611 		case 'v':
612 		    if (++i >= argc)
613 			usage = AuTrue;
614 		    else
615 			volume = atoi(argv[i]);
616 		    continue;
617 		case 'd':
618 		    if (++i >= argc)
619 			usage = AuTrue;
620 		    else
621 			duration = atoi(argv[i]);
622 		    continue;
623 		case 'g':
624 		    if (++i >= argc)
625 			usage = AuTrue;
626 		    else
627 			gain = atoi(argv[i]);
628 		    continue;
629 		case 't':
630 		    if (++i >= argc)
631 			usage = AuTrue;
632 		    else
633 			time = atoi(argv[i]);
634 		    continue;
635 		case 'r':
636 		    recognize = AuTrue;
637 		    continue;
638 		case 'm':
639 		    mic = AuTrue;
640 		    continue;
641 		case 'h':
642 		    usage = AuTrue;
643 	    }
644 	}
645 
646 	if (!dialString)
647 	{
648 	    dialString = arg;
649 	    continue;
650 	}
651     }
652 
653     if (usage || (!dialString && !recognize))
654 	fatalError(USAGE, NULL);
655 
656     /* open the audio server */
657     if (!(aud = AuOpenServer(serverName, 0, NULL, 0, NULL, NULL)))
658 	fatalError("Can't connect to audio server", NULL);
659 
660     if (recognize)
661 	doRecognize(aud, mic, gain, time);
662     else
663 	doDial(aud, dialString, volume, pause, spacing, duration);
664 
665     return 0;
666 }
667