Program Play8;

{
	Play8.p

	Play a one-shot 8SVX IFF sound file.  The command line is simply
	Play8 filename, where the filename is any path and must be
	present.  This code was derived from Eric Jacobsen's spIFF.c.
	The differences between this and spIFF.c:
	   a) This was translated from C to Pascal
	   b) Several sound files in my collection had odd-length
		name or annotation fields.  That is, the field lengths
		in the file were odd, but the actual data was padded
		with an extra 0 byte.  So this program handles that.
	   c) I added decompression routines taken from an old IFF
		documentation disk.  I couldn't find any properly
		formatted compressed sound files, however, so I'm not
		sure if the decompression is accurate.  The program
		will certainly try to decompress files, but mine came
		out garbage.  Based on the samples I've accumulated,
		it seems that few of them are compressed anyway.

	In my distribution, I included a sample sample, as it were,
	called UseTheForce.8SVX, which obviously came from Star Wars.
}

{$I ":Include/Exec.i"}
{$I ":Include/Ports.i"  I include this here so ExecIO won't have to.
			Otherwise it would try to load "Include/Ports.i",
			which would not necessarily fit in here.}
{$I ":Include/ExecIO.i"}
{$I ":Include/ExecIOUtils.i"}
{$I ":Include/DOS.i"}
{$I ":Include/StringLib.i"}
{$I ":Include/Audio.i"}
{$I ":Include/Parameters.i"}

type
    Voice8Header = record
	oneShotHiSamples,
	repeatHiSamples,
	samplesPreHiCycle : Integer;
	samplesPerSec : Short;
	ctOctave	: Byte;
	sCompression	: Byte;
	volume : Integer;
    end;

type
    FibTable = Array [0..15] of Byte;

const
    ckname : String = Nil;
    NoMem  : String = "\nNot enough memory.\n";
    reps : Integer = 1;
    wrt_flg : Boolean = True;
    ioa : IOAudioPtr = Nil;
    dbuf : Address = Nil;
    FP : FileHandle = Nil;
    codeToDelta : FibTable = (-34, -21, -13, -8, -5, -3, -2, -1, 0,
				1, 2, 3, 5, 8, 13, 21);

var
    VHeader : Voice8Header;
    chan : Char;
    s, ps : String;
    dlen, oerr, i : Integer;
    chnk : ^Integer;
    ckbuffer : Array [0..2] of Short;
    t : Address;

procedure BeginIO(io : IORequestPtr);
begin
{$A	move.l	8(a5),a1
	move.l	$14(a1),a6
	jsr	-$1E(a6)
}
end;

Function D1Unpack(source : String; n : Integer; dest : String; x : Byte) : Byte;
var
    d : Byte;
    i, lim : Integer;
begin
    lim := n shl 1;
    for i := 0 to lim - 1 do begin
	d := Ord(Source[i shr 1]);
	if Odd(i) then
	    d := d and 15
	else
	    d := d shr 4;
	x := x + codeToDelta[d];
	dest[i] := Chr(x);
    end;
    D1Unpack := x;
end;

Procedure DUnpack(source : String; n : Integer; dest : Address);
var
    x : Byte;
begin
    x := D1Unpack(Adr(source[1]), n - 2, dest, Ord(source[0]));
end;

Procedure OpenFile;
var
    NameBuffer : Array [0..127] of char;
    Name : String;
begin
    Name := Adr(NameBuffer);
    GetParam(1, Name);
    if strlen(Name) = 0 then begin
	Writeln('Usage: Play8 filename');
	Exit(10);
    end;
    FP := DOSOpen(Name, ModeOldFile);
    if FP = Nil then begin
	Writeln('Could not open ', Name);
	Exit(10);
    end;
end;

procedure CleanUp;
begin
    if ioa <> Nil then begin
	with ioa^.ioaRequest.ioMessage do begin
	    if mnReplyPort <> Nil then
		DeletePort(mnReplyPort);
	end;
	FreeMem(ioa, SizeOf(IOAudio));
    end;
    if dbuf <> Nil then
	FreeMem(dbuf, dlen);
    if FP <> nil then
	DOSClose(FP);
end;


Procedure pExit(Msg : String);
begin
    Writeln(Msg);
    CleanUp;
    Exit(20);
end;

Procedure DoRead(Buffer : Address; Length : Integer);
var
    ReadResult : Integer;
begin
    ReadResult := DOSRead(FP, Buffer, Length);
    if ReadResult <> Length then
	pExit("Read error");
end;

Procedure WriteData(len : Integer);
var
    MBuffer : Array [0..127] of Char;
    MString : String;
begin
    MString := Adr(MBuffer);
    if Odd(len) then
	len := Succ(len);
    MBuffer[127] := '\0';
    while len > 127 do begin
	DoRead(MString, 127);
	if wrt_flg then
	    Write(MString);
	len := len - 127;
    end;
    if len > 0 then begin
	DoRead(MString, len);
	MString[len] := '\0';
	if wrt_flg then
	    Writeln(MString);
    end;
    wrt_flg := True;
end;

begin
    ckname := Adr(ckbuffer);
    ckname[4] := '\0';
    chan := Chr(15);
    OpenFile;
    DoRead(ckname, 4);
    if streq(ckname, "FORM") then begin
	DoRead(ckname,4);	{ Get size out of the way. }
	DoRead(ckname,4);
	if streq(ckname,"8SVX") then begin
	    DoRead(ckname,4);
	    while not streq(ckname,"BODY") do begin
		DoRead(Adr(dlen), 4);
		if streq(ckname,"VHDR") then
		    DoRead(Adr(VHeader), SizeOf(Voice8Header))
		else begin
		    chnk := Address(ckname);
		    case chnk^ of
		      $4e414d45: Write("\nName of sample: ");
		      $41555448: Write("\nAuthor: ");
		      $28432920,
		      $28632920,
		      $2843294a,
		      $2863294a: Write("\n(c) notice: ");
		      $414e4e4f: WriteLn("\nAnnotation field:");
		    else
		      wrt_flg := True;
		    end;
		    WriteData(dlen);
		end;
		DoRead(ckname, 4);
	    end;
	    DoRead(Adr(dlen), 4);
	    Writeln(dlen, ' bytes at ', VHeader.samplesPerSec, 'Hz');
	end else
	    pExit("Not an 8SVX sound file.")
    end else
	pExit("Not an IFF file.");
    ioa := AllocMem(SizeOf(IOAudio), MemPublic);
    if ioa = Nil then
	pExit(NoMem);
    with ioa^.ioaRequest.ioMessage do begin
	mnReplyPort := CreatePort(Nil, 0);
	if mnReplyPort = nil then
	    pExit("Unable to allocate port");
    end;

    dbuf := AllocMem(dlen, MemPublic + MemChip);
    if dbuf = Nil then
	pExit(NoMem);

    with ioa^ do begin
	ioaRequest.ioMessage.mnNode.lnPri := 10;
	ioaData := Adr(chan);
	ioaLength := 1;
	ioaAllocKey := 0;
    end;

    oerr := OpenDevice(AUDIONAME, 0, IORequestPtr(ioa), 0);
    if oerr <> 0 then
	pExit("Can't open audio device");

    if dlen > 131000 then begin  { Supposed hardware limitation. }
	dlen := 131000;
    end else if Odd(dlen) then
	dlen := Pred(dlen);
    DoRead(dbuf, dlen);

    if VHeader.sCompression = 1 then begin
	t := AllocMem(dlen shl 1, MemChip + MemPublic);
	if t = Nil then
	    pExit("Not enough memory for decompression");
	DUnpack(dbuf, dlen, t);
	FreeMem(dbuf, dlen);
	dbuf := t;
	dlen := dlen shl 1;
    end else if VHeader.sCompression > 1 then
	pExit("Unknown compression type");

    with ioa^ do begin
	ioaRequest.ioCommand := CMD_WRITE;
	ioaRequest.ioFlags := ADIOF_PERVOL;
	ioaData := dbuf;
	ioaCycles := 1;		{ 1 or from command line. }
	ioaLength := dlen;
	ioaPeriod := 3579546 div VHeader.samplesPerSec;
	ioaVolume := 64;	 	{ Always use maximum volume. }
    end;

    BeginIO(IORequestPtr(ioa));
    oerr := WaitIO(IORequestPtr(ioa));

    if oerr <> 0 then
	Writeln('Error ', oerr, ' playing sample');
    CloseDevice(IORequestPtr(ioa));
    CleanUp;
end.
