language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/demo/playvoc.mod
MODULE PlayVOC;
(********************************************************)
(* *)
(* Plays sound from a VOC file *)
(* to the PC speaker *)
(* *)
(* Programmer: T. Channon *)
(* with some modifications by P. Moylan *)
(* Last edited: 21 March 1995 *)
(* Status: Working *)
(* *)
(* This module has been reorganised a little *)
(* by PM, but it's still basically TC's code. *)
(* *)
(* Changes: *)
(* - some code lifted out of lower-level *)
(* modules. *)
(* - declarations shifted around to make them *)
(* as local as possible. *)
(* - sample rate doubling for input data with *)
(* slow sampling. *)
(* *)
(********************************************************)
IMPORT Lib, Str;
FROM FileSys IMPORT
(* type *) File,
(* proc *) OpenFile, SetPosition, ReadByte, ReadRecord, CloseFile;
FROM IOErrorCodes IMPORT
(* type *) ErrorCode,
(* proc *) TranslateErrorCode;
FROM PlayList IMPORT
(* type *) BufferList, OutputBufferPointer,
(* proc *) CreateList, AddToList, PlayFromList, DiscardList;
FROM PlayBuff IMPORT
(* const*) OutputBufferSize,
(* proc *) SetCycleCount;
FROM Storage1 IMPORT
(* proc *) ALLOCATE, DEALLOCATE;
FROM Windows IMPORT
(* type *) Window, Colour, FrameType, DividerType,
(* proc *) OpenWindow, WriteString, WriteChar, WriteLn, PressAnyKey,
SetCursor;
FROM NumericIO IMPORT
(* proc *) WriteCard, WriteRJCard;
FROM LowLevel IMPORT
(* proc *) Far, Copy, BlockFill;
(************************************************************************)
CONST
(* Size of an input buffer. *)
SIZESOUNDBUFFER = 16384;
(* Sampling rate below which we decide to make the output rate *)
(* twice the input rate. *)
SlowRate = 11000; (* Hz *)
TYPE
SoundBufferType = ARRAY [0..SIZESOUNDBUFFER-1] OF BYTE;
HeadType = RECORD
name : ARRAY [0..12H] OF CHAR;
eof : CHAR;
ofs : CARDINAL;
vers : CARDINAL;
verx : CARDINAL;
END (*RECORD*);
VAR
(* The screen window used for log messages. *)
log: Window;
(* PWM span - this depends on sampling rate. *)
span: SHORTCARD; (* PWM span *)
(************************************************************************)
(* MESSAGES TO SCREEN *)
(************************************************************************)
PROCEDURE Err(s : ARRAY OF CHAR);
BEGIN
WriteLn (log); WriteString (log, 'Error: ');
WriteString (log, s);
PressAnyKey (log);
HALT;
END Err;
(************************************************************************)
(* SETTING THE SAMPLING RATE *)
(************************************************************************)
PROCEDURE SetSampleRate (Hz : CARDINAL);
(* Sets internal rate, module defaults to 13 kHz. *)
CONST
(* MAXPWM sets minimum allowed sample rate. Timer rate is *)
(* 1.19318MHz, 200 is approx 6khz. MINPWM sets max allowed *)
(* rate, would stress computer, 54 is 22.05kHz ie. half CD rate *)
MAXPWM = 200; MINPWM = 54;
BEGIN
span := SHORTCARD (1.1931817E6 / VAL(LONGREAL, Hz));
IF (span >= MINPWM) & (span < MAXPWM) THEN
(* If error use last rate, default if never previously set. *)
SetCycleCount(span);
END;
END SetSampleRate;
(************************************************************************)
(* DATA FORMAT CONVERSION *)
(************************************************************************)
PROCEDURE ScaledValue (val: SHORTCARD; scale: CARDINAL): SHORTINT;
(* Requantizes val to suit the desired output range. The basic *)
(* operation is to scale the value up and then take the high-order *)
(* bits of the product, adding some random noise for dithering. *)
(* The final step is a "round up" based on the most significant *)
(* of the ignored bits. *)
VAR ti: INTEGER;
BEGIN
ti := INTEGER(CARDINAL(val) * scale)
+ INTEGER(Lib.RANDOM(256))-128;
RETURN SHORTINT(ti DIV 128 + (ti MOD 128) DIV 64);
END ScaledValue;
(************************************************************************)
PROCEDURE LoadBlock (f: File; dlen: LONGCARD; RepeatCount: CARDINAL;
VAR (*INOUT*) OutList:BufferList);
(* Converts one block of input data to the form we want, appends *)
(* the result to OutList. Parameter dlen specifies the number of *)
(* input samples. RepeatCount specifies how many output samples *)
(* will be produced for each input sample. We pad out the data at *)
(* the end, if necessary, to give an integral number of output *)
(* buffers. *)
VAR scale: CARDINAL;
OutBuffer: OutputBufferPointer;
OutIndex: [0..OutputBufferSize-1];
OutCount: CARDINAL;
InData: POINTER TO SoundBufferType;
InIndex: [0..SIZESOUNDBUFFER-1];
InCount: CARDINAL;
rptcount: CARDINAL;
status: ErrorCode;
BEGIN
NEW (InData); InIndex := 0; rptcount := RepeatCount;
NEW (OutBuffer); OutIndex := 0; OutCount := 1;
SetCursor (log, 3, 1);
WriteString (log, " 1 output buffers");
scale := CARDINAL(span DIV 2)-2; (*reduce amplitude slightly: dither *)
WHILE dlen > 0 DO
IF dlen > LONGCARD(SIZESOUNDBUFFER-1) THEN
status := ReadRecord(f, ADR(InData^[InIndex]),
SIZESOUNDBUFFER, InCount);
ELSE
status := ReadRecord (f, ADR(InData^[InIndex]),
CARDINAL(dlen), InCount);
END(*IF*);
LOOP
IF dlen = 0 THEN EXIT END(*IF*);
OutBuffer^[OutIndex] := ScaledValue (InData^[InIndex], scale);
IF OutIndex = OutputBufferSize-1 THEN
AddToList (OutList, OutBuffer);
NEW (OutBuffer); OutIndex := 0;
INC (OutCount);
SetCursor (log, 3, 1);
WriteRJCard (log, OutCount, 6);
ELSE
INC (OutIndex);
END (*IF*);
DEC (rptcount);
IF rptcount = 0 THEN
DEC(dlen); rptcount := RepeatCount;
IF InIndex < (SIZESOUNDBUFFER-1) THEN
INC (InIndex);
ELSE
InIndex := 0; EXIT;
END(*IF*);
END (*IF*);
END(*LOOP*);
END(*WHILE*);
IF OutIndex < OutputBufferSize-1 THEN
BlockFill (Far(ADR(OutBuffer^[OutIndex])),
OutputBufferSize - 1 - OutIndex, span DIV 2);
END (*IF*);
AddToList (OutList, OutBuffer);
WriteLn (log);
DISPOSE (InData);
END LoadBlock;
(************************************************************************)
(* THE MAIN PROCEDURE *)
(************************************************************************)
PROCEDURE Play (f: File);
(* Assumption: file f is already open. Takes all the input data *)
(* from file f, converts it to our output format, and calls the *)
(* player procedure. *)
VAR blocktype : SHORTCARD;
sam : SHORTCARD;
rl : CARDINAL;
dlen : LONGCARD;
samplerate: CARDINAL; (* Hz *)
RepeatCount: CARDINAL;
comp: SHORTCARD;
OutList: BufferList;
status: ErrorCode;
BEGIN
CreateList (OutList);
LOOP
IF (ReadRecord(f, ADR(blocktype), 1, rl) <> OK)
OR (blocktype = 0) THEN EXIT END(*IF*);
CASE blocktype OF
| 1: dlen := 0;
status := ReadRecord (f, ADR(dlen), 3, rl);
DEC (dlen, 2);
RepeatCount := 1;
sam := ReadByte (f);
samplerate := VAL(CARDINAL,
1.0E6 / VAL(LONGREAL, 256-CARDINAL(sam)));
IF samplerate < SlowRate THEN
RepeatCount := 2;
samplerate := 2*samplerate;
END (*IF*);
SetSampleRate (samplerate);
WriteString (log, 'Sample rate ');
WriteCard (log, samplerate);
WriteString (log, ' kHz'); WriteLn (log);
comp := ReadByte (f);
LoadBlock (f, dlen, RepeatCount, OutList);
ELSE
Err('Unknown block header byte');
END(*CASE*);
END(*LOOP*);
PlayFromList (OutList);
DiscardList (OutList);
END Play;
(************************************************************************)
PROCEDURE Init(): File;
CONST DEFAULTPWM = 13000; (* 13kHz *)
VAR s: ARRAY [0..79] OF CHAR;
rl: CARDINAL;
head: HeadType;
f: File;
status: ErrorCode;
BEGIN
SetSampleRate (DEFAULTPWM);
OpenWindow (log, black, green, 8, 16, 10, 70, simpleframe, nodivider);
IF Lib.ParamCount() = 0 THEN
Err ("Missing file name");
ELSIF Lib.ParamCount() = 1 THEN
Lib.ParamStr(s, 1);
ELSE
Err ("Only one file name may be specified");
END(*IF*);
WriteString (log, "File: ");
WriteString (log, s);
WriteLn (log);
status := OpenFile (f, s, FALSE);
IF status <> OK THEN
TranslateErrorCode (status, s);
Err (s);
END (*IF*);
status := ReadRecord (f, ADR(head), SIZE(head), rl);
IF Str.Pos(head.name, 'Creative Voice File') = MAX(CARDINAL) THEN
Err ('Not VOC file');
END(*IF*);
status := SetPosition (f, LONGCARD(head.ofs));
Lib.RANDOMIZE;
RETURN f;
END Init;
(************************************************************************)
VAR f: File;
BEGIN
f := Init();
Play (f);
CloseFile (f);
END PlayVOC.