home *** CD-ROM | disk | FTP | other *** search
- (c) Copyright 1989-1999 Amiga, Inc. All rights reserved.
- The information contained herein is subject to change without notice, and
- is provided "as is" without warranty of any kind, either expressed or implied.
- The entire risk as to the use of this information is assumed by the user.
-
-
-
-
- DTMF - A Program for Generating
- Touch-Tone Signals
-
- by Adam Levin
-
-
- Although the use of touch-tones as a method of dialing the telephone
- is an international standard, not everyone has a telephone capable of
- generating touch-tone signals. The program presented here will allow
- you to create touch-tone signals with your Amiga's audio hardware which
- you can amplify and use to dial your telephone.
-
- With the DTMF program you can enter alternate-carrier account numbers,
- access voice-mail systems, dial alphabetic "numbers" and retrieve messages
- from your telephone answering machine (if it uses touch-tones). In addition
- DTMF can generate the four tones 12-key keypads cannot: 'A', 'B', 'C' and 'D'.
-
-
- DTMF stands for Dual Tone Multi-Frequency which is the method of using
- pairs of tones from a palette of eight frequencies to generate sixteen unique,
- machine-recognizable signals. For each touch-tone key that you press, two
- tones are generated, mixed together and sent down the wires to your phone
- company where they are interpreted as the key you have pressed.
-
- Since the phone company's decoding machine is "listening" to your line
- while you are dialing, any audible source of touch-tones can be used to dial:
- an audio recording, a well-trained voice, or, as in this case, the Amiga.
- Pairs of tones are used, rather than single tones, so that outside noises
- are less likely to be interpreted as key presses.
-
- To use DTMF to dial, you will need to be able to hear the audio output
- of your Amiga. The built-in speaker in your monitor is fine for this.
- You may also connect the Amiga's audio output to amplified speakers such as
- the Panasonic RX-C36, Sony APM-007AV or Bose Roommates.
-
- Set the volume to a comfortable listening level and hold the telephone's
- mouthpiece about 1-10 inches from the speaker. Experiment with the volume
- level and handset distance to get consistent results.
-
-
- DTMF runs only from the CLI, and has the following syntax:
-
- DTMF NUMBER "(800) 555-1212" SIDE left|right|either SPEED slow|norm|fast
-
- To enter a phone number from the command line, type the keyword
- NUMBER followed by the number you want to dial. If the phone number has an
- imbedded blank, it must be surrounded by quotes.
-
- The SIDE option allows you to specify which audio jack will be used
- for DTMF's output. If you don't specify which side, DTMF will try to use
- the left side. If the left side is in use, DTMF will try to use the right
- side. You may omit the SIDE keyword, and simply type left, right or either.
-
- The SPEED option allows you to specify how quickly the number will
- be dialed. A SPEED of "fast" will dial as quickly as the Dual Tone Multi-
- Frequency standard allows. A SPEED of "norm" will dial at one-half full
- speed and "slow" will dial at one-third full speed. You may omit the
- SPEED keyword and type just fast, norm or slow.
-
- If you don't enter a number on the command line, DTMF will take its
- input from the standard input device. This means that you can start up DTMF
- and enter more than one phone-number pressing the RETURN key when you want
- the number dialed. When you are done, just type control-backslash to quit:
-
- 1> DTMF
- (800) 555-1212
- 555-1212
- Ctrl-\
- 1>
-
- Because DTMF uses standard input when no number is specified on the command
- line, you can send a file containing phone numbers to DTMF with the
- redirection symbol "<". For example, if you have a file called Info which
- contains:
-
- 1 (800) 555-1212
-
- and you type "DTMF <Info SIDE right SPEED norm", DTMF will dial the number
- in Info.
-
- DTMF will recognize the following symbols in a phone number:
-
- o The usual touch-tone characters: 0-9, * and #.
-
- o The four other characters in the DTMF standard: A, B, C and D.
-
- o The alphabetic characters found on most touch-tone keypads,
- A-P and R-Y. Letters Q and Z do not appear. This means that
- you can dial those easy-to-remember but hard-to-dial mnemonic phone
- numbers many companies use. Use lower case when dialing alphabetic
- numbers to avoid confusion with the 4 extra DTMF tones A-D.
-
- o A comma will make a two-second pause in the dialing sequence.
-
- All other characters are ignored.
-
- Each character which generates touch-tones is converted to audio.device
- period and cycle values by look-up tables. These values are then put into
- two IOAudio request blocks - one for the lower of the two tones, one for
- the higher. Since the Amiga has two audio channels per side the low and
- high tones can be played on separate channels and the hardware will combine
- them into one audio output.
-
- The calculations performed to obtain the period and cycle values
- are dependent upon the Amiga's clock and are therefore different for
- NTSC and PAL machines. DTMF supports both versions. Be sure and include
- the line "#define NTSC" in the source code for NTSC machines and
- "#undef NTSC" for PAL machines.
-
-
-
-
- /* DTMF - DualToneMulti-Frequency tone generator.
-
- v1.2 Adam Levin Commodore-Amiga Technical Support
-
- Written with Manx's Aztec C v3.6a
- compile with: cc DTMF.c +L
- link with: ln DTMF.o -lm32 -lc32
- Note that the math library is required.
- */
-
- #include <exec/types.h>
- #include <exec/memory.h>
- #include <devices/audio.h>
- #include <libraries/dos.h>
- #include <hardware/dmabits.h>
- #include <graphics/gfxbase.h>
- #include <intuition/intuition.h>
- #include <stdio.h>
-
- #define NUM_PAIRS 16 /* Number of pairs of periods. */
-
-
- #define DTMF_PRI 90 /* Priority of the DTMF sounds. */
- #define NUM_LEN 63 /* Maximum number of chars in CLI "NUMBER". */
-
- #define SQUARE_WAVE_LEN 8L /* Length of audio sample. */
- #define LO_TONES DMAF_AUD0|DMAF_AUD1 /* Audio channel masks. */
- #define HI_TONES DMAF_AUD2|DMAF_AUD3
- #define LEFT_SIDE DMAF_AUD0|DMAF_AUD3
- #define RIGHT_SIDE DMAF_AUD1|DMAF_AUD2
- #define ERROR_CANNOT_CREATE_PORT ERROR_BAD_STREAM_NAME
- #define SLOW_SPEED 3 /* Scaling factors for tone duration. */
- #define NORM_SPEED 2
- #define FAST_SPEED 1
-
- struct IntuitionBase *IntuitionBase;
- struct GfxBase *GfxBase;
-
- BYTE *square_wave; /* Audio sample. */
- UBYTE side[] = {LEFT_SIDE, RIGHT_SIDE}; /* Audio channel choices. */
- char number[NUM_LEN+1]; /* CLI "NUMBER" argument. */
- int speed = NORM_SPEED, side_cnt = sizeof(side); /* Scaling, # unit choices. */
- struct Remember *remember; /* AllocRemember anchor. */
- /* Audio request blocks. One for control, one each for low and high tones. */
- struct IOAudio *cmd_ioA, *lo_ioA, *hi_ioA;
-
-
- /*
- Valid keypad symbols.
- '0'-'9', '*' and '#' are standard.
- The keys 'A', 'B', 'C' and 'D' appear on 16-key keypads.
- */
-
- char keypad[] = "0123456789*#ABCD";
-
-
- /*
- Tone periods.
- There is a pair of tones for each of the 16 keys on a DTMF keypad.
- Each tone has been converted to an audio.device period with
- the formula:
-
- 1,000,000 / SQUARE_WAVE_LEN / (1 / CLOCK) / freq
-
- where SQUARE_WAVE_LEN is the length of the square wave sample,
- CLOCK is the clock constant (NTSC = 3.579545, PAL = 3.546895), and
- freq is the frequency of the tone.
-
- The order of the following pairs (0, 1, 2...) must match
- the order of the symbols in the keypad string.
- */
-
- /* N T S C P E R I O D S */
- USHORT periods[][2] =
- {
- { 475, 335 }, /* 0 */
- { 642, 370 }, /* 1 */
- { 642, 335 }, /* 2 */
- { 642, 303 }, /* 3 */
- { 581, 370 }, /* 4 */
- { 581, 335 }, /* 5 */
- { 581, 303 }, /* 6 */
- { 525, 370 }, /* 7 */
- { 525, 335 }, /* 8 */
- { 525, 303 }, /* 9 */
- { 475, 370 }, /* * */
- { 475, 303 }, /* # */
- { 642, 274 }, /* A */
- { 581, 274 }, /* B */
- { 525, 274 }, /* C */
- { 475, 274 }, /* D */
- /* P A L P E R I O D S */
- { 471, 332 }, /* 0 */
- { 636, 367 }, /* 1 */
- { 636, 332 }, /* 2 */
- { 636, 300 }, /* 3 */
- { 576, 367 }, /* 4 */
- { 576, 332 }, /* 5 */
- { 576, 300 }, /* 6 */
- { 520, 367 }, /* 7 */
- { 520, 332 }, /* 8 */
- { 520, 300 }, /* 9 */
- { 471, 367 }, /* * */
- { 471, 300 }, /* # */
- { 636, 271 }, /* A */
- { 576, 271 }, /* B */
- { 520, 271 }, /* C */
- { 471, 271 } /* D */
- };
-
-
- /*
- Tone cycles.
- Each tone pair must sound for at least 40 mS, according to DTMF
- specifications. A scaling factor (SPEED) is used to increase
- this duration, when needed. The formulas used were:
-
- lo_cycles = ceil((mS_DELAY / 1000.) / ( 1. / hi_freq))
- hi_cycles = ceil(((float)hi_freq / (float)lo_freq) * (float)lo_cycles)
-
- where mS_DELAY was 40, and lo_freq and hi_freq were the frequencies
- specified for the given tone pair.
- */
-
- USHORT cycles[][2] =
- {
- { 53, 75 }, /* 0 */
- { 48, 83 }, /* 1 */
- { 53, 102 }, /* 2 */
- { 59, 125 }, /* 3 */
- { 48, 75 }, /* 4 */
- { 53, 92 }, /* 5 */
- { 59, 113 }, /* 6 */
- { 48, 68 }, /* 7 */
- { 53, 83 }, /* 8 */
- { 59, 102 }, /* 9 */
- { 48, 62 }, /* * */
- { 59, 93 }, /* # */
- { 65, 152 }, /* A */
- { 65, 138 }, /* B */
- { 65, 125 }, /* C */
- { 65, 113 } /* D */
- };
-
-
- /*
- T O L O W E R
- Converts upper case letters to their lower case counterparts.
- Properly converts international ASCII characters.
- This is a 'C' version of Bob "Kodiak" Burns' deCase assembler code.
- */
-
- char
- tolower(x)
- char x;
- {
- UBYTE u;
- u = x;
- if ((u >= 0x41 && u <= 0x5a) ||
- (u >= 0xc0 && u <= 0xd6) ||
- (u >= 0xd8 && u <= 0xde)) x |= 0x20;
-
- return(x);
-
- } /* end tolower() */
-
-
- /*
- S T R C M P I
- String compare which ignores case.
- */
-
- int
- strcmpi(s1, s2)
- char s1[], s2[];
- {
- int i = 0;
-
- while ( tolower(s1[i]) == tolower(s2[i]) )
- if ( s1[i++] == '\0' ) return(0);
- return((int) (s1[i] - s2[i]) );
-
- } /* end strcmpi() */
-
-
- /*
- I S I N
- Searches for a word in a list of words.
- */
-
- int
- isin(word, words, numwords)
- char *word, *words[];
- int numwords;
- {
- int i;
- for (i=0; i<numwords; i++)
- if (strcmpi(word, words[i]) == 0) return(i);
- return(-1);
- } /* end isin() */
-
-
- /*
- A R G S
- Parse the CLI argument list.
- */
-
- #define NUMBER_I 0
- #define SIDE_I 1
- #define LEFT_I 2
- #define RIGHT_I 3
- #define EITHER_I 4
- #define SPEED_I 5
- #define FAST_I 6
- #define NORM_I 7
- #define SLOW_I 8
-
- #define NUM_FLAGS 9
-
- BOOL
- args(argc, argv)
- int argc;
- char *argv[];
- {
- char *pointer, side_chr[6], speed_chr[5];
- static char *flags[NUM_FLAGS] =
- { "NUMBER","SIDE","LEFT","RIGHT","EITHER","SPEED","FAST","NORM","SLOW" };
- int i, index;
-
- if (argc > 1 && argv[argc-1][0] == '?')
- {
- (void)fprintf(stderr, "Usage:\n%s NUMBER \"555-1212\" SIDE left|right|either SPEED slow|norm|fast\n", argv[0]);
- return(FALSE);
- }
-
- for (i=1; i<argc; i++)
- {
- if ((index = isin(argv[i], flags, NUM_FLAGS)) >= 0)
- {
- pointer = NULL;
- switch (index)
- {
- /* Option flag: copy pointer into variable "pointer". */
- case (NUMBER_I):
- pointer = number;
- break;
- case (SIDE_I):
- pointer = side_chr;
- break;
- /* Argument given without flag; take it directly. */
- case (LEFT_I):
- case (RIGHT_I):
- case (EITHER_I):
- (void)strcpy(side_chr, flags[index]);
- break;
- case (SPEED_I):
- pointer = speed_chr;
- break;
- case (FAST_I):
- case (NORM_I):
- case (SLOW_I):
- (void)strcpy(speed_chr, flags[index]);
- break;
- default:
- break;
- }
- /* If this is an option flag, copy the following argument into
- the string (which is now pointed to by "pointer").
- */
- if (pointer != NULL)
- {
- if (i < argc-1)
- (void)strcpy(pointer, argv[++i]);
- else
- {
- (void)fprintf(stderr, "%s: \"%s\" requires an argument.\n", argv[0], argv[i]);
- return(FALSE);
- }
- }
- }
- else
- {
- (void)fprintf(stderr, "%s: Unknown option \"%s\".\n", argv[0], argv[i]);
- return(FALSE);
- }
- }
-
- if (strlen(side_chr))
- {
- if (strcmpi(side_chr, "LEFT") == 0)
- {
- side[0] = LEFT_SIDE;
- side_cnt = 1;
- }
- else if (strcmpi(side_chr, "RIGHT") == 0)
- {
- side[0] = RIGHT_SIDE;
- side_cnt = 1;
- }
- else if (strcmpi(side_chr, "EITHER") == 0); /* Use default. */
- else
- {
- (void)fprintf(stderr, "%s: Unknown SIDE argument \"%s\".\n", argv[0], side_chr);
- return(FALSE);
- }
- }
-
- if (strlen(speed_chr))
- {
- if (strcmpi(speed_chr, "FAST") == 0) speed = FAST_SPEED;
- else if (strcmpi(speed_chr, "NORM") == 0); /* Use default. */
- else if (strcmpi(speed_chr, "SLOW") == 0) speed = SLOW_SPEED;
- else
- {
- (void)fprintf(stderr, "%s: Unknown SPEED argument \"%s\".\n", argv[0], speed_chr);
- return(FALSE);
- }
- }
-
- return(TRUE);
-
- } /* end args() */
-
-
- /*
- S E T U P
- Opens libraries & devices, allocates memory, initializes structures.
- */
-
- int
- setup()
- {
- if ((IntuitionBase = (struct IntuitionBase *)
- OpenLibrary("intuition.library", 0L)) == NULL)
- return(ERROR_INVALID_RESIDENT_LIBRARY);
-
- if ((GfxBase = (struct GfxBase *)
- OpenLibrary("graphics.library", 0L)) == NULL)
- return(ERROR_INVALID_RESIDENT_LIBRARY);
-
- remember = NULL;
-
- if ((cmd_ioA = (struct IOAudio *)
- AllocRemember(&remember, (long)sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR))==NULL)
- return(ERROR_NO_FREE_STORE);
-
- if ((lo_ioA = (struct IOAudio *)
- AllocRemember(&remember, (long)sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR))==NULL)
- return(ERROR_NO_FREE_STORE);
-
- if ((hi_ioA = (struct IOAudio *)
- AllocRemember(&remember, (long)sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR))==NULL)
- return(ERROR_NO_FREE_STORE);
-
- if ((square_wave = (BYTE *)
- AllocRemember(&remember, SQUARE_WAVE_LEN, MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR))==NULL)
- return(ERROR_NO_FREE_STORE);
-
- if ((cmd_ioA->ioa_Request.io_Message.mn_ReplyPort = (struct MsgPort *)
- CreatePort("DTMF", 0L)) == NULL)
- return(ERROR_CANNOT_CREATE_PORT);
-
- if (OpenDevice(AUDIONAME, 0L, cmd_ioA, 0L)!=NULL)
- return(ERROR_OBJECT_IN_USE);
-
- /* Initialize cmd audio request block. */
- cmd_ioA->ioa_Request.io_Message.mn_Node.ln_Pri = DTMF_PRI;
- cmd_ioA->ioa_Request.io_Command = ADCMD_ALLOCATE;
- cmd_ioA->ioa_Request.io_Flags = ADIOF_NOWAIT;
- cmd_ioA->ioa_Data = side;
- cmd_ioA->ioa_Length = side_cnt;
-
- /* Must use immediately; return if requested channel(s) in use. */
- BeginIO(cmd_ioA);
- if (WaitIO(cmd_ioA))
- return(ERROR_OBJECT_IN_USE);
-
- /* Copy cmd values to lo request block; make needed changes. */
- *lo_ioA = *cmd_ioA;
- lo_ioA->ioa_Request.io_Command = CMD_WRITE;
- lo_ioA->ioa_Request.io_Unit =
- (ULONG)cmd_ioA->ioa_Request.io_Unit & LO_TONES;
- lo_ioA->ioa_Volume = 64;
- lo_ioA->ioa_Data = (UBYTE *)square_wave;
- lo_ioA->ioa_Length = SQUARE_WAVE_LEN;
-
- /* Copy lo values to hi request block; make needed changes. */
- *hi_ioA = *lo_ioA;
- hi_ioA->ioa_Request.io_Unit =
- (ULONG)cmd_ioA->ioa_Request.io_Unit & HI_TONES;
-
- /*
- Construct a square wave. Having it begin and end at the same
- level gives a smooth transition when cycling. Having it begin
- and end at zero avoids "POP"s when starting and stopping.
-
- __ | _ 127 L
- | | | E
- _| |__ _ | _ 0 V
- | | | E
- |__| | _ -127 L
- ______________|
-
- 0 12 34 56 7
-
- SQUARE_WAVE[x]
- */
-
- square_wave[1] = square_wave[2] = 127;
- square_wave[5] = square_wave[6] = -127;
-
- return(RETURN_OK);
-
- } /* end setup() */
-
-
- /*
- K E Y C O N V
- Given a valid DTMF key, returns the low & high periods and cycles.
- */
-
- BOOL
- keyConv(key, lo_period, hi_period, lo_cycles, hi_cycles)
- int key, *lo_period, *hi_period, *lo_cycles, *hi_cycles;
- {
- char *button;
- int index = -1;
-
- if (key != '\0')
- {
- /* If key is alphabetic, convert into corresponding numeral. */
- if (key >= 'a' && key < 'z' && key != 'q')
- {
- /* Fold keys greater than 'q' down 1 slot to fill q's gap. */
- if (key > 'q') --key;
- /* Slide to the range of 1 to 24. */
- key = key - ('a' - 1);
- /*
- Map groups of three onto the digits 2 to 9.
-
- a b c d e f w x y
-
- 1 2 3 4 5 6 ... 22 23 24
- \ | / \ | / \ | /
- 2 3 ... 9
- */
- index = 1 + (int)(key/3. + .9);
- }
- else
- {
- /* If key is one of the characters in the keypad string... */
- if (button = strchr(keypad, (char)key))
- {
- /* ...calculate index by the difference in pointers. */
- index = (int)(button-keypad);
- }
- }
- /* If key was valid... */
- if (index >= 0)
- {
- /* ...index into matrix for the periods and cycles. */
-
- *lo_cycles = cycles[index][0] * speed;
- *hi_cycles = cycles[index][1] * speed;
-
- /* If running on a PAL Amiga, offset the index by NUM_PAIRS. */
- if (GfxBase->DisplayFlags & PAL) index += NUM_PAIRS;
-
- *lo_period = periods[index][0];
- *hi_period = periods[index][1];
-
- return(TRUE);
- }
- }
- return (FALSE);
- } /* end keyConv() */
-
-
- /*
- T O N E S
- Generates the tones, one key at a time.
- */
-
- void
- tones()
- {
- int key;
- int lo_period, hi_period, lo_cycles, hi_cycles;
- int i=0, s;
-
- s = strlen(number);
-
- do
- {
- /* If CLI "NUMBER", get next char. */
- if (s)
- key = ( i < s ) ? number[i++] : EOF;
- /* Otherwise, use stdin. */
- else
- key = getchar();
-
- if (keyConv(key, &lo_period, &hi_period, &lo_cycles, &hi_cycles))
- {
- lo_ioA->ioa_Period = lo_period;
- lo_ioA->ioa_Cycles = lo_cycles;
-
- hi_ioA->ioa_Period = hi_period;
- hi_ioA->ioa_Cycles = hi_cycles;
-
- lo_ioA->ioa_Request.io_Flags =
- hi_ioA->ioa_Request.io_Flags = ADIOF_PERVOL;
-
- BeginIO(lo_ioA);
- BeginIO(hi_ioA);
- (void) WaitIO(lo_ioA);
- (void) WaitIO(hi_ioA);
-
- /* Insure there is a delay between successive tones. */
- Delay(2L * speed);
- }
- /* A comma in the input delays for two seconds. */
- else if (key == ',') Delay(2L * TICKS_PER_SECOND);
-
- } while (key != EOF);
- } /* end tones() */
-
-
- /*
- E R R O R M S G
- Prints an error message to stdout.
- */
-
- void
- errormsg(errval, progname)
- int errval;
- char *progname;
- {
- switch(errval)
- {
- case (ERROR_INVALID_RESIDENT_LIBRARY):
- (void)fprintf(stderr, "%s: Could not open one of either intuition or graphics libraries.\n", progname);
- break;
- case (ERROR_NO_FREE_STORE):
- (void)fprintf(stderr, "%s: Not enough memory.\n", progname);
- break;
- case (ERROR_CANNOT_CREATE_PORT):
- (void)fprintf(stderr, "%s: Could not create port.\n", progname);
- break;
- case (ERROR_OBJECT_IN_USE):
- (void)fprintf(stderr, "%s: Object in use.\n", progname);
- break;
- default:
- (void)fprintf(stderr, "%s: Unknown error.\n", progname);
- break;
- }
- }
-
-
- /*
- S H U T D O W N
- Clean up.
- */
-
- void
- shutdown()
- {
- if (cmd_ioA->ioa_Request.io_Device)
- CloseDevice(cmd_ioA);
-
- if (cmd_ioA->ioa_Request.io_Message.mn_ReplyPort)
- DeletePort(cmd_ioA->ioa_Request.io_Message.mn_ReplyPort);
-
- FreeRemember(&remember, TRUE);
-
- if (GfxBase) CloseLibrary(GfxBase);
-
- if (IntuitionBase) CloseLibrary(IntuitionBase);
-
- } /* end shutdown() */
-
-
- /*
- M A I N
- Controlling routine.
- */
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int errval = RETURN_OK;
-
- if (args(argc, argv))
- {
- errval = setup();
- if (errval == RETURN_OK)
- tones();
- else
- {
- errormsg(errval, argv[0]);
- errval = (RETURN_FAIL);
- }
- shutdown();
- exit(errval);
- }
- else
- /* Usage errors. */
- exit(RETURN_WARN);
- } /* end main() */
-
-
-
-