/* beepHP.c -- seligman 5/92 */

/*
-- Implementation of beep.h for HP9000s700 with audio hardware.
--
-- If the environment variable SPEAKER begins with "E", the external
-- audio connection is used.  If it begins with "I", the internal speaker
-- is used.  Otherwise both are used.
--
-- Compile with the math library "-lm".
*/

#include "beep.h"
#include "alarm.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <fcntl.h>
#include <malloc.h>


static char *AudioDev;

static int DoBeep();


int BeepInit()
{
    char *speaker = getenv("SPEAKER");
    int fd;

    AudioDev =   speaker && toupper(speaker[0])=='E'  ?  "/dev/audioEL"
	       : speaker && toupper(speaker[0])=='I'  ?  "/dev/audioIL"
	       :					 "/dev/audioBL";

    /*
    -- A quick check to ensure that we can open the device.
    */
    if ((fd = open(AudioDev, O_WRONLY)) < 0) {
	perror("?? Error opening audio device");
	return 1;
    }
    close(fd);
    return 0;
}


/*
-- Optimized for the case where volume & pitch don't change (except possibly
-- to zero and back) between successive calls.
*/
int Beep(time, volume, pitch)
    int time, volume, pitch;
{
    int rc;

    AlarmWait();
    if (volume != 0  &&  pitch != 0)
	if ((rc = DoBeep(time, volume, pitch))  !=  0)
	    return rc;
    AlarmSet(time);
    return 0;
}


int BeepWait()
{
    AlarmWait();
    return 0;
}


int BeepCleanup()
{
    return 0;
}


int BeepResume()
{
    return 0;
}


/*----------------------------------------*/

#define SamplingRate 8000		/* audio device samples per second */
#define NumRamp (SamplingRate / 200)	/* # samples in 5ms ramp up/down */

typedef short int Sample;
#define MaxSample (Sample)0x7FFF


/*
-- Sound a beep.
-- Optimized for the case where volume & pitch don't change between successive
-- calls.
*/
static int DoBeep(time, volume, pitch)
    int time, volume, pitch;
{
    static int audioDevFD = -1;  /* -1 indicates not yet open. */

    /* Save sample buffer from call to call, and reuse it when possible. */
    static Sample *buf = NULL;
    static int bufLen    = 0;
    static int bufVolume = 0;
    static int bufPitch  = 0;

    Sample rampbuf[NumRamp];  /* Sample buffer for ramp-down. */

    /* Are volume and pitch same as previous call? */
    int sameVP  =  (volume == bufVolume  &&  pitch == bufPitch);

    int i, j;
    int numSamples = SamplingRate * time / 1000;

    /*
    -- It seems that the first write to the audio device after it's
    -- been opened must be the longest.
    */
    if (numSamples > bufLen) {
	if (audioDevFD >= 0) close(audioDevFD);
	audioDevFD = -1;
    }
    if (audioDevFD < 0) {
	if ((audioDevFD = open(AudioDev, O_WRONLY)) < 0) {
	    perror("?? Error opening audio device");
	    return 1;
	}
    }

    /*
    -- Allocate space for sample buffer.  Set "i" to lowest index in
    -- need of updating.
    */
    i = sameVP ? bufLen : 0;
    if (numSamples > bufLen) {
	if (sameVP) {
	    buf = realloc(buf, numSamples * sizeof(Sample));
	} else {
	    if (buf != NULL) free(buf);
	    bufVolume = volume;
	    bufPitch  = pitch;
	    buf = malloc(numSamples * sizeof(Sample));
	}
	if (buf == NULL) {
	    fprintf(stderr, "?? Buy more memory.\n");
	    return 1;
	}
	bufLen = numSamples;
    }

    /*
    -- Update any newly-allocated tail of sample buffer.  The ramp-up is
    -- handled here (since it always occurs at the same place); the ramp-down
    -- is handled later.
    */
    while (i < bufLen) {
	double rampScale  =  (i < NumRamp)  ?  (double) i / NumRamp  :  1;
	double t = (double) i / SamplingRate;
	buf[i++] =
	    sin(2 * M_PI * pitch * t) * MaxSample * rampScale * volume / 100;
    }

    /* Sound the beep, except for the ramp-down. */
    write(audioDevFD, buf, (numSamples - NumRamp) * sizeof(Sample));

    /* Ramp-down */
    for (i = 0, j = numSamples-NumRamp;  i < NumRamp;  i++, j++)
	rampbuf[i] = buf[j] * ((double) (NumRamp - i) / NumRamp);
    write(audioDevFD, rampbuf, sizeof rampbuf);

    return 0;
}
