(*-------------------------------------------------------------------------
  :Program.       AudioSupport.mod
  :Contents.      Easy sound programming
  :Author.        Christian Stiens
  :Address.       Heustiege 2, W-4710 Lüdinghausen
  :Copyright.     PD
  :Language.      Oberon
  :Translator.    Amiga Oberon V2.01 [fbs]
  :History.       V1.0, 25-Aug-90: First release
  :History.       V1.1, 24-Jul-91: Oberon, +PlayLoopSound
  :History.                        +DontAbort, +SetPriority
-------------------------------------------------------------------------*)


(*---------------------------- DEFINITION --------------------------------

DEFINITION AudioSupport;

CONST
  clock = 3563220;   (* per = clock / freq *)
  left0 = 0;
  right0 = 1;
  right1 = 2;
  left1 = 3;

PROCEDURE SetPriority(pri: SHORTINT);

  (* OpenChannel will open channels with priority pri *)

PROCEDURE DontAbort;

  (* Program will not be aborted if channels are not free *)

PROCEDURE OpenChannel(mask: SET): SHORTINT;

  (* Open one channel of mask and return its channel number *)

PROCEDURE CloseChannel(chan: SHORTINT);

  (* Close the channel chan (which not has to be open). All open
     channels are closed automatic at program end *)

PROCEDURE PlaySound(chan: SHORTINT; sound: e.ADDRESS; size: LONGINT;
                    per, vol, cyc: INTEGER);

  (* Play the sample at address 'sound' (in chip mem) with period 'per'
     (per >= 124), volume 'vol' (0 <= vol <= 64) 'cyc' times (cyc=0 means
     forever) *)

PROCEDURE PlayLoopSound(chan: SHORTINT; sound: e.ADDRESS;
                        oneShot, repeat: LONGINT;
                        per, vol: INTEGER);

  (* Play a looped sound with attack length 'oneShot' and looped
     length 'repeat' *)

PROCEDURE StopSound(chan: SHORTINT);

  (* Stop the sound in channel 'chan' *)

PROCEDURE CheckSound(chan: SHORTINT): BOOLEAN;

  (* Returns 'TRUE' if channel 'chan' is playing at this moment *)

PROCEDURE ModifySound(chan: SHORTINT; per, vol: INTEGER);

  (* Modifies frequency and volume of a running sound *)

PROCEDURE WaitSound(chan: SHORTINT);

  (* Waits until channel 'chan' has finished his sound *)

PROCEDURE Filter(on: BOOLEAN);

  (* Set the Hardware filter on or off *)

----------------------------- END DEFINITION ---------------------------*)



MODULE AudioSupport;

  (* $StackChk- $NilChk- $RangeChk- $OvflChk- $ReturnChk- $CaseChk- *)

  IMPORT
    au : Audio,
    e  : Exec,
    es : ExecSupport,
    hw : Hardware,
    rq : Requests,
    sys: SYSTEM;

  CONST
    clock  * = 3563220;

    left0  * = 0;
    right0 * = 1;
    right1 * = 2;
    left1  * = 3;

    cantOpen = "Can't open audio channel";
    notOpen  = "Audio channel not open";
    illegal  = "Illegal audio channel";

  VAR
    mp1            : ARRAY 4 OF e.MsgPortPtr;
    ioa1,ioa2,ioa3 : ARRAY 4 OF au.IOAudioPtr;
    open           : ARRAY 4 OF BOOLEAN;
    looped         : ARRAY 4 OF BOOLEAN;
    lastPer,lastVol: ARRAY 4 OF INTEGER;
    priority       : SHORTINT;
    abort          : BOOLEAN;


  PROCEDURE SetPriority*(pri: SHORTINT);
  BEGIN
    priority := pri
  END SetPriority;

  PROCEDURE DontAbort*;
  BEGIN
    abort := FALSE;
  END DontAbort;

  PROCEDURE OpenChannel*(mask: SET): SHORTINT;
    VAR
      allocMask : ARRAY 4 OF SHORTSET;
      chan      : SHORTINT;
      i         : INTEGER;
      mp        : e.MsgPortPtr;
      ioa       : POINTER TO ARRAY 3 OF au.IOAudio;
  BEGIN
    IF mask * {0..3} = {} THEN mask := {0..3} END;
    mp := es.CreatePort("",0);
    IF mp # NIL THEN
      ioa := e.AllocMem(sys.SIZE(ioa^),LONGSET{e.memClear,e.public});
      IF ioa # NIL THEN
        ioa[0].request.message.node.type := e.message;
        ioa[0].request.message.length := sys.SIZE(au.IOAudio);
        ioa[0].request.message.replyPort := mp;
        i := 0;
        chan := 0; WHILE chan < 4 DO
          IF chan IN mask THEN allocMask[i] := SHORTSET{chan}; INC(i) END;
          INC(chan)
        END;
        ioa[0].request.message.node.pri := priority;
        ioa[0].data   := sys.ADR(allocMask);
        ioa[0].length := i;
        IF e.OpenDevice(au.audioName,0,sys.VAL(e.MessagePtr,ioa),LONGSET{})
                                                                     = 0 THEN
          ioa^[1] := ioa^[0];
          ioa^[2] := ioa^[0];
          CASE sys.VAL(LONGINT,ioa[0].request.unit) OF
            1: chan := left0  |
            2: chan := right0 |
            4: chan := right1 |
            8: chan := left1
          ELSE
            HALT(20);
          END;
          mp1[chan] := mp;
          ioa1[chan] := sys.ADR(ioa[0]);
          ioa2[chan] := sys.ADR(ioa[1]);
          ioa3[chan] := sys.ADR(ioa[2]);
          ioa1[chan].data := 0;
          open[chan] := TRUE;
          looped[chan] := FALSE;
          RETURN chan
        END;
        e.FreeMem(ioa,sys.SIZE(ioa^));
      END;
      es.DeletePort(mp)
    END;
    rq.Assert(~abort,cantOpen);
    RETURN -1
  END OpenChannel;

  PROCEDURE CloseChannel*(chan: SHORTINT);
  BEGIN
    IF (chan >= 0) & (chan <= 3) & open[chan] THEN
      e.CloseDevice(ioa1[chan]);
      e.FreeMem(ioa1[chan],3*sys.SIZE(au.IOAudio));
      es.DeletePort(mp1[chan]);
      open[chan] := FALSE;
    END
  END CloseChannel;

  PROCEDURE CheckChannel(chan: SHORTINT): BOOLEAN;
  BEGIN
    CASE chan OF
       -1 : rq.Assert(~abort,notOpen); RETURN FALSE |
     0..3 : IF open[chan] THEN RETURN TRUE
            ELSE rq.Assert(~abort,notOpen); RETURN FALSE END
    ELSE    rq.Assert(FALSE,illegal);
    END;
  END CheckChannel;

  PROCEDURE PlaySound*(chan:SHORTINT; sound:e.ADDRESS; size:LONGINT;
                       per,vol,cyc:INTEGER);
  BEGIN
    IF ~CheckChannel(chan) THEN RETURN END;
    IF looped[chan] THEN es.AbortIO(ioa2[chan]) END;
    es.AbortIO(ioa1[chan]);
    IF size > 1 THEN
      ioa1[chan].request.command := e.write;
      ioa1[chan].request.flags := SHORTSET{au.pervol};
      ioa1[chan].data   := sound;
      ioa1[chan].length := size;
      ioa1[chan].period := per;
      ioa1[chan].volume := vol;
      ioa1[chan].cycles := cyc;
      es.BeginIO(ioa1[chan]);
    END;
    lastPer[chan] := per;
    lastVol[chan] := vol;
    looped[chan] := FALSE;
  END PlaySound;

  PROCEDURE PlayLoopSound*(chan:SHORTINT; sound:e.ADDRESS;
                           oneShot,repeat:LONGINT;
                           per,vol:INTEGER);
  BEGIN
    IF oneShot = 0 THEN
      PlaySound(chan,sound,repeat,per,vol,0);
      RETURN;
    END;
    IF repeat = 0 THEN
      PlaySound(chan,sound,oneShot,per,vol,1);
      RETURN;
    END;
    IF ~CheckChannel(chan) THEN RETURN END;
    es.AbortIO(ioa2[chan]);
    es.AbortIO(ioa1[chan]);
    ioa1[chan].request.command := e.write;
    ioa1[chan].request.flags := SHORTSET{au.pervol};
    ioa1[chan].data := sound;
    ioa1[chan].length := oneShot;
    ioa1[chan].period := per;
    ioa1[chan].volume := vol;
    ioa1[chan].cycles := 1;
    es.BeginIO(ioa1[chan]);
    ioa2[chan].request.command := e.write;
    ioa2[chan].request.flags := SHORTSET{};
    ioa2[chan].data := sound+oneShot;
    ioa2[chan].length := repeat;
    ioa2[chan].cycles := 0;
    es.BeginIO(ioa2[chan]);
    lastPer[chan] := per;
    lastVol[chan] := vol;
    looped[chan] := TRUE;
  END PlayLoopSound;

  PROCEDURE StopSound*(chan: SHORTINT);
  BEGIN
    IF ~CheckChannel(chan) THEN RETURN END;
    IF looped[chan] THEN
      es.AbortIO(ioa2[chan]); looped[chan] := FALSE
    END;
    es.AbortIO(ioa1[chan])
  END StopSound;

  PROCEDURE CheckSound*(chan: SHORTINT): BOOLEAN;
  BEGIN
    IF ~CheckChannel(chan) THEN RETURN FALSE END;
    RETURN looped[chan] OR ((ioa1[chan].data # 0) & ~e.CheckIO(ioa1[chan]))
  END CheckSound;

  PROCEDURE ModifySound*(chan:SHORTINT; per,vol: INTEGER);
  BEGIN
    IF ~CheckChannel(chan) THEN RETURN END;
    IF (vol#lastVol[chan]) OR (per#lastPer[chan]) THEN
      ioa3[chan].request.command := au.perVol;
      ioa3[chan].period := per;
      ioa3[chan].volume := vol;
      e.OldDoIO(ioa3[chan]);
      lastPer[chan] := per;
      lastVol[chan] := vol;
    END;
  END ModifySound;

  PROCEDURE WaitSound*(chan: SHORTINT);
  BEGIN
    IF ~CheckChannel(chan) THEN RETURN END;
    IF NOT looped[chan] THEN e.OldWaitIO(ioa1[chan]) END;
  END WaitSound;

  PROCEDURE Filter*(on: BOOLEAN);
  BEGIN
    IF on THEN
      EXCL(hw.ciaa.pra,hw.led)
    ELSE
      INCL(hw.ciaa.pra,hw.led)
    END
  END Filter;

BEGIN
  priority     := 0;
  abort        := TRUE;
  open[left0]  := FALSE;
  open[right0] := FALSE;
  open[right1] := FALSE;
  open[left1]  := FALSE;
CLOSE
  CloseChannel(left0);
  CloseChannel(right0);
  CloseChannel(right1);
  CloseChannel(left1);
END AudioSupport.
