-- DCT.mesa
-- D0 printer port CAT test
-- Last Modified  LStewart January 29, 1981  5:51 PM

DIRECTORY
  BcplOps USING [CleanupReason],
  ImageDefs USING [
    AddCleanupProcedure, AllReasons, BcdTime, CleanupItem,
    RemoveCleanupProcedure],
  Inline USING [COPY],
  IODefs USING [GetInputStream, ReadChar, ReadID, Rubout],
  MiscDefs USING [Zero],
  Mopcodes USING [zJRAM, zLIW],
  ProcessDefs USING [MsecToTicks, Pause, Yield],
  StreamDefs USING [
    DiskHandle, FileNameError, NewWordStream, Read, ReadBlock, StreamHandle,
    WriteAppend, WriteBlock],
  StringDefs USING [InvalidNumber, LowerCase, StringToNumber],
  SystemDefs USING [
    AllocateHeapNode, AllocateResidentSegment, FreeHeapNode, FreeSegment, Quad],
  TimeDefs USING [PackedTime],
  WF USING [SWF1, WF0, WF1, WFC, WFCR];

DCT: PROGRAM
  IMPORTS
    ImageDefs, Inline, IODefs, MiscDefs, ProcessDefs, StreamDefs, StringDefs,
    SystemDefs, WF =
  BEGIN
  -- Data buffer
  BufSize: CARDINAL = 16384; -- for each input and output (samples)
  BufWords: CARDINAL = BufSize/2;
  Buffer: TYPE = RECORD [
    outputStartOffset: CARDINAL,
    outputEndOffset: CARDINAL,
    outputCurrentOffset: CARDINAL,
    inputDisplacement: CARDINAL,
    buffers: ARRAY [0..0) OF UNSPECIFIED];
  buffer: POINTER TO Buffer;
  lbuffer: LONG POINTER TO Buffer;
  currentSize: CARDINAL;
  audioOn, echoOn, tickOn, quitFlag, tock: BOOLEAN ← FALSE;
  memory: POINTER;
  request: CARDINAL;
  echoer, ticker: PROCESS;
  fileName: STRING ← [40];

  Main: PROC = {
    c: CHARACTER;
    bcdTime: TimeDefs.PackedTime ← ImageDefs.BcdTime[];
    WF.WF1["Dolphin Printer Port Audio Terminal Test of %lt*n", @bcdTime];
    DO
      ENABLE { StringDefs.InvalidNumber, IODefs.Rubout => {WF.WFCR[]; LOOP; }; };
      WF.WF0["%% "];
      c ← StringDefs.LowerCase[IODefs.ReadChar[]];
      SELECT c FROM
	'a => {
	  arg: LONG UNSPECIFIED;
	  WF.WF0["Audio is now "];
	  audioOn ← NOT audioOn;
	  IF audioOn THEN buffer.outputCurrentOffset ← buffer.outputStartOffset;
	  arg ← IF audioOn THEN lbuffer ELSE 1;
	  AudioControl[arg];
	  WF.WF0[IF audioOn THEN "ON*n" ELSE "OFF*n"];
	  };
	'b => {
	  s: STRING ← [40];
	  arg: CARDINAL;
	  AllOff[];
	  WF.WF1["Buffer length (max %u words): ", BufWords];
	  WF.SWF1[s, "%u", currentSize];
	  IODefs.ReadID[s];
	  arg ← StringDefs.StringToNumber[s, 10];
	  currentSize ← MIN[BufWords, arg];
	  buffer.outputEndOffset ← buffer.outputStartOffset + currentSize - 1;
	  buffer.outputCurrentOffset ← buffer.outputStartOffset;
	  WF.WF1[" [used %u]*n", currentSize];
	  };
	'e => {
	  echoOn ← NOT echoOn;
	  tickOn ← echoOn;
	  IF NOT echoOn THEN ProcessDefs.Pause[ProcessDefs.MsecToTicks[500]];
	  IF tock THEN WF.WF0["*n%% "];
	  tock ← FALSE;
	  WF.WF0["Echo is now "];
	  WF.WF0[IF echoOn THEN "ON*n" ELSE "OFF*n"];
	  };
	'o => {
	  s: STRING ← [40];
	  arg: CARDINAL;
	  AllOff[];
	  WF.WF1["Offset of input buffer (max %u): ", BufWords];
	  WF.SWF1[s, "%u", buffer.inputDisplacement];
	  IODefs.ReadID[s];
	  arg ← StringDefs.StringToNumber[s, 10];
	  arg ← MIN[BufWords, arg];
	  buffer.inputDisplacement ← arg;
	  WF.WF1[" [used %u]*n", arg];
	  };
	'p => PlayFile[];
	'q => {WF.WF0["Quit!*n"]; EXIT; };
	'r => RecordFile[];
	'z => {
	  WF.WF0["Zero output buffer*n"];
	  MiscDefs.Zero[@buffer.buffers[0], currentSize];
	  };
	'? => {
	  WF.WF0["Audio on/off, "];
	  WF.WF0["Buffer length, "];
	  WF.WF0["Echo on/off, "];
	  WF.WF0["Offset (to input buffer), "];
	  WF.WF0["Play, "];
	  WF.WF0["Quit, "];
	  WF.WF0["Record, "];
	  WF.WF0["Zero output buffer, "];
	  WF.WF0["???, "];
	  WF.WFCR[];
	  };
	ENDCASE => WF.WF0["???*n"];
      ENDLOOP;
    WF.WF0["Bye*n"];
    };

  AllOff: PROC = {
    IF audioOn OR echoOn THEN {
      audioOn ← echoOn ← tickOn ← FALSE;
      AudioControl[1];
      ProcessDefs.Pause[ProcessDefs.MsecToTicks[200]];
      IF tock THEN WF.WF0["*n%% "];
      WF.WF0["Audio and echoing are off*n"];
      ProcessDefs.Pause[ProcessDefs.MsecToTicks[50]];
      tock ← FALSE;
      };
    };

  PlayFile: PROC = {
    fh: StreamDefs.DiskHandle;
    eof: BOOLEAN ← FALSE;
    rLoc, wLoc, count, eofLoc: CARDINAL;
    AllOff[];
    WF.WF0["Play local file (name): "];
    IODefs.ReadID[fileName];
    WF.WFCR[];
    fh ← StreamDefs.NewWordStream[
      name: fileName, access: StreamDefs.Read !
      StreamDefs.FileNameError => {WF.WF0[" no such file*n"]; GOTO Quit; }];
    currentSize ← BufWords;
    buffer.outputEndOffset ← buffer.outputStartOffset + currentSize - 1;
    wLoc ← buffer.outputCurrentOffset ← buffer.outputStartOffset;
    -- keep input out of the way
    buffer.inputDisplacement ← BufWords;
    -- prefill buffer
    rLoc ← StreamDefs.ReadBlock[
      stream: fh, address: @buffer.buffers[0], words: currentSize];
    IF rLoc # currentSize THEN {eof ← TRUE; eofLoc ← rLoc; } ELSE rLoc ← 0;
    -- turn on audio
    audioOn ← TRUE;
    AudioControl[lbuffer];
    -- keep buffer filled
    DO
      IF eof THEN EXIT;
      ProcessDefs.Yield[];
      wLoc ← buffer.outputCurrentOffset - buffer.outputStartOffset;
      IF (BufWords + wLoc - rLoc) MOD currentSize > 512 THEN {
	IF wLoc < rLoc THEN {
	  --wraparound
	  count ← StreamDefs.ReadBlock[
	    stream: fh, address: @buffer.buffers[rLoc],
	    words: currentSize - rLoc];
	  IF count # (currentSize - rLoc) THEN {
	    eof ← TRUE; rLoc ← rLoc + count; eofLoc ← rLoc; EXIT; }
	  ELSE rLoc ← 0;
	  };
	-- no wraparound
	count ← StreamDefs.ReadBlock[
	  stream: fh, address: @buffer.buffers[rLoc], words: wLoc - rLoc];
	IF count # (wLoc - rLoc) THEN {
	  eof ← TRUE; rLoc ← rLoc + count; eofLoc ← rLoc; EXIT; }
	ELSE rLoc ← wLoc;
	};
      ENDLOOP;
    -- supply a bunch of zeros
    DO
      ProcessDefs.Yield[];
      wLoc ← buffer.outputCurrentOffset - buffer.outputStartOffset;
      IF (BufWords + wLoc - eofLoc) MOD currentSize > 1024 THEN EXIT;
      ENDLOOP;
    IF wLoc < rLoc THEN {
      MiscDefs.Zero[@buffer.buffers[rLoc], currentSize - rLoc]; rLoc ← 0; };
    MiscDefs.Zero[@buffer.buffers[rLoc], wLoc - rLoc];
    -- wait for device to finish file
    DO
      ProcessDefs.Yield[];
      wLoc ← buffer.outputCurrentOffset - buffer.outputStartOffset;
      IF (BufWords + wLoc - eofLoc) MOD currentSize < 1024 THEN EXIT;
      ENDLOOP;
    -- turn off audio and close file
    AudioControl[1];
    audioOn ← FALSE;
    fh.destroy[fh];
    EXITS Quit => NULL;
    };

  RecordFile: PROC = {
    fh: StreamDefs.DiskHandle;
    keys: StreamDefs.StreamHandle ← IODefs.GetInputStream[];
    eof: BOOLEAN ← FALSE;
    rLoc, wLoc, count: CARDINAL;
    {
    -- extra for Quit below
    AllOff[];
    WF.WF0["Record local file (name): "];
    IODefs.ReadID[fileName];
    WF.WFCR[];
    fh ← StreamDefs.NewWordStream[name: fileName, access: StreamDefs.WriteAppend];
    currentSize ← BufWords;
    buffer.outputEndOffset ← buffer.outputStartOffset + currentSize - 1;
    wLoc ← buffer.outputCurrentOffset ← buffer.outputStartOffset;
    -- sidetone
    buffer.inputDisplacement ← 1;
    WF.WF0["Type any key to start, then another key to stop.*n"];
    [] ← IODefs.ReadChar[];
    WF.WF0["Start."];
    -- turn on audio
    audioOn ← TRUE;
    AudioControl[lbuffer];
    tickOn ← TRUE;
    rLoc ← 0;
    -- keep buffer emptied
    DO
      IF NOT keys.endof[keys] THEN EXIT;
      ProcessDefs.Yield[];
      wLoc ← buffer.outputCurrentOffset - buffer.outputStartOffset;
      IF (BufWords + wLoc - rLoc) MOD currentSize > 512 THEN {
	IF wLoc < rLoc THEN {
	  --wraparound
	  count ← StreamDefs.WriteBlock[
	    stream: fh, address: @buffer.buffers[rLoc],
	    words: currentSize - rLoc];
	  IF count # (currentSize - rLoc) THEN {eof ← TRUE; EXIT; } ELSE rLoc ← 0;
	  };
	-- no wraparound
	count ← StreamDefs.WriteBlock[
	  stream: fh, address: @buffer.buffers[rLoc], words: wLoc - rLoc];
	IF count # (wLoc - rLoc) THEN {eof ← TRUE; EXIT; } ELSE rLoc ← wLoc;
	};
      ENDLOOP;
    -- turn off audio
    AudioControl[1];
    audioOn ← tickOn ← FALSE;
    ProcessDefs.Yield[];
    IF eof THEN GOTO Quit;
    -- finish writing file
    wLoc ← buffer.outputCurrentOffset - buffer.outputStartOffset;
    IF wLoc < rLoc THEN {
      --wraparound
      count ← StreamDefs.WriteBlock[
	stream: fh, address: @buffer.buffers[rLoc], words: currentSize - rLoc];
      IF count # (currentSize - rLoc) THEN GOTO Quit ELSE rLoc ← 0;
      };
    -- no wraparound
    count ← StreamDefs.WriteBlock[
      stream: fh, address: @buffer.buffers[rLoc], words: wLoc - rLoc];
    IF count # (wLoc - rLoc) THEN GOTO Quit;
    -- clean up
    ProcessDefs.Yield[];
    [] ← IODefs.ReadChar[];
    WF.WF0["Done*n"];
    fh.destroy[fh];
    buffer.inputDisplacement ← currentSize;
    EXITS Quit => {WF.WF0["file error!*n"]; fh.destroy[fh]; };
    };
    };

  TickProcess: PROC = {
    wLoc, rLoc: CARDINAL ← 0;
    DO
      IF quitFlag THEN EXIT;
      ProcessDefs.Pause[ProcessDefs.MsecToTicks[1000]];
      IF NOT tickOn THEN LOOP;
      WF.WFC['.];
      tock ← TRUE;
      ENDLOOP;
    };

  EchoProcess: PROC = {
    wLoc, rLoc: CARDINAL ← 0;
    DO
      IF quitFlag THEN EXIT;
      ProcessDefs.Yield[];
      wLoc ← buffer.outputCurrentOffset - buffer.outputStartOffset;
      IF NOT echoOn THEN {rLoc ← wLoc; LOOP; };
      IF (BufWords + wLoc - rLoc) MOD currentSize > 256 THEN {
	IF wLoc < rLoc THEN {
	  --wraparound
	  Inline.COPY[
	    from: @buffer.buffers[rLoc + buffer.inputDisplacement],
	    nwords: currentSize - rLoc, to: @buffer.buffers[rLoc]];
	  rLoc ← 0;
	  };
	IF wLoc > rLoc THEN {
	  -- no wraparound
	  Inline.COPY[
	    from: @buffer.buffers[rLoc + buffer.inputDisplacement],
	    nwords: wLoc - rLoc, to: @buffer.buffers[rLoc]];
	  rLoc ← wLoc;
	  };
	};
      ENDLOOP;
    };

  -- Calling AudioControl with a LONG POINTER to the (quad-aligned) Buffer will turn on the audio microcode.  Calling AudioControl with an odd value will turn it off again.

  AudioGo: CARDINAL = 166600B;
  oAudioControl: PROC [bp: LONG UNSPECIFIED] = MACHINE CODE
    BEGIN Mopcodes.zLIW, AudioGo/256, AudioGo MOD 256; Mopcodes.zJRAM; END;

  AudioControl: PROC [bp: LONG UNSPECIFIED, xxx: CARDINAL ← AudioGo] = MACHINE
    CODE BEGIN Mopcodes.zJRAM; END;

  AudioCleanup: PROC [why: BcplOps.CleanupReason] = {
    SELECT why FROM
      InLd => {
	-- if returning from the debugger
	IF wasEnabled THEN {AudioControl[lbuffer]; wasEnabled ← FALSE; };
	};
      Finish, Abort => {
	-- leaving Mesa
	AudioControl[1];
	};
      ENDCASE => {
	-- everything else
	IF audioOn THEN {AudioControl[1]; wasEnabled ← TRUE; };
	};
    };
  -- Mainline code

  wasEnabled: BOOLEAN ← FALSE;
  cleanUpItemP: POINTER TO ImageDefs.CleanupItem;
  cleanUpItemP ← SystemDefs.AllocateHeapNode[SIZE[ImageDefs.CleanupItem]];
  cleanUpItemP↑ ← [NIL, ImageDefs.AllReasons, AudioCleanup];
  ImageDefs.AddCleanupProcedure[cleanUpItemP];
  request ← SIZE[Buffer] + (BufWords*2) + 4; -- +4 for Quad alignment
  memory ← SystemDefs.AllocateResidentSegment[request];
  buffer ← LOOPHOLE[SystemDefs.Quad[memory], POINTER];
  lbuffer ← buffer; -- extend to LONG POINTER
  currentSize ← BufWords;
  MiscDefs.Zero[@buffer.buffers[0], currentSize];
  buffer.outputStartOffset ← SIZE[Buffer];
  buffer.outputEndOffset ← buffer.outputStartOffset + BufWords - 1;
  buffer.outputCurrentOffset ← buffer.outputStartOffset + 0;
  buffer.inputDisplacement ← BufWords;
  echoer ← FORK EchoProcess[];
  ticker ← FORK TickProcess[];
  Main[];
  tickOn ← echoOn ← FALSE;
  quitFlag ← TRUE;
  JOIN echoer;
  JOIN ticker;
  AudioControl[1]; -- Definitely turn off the audio stuff.
  ImageDefs.RemoveCleanupProcedure[cleanUpItemP];
  SystemDefs.FreeHeapNode[cleanUpItemP];
  SystemDefs.FreeSegment[memory];
  END.