packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/soundeff.mod

IMPLEMENTATION MODULE SoundEffects;

	(********************************************************)
	(*							*)
	(*	Procedures to produce audible output.		*)
	(*							*)
	(*	Programmer:	P. Moylan			*)
	(*	Last edited:	9 March 1995			*)
	(*	Status:		Working				*)
	(*							*)
	(********************************************************)

FROM SYSTEM IMPORT
    (* proc *)	ADR;

FROM Storage1 IMPORT
    (* proc *)	ALLOCATE, DEALLOCATE;

FROM LowLevel IMPORT
    (* proc *)	OutByte, LowByte, HighByte;

FROM Queues IMPORT
    (* type *)	Queue,
    (* proc *)	CreateQueue, AddToQueue, TakeFromQueue, Empty;

FROM Semaphores IMPORT
    (* proc *)	CreateSemaphore, Wait, Signal;

FROM TaskControl IMPORT
    (* proc *)	CreateTask, NotUsingFloatingPoint;

FROM Timer IMPORT
    (* proc *)	Sleep;

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure;

(************************************************************************)

CONST
    OffCode = 0CH;  OnCode = 0FH;

TYPE

    (* The following declaration is the closest we can come in Modula-2	*)
    (* to specifying the generic type "ARRAY OF Note".  It means, of	*)
    (* course, that we have to disable array bound checking when	*)
    (* compiling this module.						*)

    NoteArray = ARRAY [0..0] OF Note;

    NoteArrayPointer = POINTER TO NoteArray;

    SemaphorePointer = POINTER TO Semaphore;

    QueuePointer = POINTER TO QueueElement;

    (* The music waiting to be played is ordered by having a queue of	*)
    (* waiting entries.  Each element of the queue is a record with a	*)
    (* pointer to an array of music data, the last subscript of that	*)
    (* array (the first subscript is always assumed to be zero), and a	*)
    (* pointer to the semaphore on which we will perform a Signal to	*)
    (* indicate that we have finished working on this section of data.	*)
    (* Notice that the queue is not directly a queue of data.  Rather, 	*)
    (* it is a queue of pointers to data.				*)

    QueueElement =  RECORD
			dataptr: NoteArrayPointer;
			lastsubscript: CARDINAL;
			CompletionSemaphoreAddress: SemaphorePointer;
		    END (*RECORD*);

(************************************************************************)

VAR

    (* PlayQueue is the actual queue.	*)

    PlayQueue: Queue;

    (* beep holds the data to be used to produce a "beep" noise.	*)

    beep: ARRAY [0..1] OF Note;

    (* The completion semaphore used when doing the beep.	*)

    BeepSem: Semaphore;

(************************************************************************)

PROCEDURE Play (VAR (*IN*) playdata: ARRAY OF Note;
			VAR (*INOUT*) done: Semaphore);

    (* Adds the array to the list of music queued up waiting to be	*)
    (* played.  The actual playing is handled by a separate task - see	*)
    (* PlayerTask later in this module.  On return from this procedure,	*)
    (* the playing is not necessarily over.  The caller must perform a	*)
    (* Wait(done) to know when the array playdata is no longer in use.	*)

    VAR elementpointer: QueuePointer;

    BEGIN
	NEW (elementpointer);
	WITH elementpointer^ DO
	    dataptr := ADR (playdata);  lastsubscript := HIGH (playdata);
	    CompletionSemaphoreAddress := ADR(done);
	END (*WITH*);
	AddToQueue (PlayQueue, elementpointer);
    END Play;

(************************************************************************)

PROCEDURE Beep;

    (* Produces a short "beep" noise.	*)

    BEGIN
	Wait (BeepSem);
	Play (beep, BeepSem);
    END Beep;

(************************************************************************)

PROCEDURE PlayerTask;

    (* This is the procedure which does all the real work.  It runs as	*)
    (* a separate task, which typically spends most of its time		*)
    (* blocked while waiting for something to play.  Even while it is	*)
    (* playing something, it spends most of its time sleeping, because	*)
    (* the timer hardware does most of its work for it.			*)

    (* A duration code of 0 indicates the end of the data, in cases	*)
    (* where the data do not fill the entire array.			*)
    (* A period code of 1, with a nonzero duration, indicates a rest.	*)

    CONST TimerControlPort = 67;  channel2 = 66;

    VAR arrayptr: NoteArrayPointer;  j, top, Period, Duration: CARDINAL;
	doneaddress: SemaphorePointer;  qptr: QueuePointer;

    BEGIN
	NotUsingFloatingPoint;
	LOOP	(* forever *)

	    (* Note that this task will remain blocked inside procedure	*)
	    (* TakeFromQueue while there is nothing to play.		*)

	    qptr := TakeFromQueue (PlayQueue);
	    WITH qptr^ DO
		arrayptr := dataptr;
		top := lastsubscript;
		doneaddress := CompletionSemaphoreAddress;
	    END (*WITH*);
	    DISPOSE (qptr);

	    (* Turn on the speaker.  This is done by setting the two	*)
	    (* low-order bits of port 97.				*)

	    OutByte (97, OnCode);

	    j := 0;

	    LOOP
		(*# save, check(index=>off) *)
		Duration := arrayptr^[j].duration;
		Period := arrayptr^[j].period;
		(*# restore *)

		(* A duration code of 0 indicates the end of the data.	*)

		IF Duration = 0 THEN
		    EXIT(*LOOP*);
		END (*IF*);

		IF Period = 1 THEN

		    (* A rest has been requested.  Turn off the speaker	*)
		    (* and sleep for a while, then turn on the speaker.	*)

		    OutByte (97, OffCode);
		    Sleep (Duration);
		    OutByte (97, OnCode);

		ELSE

		    (* We have a normal note to play.  The control byte	*)
		    (* sent to the timer control port specifies: timer	*)
		    (* #2, two count bytes to follow, and mode 3	*)
		    (* (square wave).					*)

		    OutByte (TimerControlPort, 0B6H);
		    OutByte (channel2, LowByte(Period));
		    OutByte (channel2, HighByte(Period));

		    Sleep (Duration);

		END (*IF*);

		IF j = top THEN EXIT(*LOOP*) END(*IF*);
		INC (j);

	    END (*LOOP*);

	    (* Turn off the speaker.	*)

	    OutByte (97, OffCode);

	    (* Tell the user task that we have finished with this	*)
	    (* buffer-full of data.					*)

	    Signal (doneaddress^);

	END (*LOOP*);
    END PlayerTask;

(************************************************************************)
(*			MODULE TERMINATION				*)
(************************************************************************)

PROCEDURE CloseDown;

    (* Brings the module to an orderly halt.	*)

    VAR qptr: QueuePointer;

    BEGIN
	WHILE NOT Empty(PlayQueue) DO
	    qptr := TakeFromQueue (PlayQueue);
	    Signal (qptr^.CompletionSemaphoreAddress^);
	    DISPOSE (qptr);
	END (*WHILE*);

	(* Turn off the speaker. *)

	OutByte (97, OffCode);

    END CloseDown;

(************************************************************************)
(*			MODULE INITIALISATION				*)
(************************************************************************)

BEGIN

    (* Create an initially empty queue of music to be played.	*)

    CreateQueue (PlayQueue);

    (* Define the parameters of a "beep" noise.	*)

    beep[0].period := 1000;
    beep[0].duration := 40;
    beep[1].period := 1;
    beep[1].duration := 20;
    CreateSemaphore (BeepSem, 1);

    SetTerminationProcedure (CloseDown);

    (* Create the task which plays the music.	*)

    CreateTask (PlayerTask, 10, "Sound effects");

END SoundEffects.