(c)  Copyright 1989 Commodore-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() */



