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