/*

	NetFone: Network sound transmission program

	Designed and implemented in July of 1991 by John Walker

*/

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

struct soundbuf {
	int compression;
	char sendinghost[16];
	struct {
		int buffer_len;
		char buffer_val[8000];
	} buffer;
};
typedef struct soundbuf soundbuf;

#define TRUE	1
#define FALSE	0

static int sock;		      /* Communication socket */

#define V	(void)

/*  Destination host descriptor.  */

struct destination {
    struct destination *dnext;
    char *server;
    struct sockaddr_in name;
};

static struct destination *dests = NULL, *dtail;
static int compressing = FALSE;       /* Compress sound buffers */
static int squelch = 0; 	      /* Squelch level if > 0 */
static int ring = FALSE;	      /* Force speaker & level on next pkt ? */
static int agc = TRUE;		      /* Automatic gain control active ? */
static int rgain = 33;		      /* Current recording gain level */
static int debugging = FALSE;	      /* Debugging enabled here and there ? */
static char hostname[20];	      /* Host name to send with packets */
static int loopback = FALSE;	      /* Remote loopback mode */

/*  ADDEST  --	Add destination host to host list.  */

static int addest(host)
  char *host;
{
    struct destination *d;
    struct hostent *hp, *gethostbyname();

    hp = gethostbyname(host);
    if (hp == 0) {
        fprintf(stderr, "%s: unknown host\n", host);
	return FALSE;
    }

    d = (struct destination *) malloc(sizeof(struct destination));
    d->dnext = NULL;
    d->server = host;
    bcopy((char *) hp->h_addr, (char *) &(d->name.sin_addr), hp->h_length);
    d->name.sin_family = AF_INET;
    d->name.sin_port = htons(Internet_Port);
    if (dests == NULL) {
	dests = d;
    } else {
	dtail->dnext = d;
    }
    dtail = d;
    return TRUE;
}

/*  SENDMSG  --  Send a message to all active destinations.  */

static int sendmsg(sb)
  struct soundbuf *sb;
{
    struct destination *d;

    for (d = dests; d != NULL; d = d->dnext) {
    if (sendto(sock, sb, (sizeof(struct soundbuf)) - (8000 - sb->buffer.buffer_len),
	0, (struct sockaddr *) &(d->name), sizeof d->name) < 0) {
        perror("sending datagram message");
	return FALSE;
    }
    }
    return TRUE;
}

/*  SENDFILE  --  Send a file or, if the file name is NULL or a
		  single period, send real-time sound input. */

static int sendfile(f)
  char *f;
{ 
    soundbuf netbuf;
#define buf netbuf.buffer.buffer_val
    int nread;
    FILE *afile = NULL;

    strcpy(netbuf.sendinghost, hostname);
    if (f != NULL && (strcmp(f, ".") != 0)) {
        afile = fopen(f, "r");
	if (afile == NULL) {
            fprintf(stderr, "Unable to open sound file %s.\n", f);
	    return 2;
	}
    }

    /* Send a file */

    if (afile) {
	while ((nread = fread(buf, 1, sizeof buf, afile)) > 0) {
	    netbuf.compression = FALSE | (ring ? 4 : 0);
	    ring = FALSE;
	    netbuf.compression |= debugging ? 2 : 0;
	    netbuf.compression |= loopback ? 16 : 0;
	    if (compressing) {
		int i;

		nread /= 2;
		for (i = 1; i < nread; i++) {
		    buf[i] = buf[i * 2];
		}
		netbuf.compression |= 1;
	    }
	    netbuf.buffer.buffer_len = nread;
	    if (!sendmsg(&netbuf)) {
		fclose(afile);
		return 1;
	    }
	    /* Horrible kludge.  Fake flow control by sleeping for about
	       long enough for the buffer to play. */

	    usleep(950 * ((nread * (compressing ? 2 : 1)) / 8L));
	}

	if (debugging) {
            fprintf(stderr, "Sent sound file %s.\n", f);
	}
	fclose(afile);
    } else {

	/* Send real-time sound. */

	if (!soundinit(O_RDONLY /* | O_NDELAY */ )) {
            fprintf(stderr, "Unable to initialise audio.\n");
	    return 2;
	}
	if (agc) {
	    soundrecgain(rgain);      /* Set initial record level */
	}
	if (soundrecord()) {
	    while (TRUE) {
		int soundel = soundgrab(buf, sizeof buf);
		unsigned char *bs = (unsigned char *) buf;

		if (soundel > 0) {
		    register unsigned char *start = bs;
		    register int j;
		    int squelched = (squelch > 0);

		    /* If entire buffer is less than squelch, ditch it. */

		    if (squelch > 0) {
			for (j = 0; j < soundel; j++) {
			    if (((*start++ & 0x7F) ^ 0x7F) > squelch) {
				squelched = FALSE;
				break;
			    }
			}
		    }

		    if (squelched) {
			if (debugging) {
                            printf("Entire buffer squelched.\n");
			}
		    } else {
			netbuf.compression = FALSE | (ring ? 4 : 0);
			netbuf.compression |= debugging ? 2 : 0;
			netbuf.compression |= loopback ? 16 : 0;

			/* If automatic gain control is enabled,
			   ride the gain pot to fill the dynamic range
			   optimally. */

			if (agc) {
			    register unsigned char *start = bs;
			    register int j;
			    long msamp = 0;

			    for (j = 0; j < soundel; j++) {
				int tsamp = ((*start++ & 0x7F) ^ 0x7F);

				msamp += tsamp;
			    }
			    msamp /= soundel;
			    if (msamp < 0x30) {
				if (rgain < 100) {
				    soundrecgain(++rgain);
				}
			    } else if (msamp > 0x35) {
				if (rgain > 1) {
				    soundrecgain(--rgain);
				}
			    }
			}

			ring = FALSE;
			if (compressing) {
			    int i;

			    soundel /= 2;
			    for (i = 1; i < soundel; i++) {
				buf[i] = buf[i * 2];
			    }
			    netbuf.compression |= 1;
			}
			netbuf.buffer.buffer_len = soundel;
			if (!sendmsg(&netbuf)) {
			    return 1;
			}
		    }
		} else {
		    usleep(100000L);  /* Wait for some sound to arrive */
		}
	    }
	} else {
            fprintf(stderr, "Unable to start recording.\n");
	    return 2;
	}
    }
    return 0;
}

/*  USAGE  --  Print how-to-call information.  */

static void usage()
{
    V fprintf(stderr, "mike  --  Sound transmission tool.\n");
    V fprintf(stderr, "\n");
    V fprintf(stderr, "Usage: mike hostname <options> [ file1 / . ]...\n");
    V fprintf(stderr, "Options: (* indicates defaults)\n");
    V fprintf(stderr, "           -C         Compress subsequent sound\n");
    V fprintf(stderr, "           -D         Enable debug output\n");
    V fprintf(stderr, "     *     -G         Automatic gain control\n");
    V fprintf(stderr, "           -L         Remote loopback\n");
    V fprintf(stderr, "           -M         Manual record gain control\n");
    V fprintf(stderr, "     *     -N         Do not compress subsequent sound\n");
    V fprintf(stderr, "           -Phostname Party line, add host to list\n");
    V fprintf(stderr, "     *     -Q         Disable debug output\n");
    V fprintf(stderr, "           -R         Ring--force volume, output to speaker\n");
    V fprintf(stderr, "           -Sn        Squelch at level n (0-255)\n");
    V fprintf(stderr, "           -U         Print this message\n");
    V fprintf(stderr, "\n");
    V fprintf(stderr, "par John Walker\n");
    V fprintf(stderr, "    Autodesk SA Neuch\342tel\n");
    V fprintf(stderr, "    Avenue des Champs-Montants 14b\n");
    V fprintf(stderr, "    CH-2074 MARIN\n");
    V fprintf(stderr, "    Suisse/Schweiz/Svizzera/Svizra/Switzerland\n");
    V fprintf(stderr, "    Usenet: kelvin@Autodesk.com\n");
    V fprintf(stderr, "    Fax:    038/33 88 15\n");
    V fprintf(stderr, "    Voice:  038/33 76 33\n");
}

/*  Main program.  */

main(argc, argv)
  int argc;
  char *argv[];
{
    int i, sentfile = 0;

    gethostname(hostname, sizeof hostname);

    /* Create the socket used to send data. */

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("opening datagram socket");
	return 1;
    }

    /*	Process command line options.  */

    for (i = 1; i < argc; i++) {
	char *op, opt;

	op = argv[i];
        if (*op == '-') {
	    opt = *(++op);
	    if (islower(opt))
		opt = toupper(opt);
	    switch (opt) {

                case 'C':             /* -C  -- Compress sound samples */
		    compressing = TRUE;
		    break;

                case 'D':             /* -D  --  Enable debug output  */
		    debugging = TRUE;
		    break;

                case 'G':             /* -G  --  Automatic gain control */
		    agc = TRUE;
		    break;

                case 'L':             /* -L  --  Remote loopback */
		    loopback = TRUE;
		    break;

                case 'M':             /* -M  --  Manual record gain control */
		    agc = FALSE;
		    break;

                case 'N':             /* -N  --  Do not compress sound samples */
		    compressing = FALSE;
		    break;

                case 'P':             /* -Phost  --  Copy output to host  */
		    if (!addest(op + 1)) {
			return 1;
		    }
		    break;

                case 'Q':             /* -Q  --  Disable debug output  */
		    debugging = FALSE;
		    break;

                case 'R':             /* -R  --  Ring: divert output to speaker */
		    ring = TRUE;
		    break;

                case 'S':             /* -Sn  --  Squelch at level n */
		    if (strlen(op + 1) == 0) {
			squelch = 50; /* Default squelch */
		    } else {
			squelch = atoi(op + 1);
		    }
		    break;

                case 'U':             /* -U  --  Print usage information */
                case '?':             /* -?  --  Print usage information */
		    usage();
		    return 0;
	    }
	} else {
	    if (dests == NULL) {
		if (!addest(op)) {
		    return 1;
		}
	    } else {
		int ok = sendfile(op);
		if (ok != 0)
		    return ok;
		sentfile++;
	    }
	}
    }

    if (dests == NULL) {
	usage();
    } else {
	if (sentfile == 0) {
	    return sendfile(NULL);
	}
    }

    return 0;
}
