[net.micro.amiga] audible beep

sam@amiga.UUCP (Samuel C. Dicker) (02/25/86)

Last week I attended a developers meeting (BADGE). When I told them that I am
Commodore-Amiga's resident sound guru, I was immediately bombarded with
requests for a simple 'Beep' sound.  Many, of the developers wanted an
alternative to a spoken "BEEP".  One developer asked me if it was possible
to produce a beep with less then 8K of code and waveform data!!  When I ask if
they would be at all interested in a simple beep sound, like a cheap terminal:
They're overwhelming responded was "YES!!!"

This is the source for a beep sound that I wrote for a VT100 terminal emulator
The module itself generates less then 800 bytes in Lattice C.  Because some
of this code is a child task (rule also applies to interrupt code), you must
turn off stack checking when you compile ('LC2 -v <file> ...').

Please send me your comments and suggestions.

-------------------------------- cut here ------------------------------------
/*****************************************************************************
 *
 * VT100-like Beep Sound Example		22 February 1986
 *
 * Copyright (c) 1986 Sam Dicker, Commodore/Amiga Inc.
 * This is public domain software.  Permission is granted to copy and use
 * this software as long as this notice is included.
 *
 * This module makes a beep sound like the CTRL-G on a VT100 terminal.
 *
 * Calling VTBeep() starts the sound which stops when it times out.
 * Calling it again, while it is playing, only restarts the timer.
 * Call KillVTBeep() to kill any beeps in progress before exiting your
 * main program.
 *
 * VTBeep() and KillVTBeep() are re-entrant but cannot be used in interrupt
 * code.
 *
 * The audio in this example is very crude.  It is more of an example of
 * using the timer device and multi-tasking.  I sincerely hope, if it appears
 * in any products, that it is only used as an authentic 'cheap terminal'
 * sound.  A little more code can produce much better (less obnoxious) sound.
 *
 * VT100 is a trademark of Digital Equipment Corporation.
 *
 *****************************************************************************/

#include "exec/types.h"
#include "exec/errors.h"
#include "exec/memory.h"
#include "devices/timer.h"
#include "devices/audio.h"
#include "libraries/dos.h"

#define BEEPNAME	"VTBeep"
#define BEEPSIZE	10
#define BEEPFREQ	1000
#define COLORCLOCK	3579545
#define SIGB_BEEP	31
#define SIGF_BEEP	(1 << 31)
#define SIGF_PORT	(1 << replyPort->mp_SigBit)

/* channel allocation map (try left channel; if unavailable try right) */
UBYTE allocationMap[] = { 1, 8, 2, 4 };


/* make beep sound */

VTBeep()
{
    struct Task *beepTCB;
    VOID beepTask();

    /* prevent beep child task, if it already exists, from going away before
     * it is signaled */
    Forbid();

    /* find the task by name */
    beepTCB = (struct Task *)FindTask(BEEPNAME);

    /* check if the task exists */
    if (beepTCB == NULL)

	/* it doesn't exist, so create it */
	beepTCB = (struct Task *)CreateTask(BEEPNAME, 25, beepTask, 500);

    else

	/* it already exist so signal it so restart it's timer */
	Signal(beepTCB, SIGF_BEEP);

    Permit();

    /* return success */
    return(beepTCB != NULL);
}


/* kill any beep sounds in progress.  This is necessary before exiting the
 * main program; otherwise, if a beep is playing, when the beep times out
 * and the child task wakes up its code segment may be gone */

KillVTBeep()
{
    struct Task *beepTCB;

    do {

	/* prevent beep child task, if it already exists, from going away
	 * before it is signaled */
	Forbid();

	/* find the task by name */
	beepTCB = (struct Task *)FindTask(BEEPNAME);

	/* check if the task exists */
	if (beepTCB != NULL) {

	    /* it already exist so signal it so go away */
	    Signal(beepTCB, SIGBREAKF_CTRL_C);

	    /* give it a chance to wake up, if it is lower priority */
	    Delay(10);

	}
	Permit();

    /* if it existed, kill it again */
    } while (beepTCB != NULL);
}


/* beep sound child task */

VOID beepTask()
{
    struct MsgPort *replyPort;
    struct timerequest *timerIOB;
    struct IOAudio *audioIOB;
    UBYTE *beepWave;
    ULONG signals;

    /* allocate signal used to re-start beep */
    if (AllocSignal(SIGB_BEEP) == SIGB_BEEP) {

	/* create reply port for timer and sound I/O block */
	replyPort = (struct MsgPort *)CreatePort(NULL);

	if (replyPort != NULL) {

	    /* create timer I/O block */
	    timerIOB = (struct timerequest *)
		    CreateExtIO(replyPort, sizeof(struct timerequest));

	    if (timerIOB != NULL) {

		/* open timer device */
		if (OpenDevice(TIMERNAME, UNIT_VBLANK, timerIOB, 0) == 0) {

		    timerIOB->tr_node.io_Command = TR_ADDREQUEST;

		    /* create beep waveform */

		    beepWave = (UBYTE *)AllocMem(BEEPSIZE,
			    MEMF_CHIP | MEMF_CLEAR);
		    if (beepWave != 0) {
			beepWave[0] = 100;


			/* create audio I/O block */
			audioIOB = (struct IOAudio *)
				CreateExtIO(replyPort, sizeof(struct IOAudio));
			if (audioIOB != NULL) {

			    /* setup audio I/O block to allocate a channel
			     * when the audio device is opened (see Volume 1 of
			     * the Amiga ROM Kernel Manual - Audio Device
			     * chapter and Volume 2 of the Amiga ROM Kernel
			     * Manual - Device Summaries appendix for details)
			     */

			    audioIOB->ioa_Request.io_Message.mn_Node.ln_Pri =
				    85;
			    audioIOB->ioa_Data = allocationMap;
			    audioIOB->ioa_Length = sizeof(allocationMap);


			    /* open the audio device */
			    if (OpenDevice(AUDIONAME, 0, audioIOB, 0) == 0) {

				/* setup the audio I/O block to play sound */

				audioIOB->ioa_Request.io_Command = CMD_WRITE;
				audioIOB->ioa_Request.io_Flags = ADIOF_PERVOL;
				audioIOB->ioa_Data = beepWave;
				audioIOB->ioa_Length = BEEPSIZE;
				audioIOB->ioa_Period =
					COLORCLOCK / (BEEPSIZE * BEEPFREQ);
				audioIOB->ioa_Volume = 64;


				/* start the sound */
				BeginIO(audioIOB);

				/* from this point on the task in cannot
				   be pre-empted.  This prevents the
				   parent task from signaling it to
				   restart the timer while it is cleaning
				   up */
			    
				Forbid();
				do {

				    /* start the timer */

				    timerIOB->tr_time.tv_secs = 0;
				    timerIOB->tr_time.tv_micro = 250000;
				    SendIO(timerIOB);

				    
				    /* wait for:
					. the timer to time out,
					. the audio I/O block to return
					  (indicating an error had occurred),
					. a signal to restart to timer, or
					. a signal to quit */

				    do
					signals = Wait(SIGBREAKF_CTRL_C |
						SIGF_BEEP | SIGF_PORT);
				    while ((signals & SIGF_PORT) != 0 &&
					    CheckIO(timerIOB) == 0 &&
					    CheckIO(audioIOB) == 0);


				    /* if the timer is still going, kill it */

				    if (CheckIO(timerIOB) == 0) {
					AbortIO(timerIOB);
					WaitIO(timerIOB);
				    }


				/* restart the timer if signaled */
				} while ((signals & SIGF_BEEP) != 0);

				/* clean up */

				/* closing the audio device kills the sound
				 * and frees the channels */
				CloseDevice(audioIOB);
			    }
			    DeleteExtIO(audioIOB);
			}
			FreeMem(beepWave, BEEPSIZE);
		    }
		    CloseDevice(timerIOB);
		}
		DeleteExtIO(timerIOB);
	    }
	    DeletePort(replyPort);
	}
    }
}