'===========================================================================
' Subject: WAV PLAYER FOR PB35                Date: 08-09-98 (03:58)       
'  Author: Don Schullian                      Code: PB                     
'  Origin: d83@ath.forthnet.gr              Packet: SOUND.ABC
'===========================================================================
$if 0
    ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ·                        PowerBASIC v3.50
 ÚÄÄ´          DASoft          ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ·
 ³  ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¶     Don Schullian    ³ DATE: 1998-01-31 ÇÄ·
 ³  ³ FILE NAME   WAVPLAYR.BAS º   www.DASoftVSS.com  ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÄ º º
 ³  ³                          º                                         º º
 ³  ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¼                                         º º
 ÔÍÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¼ º
   ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¼

Somewhere around here there should be a list of credits but as I've had to
go through several non-functional .WAV players to get enough information to
make this thing work I've lost track of all the names. Anyhow, suffice it
say that I'm not the originator of this code I'm just the guy that got it
all together in one place at one time.

The code works for 8 bit mono .WAV files of any length and (seemingly) of
any Hz. It has been tested and proved on a Sound Blaster Pro, a Sound
Blaster 16, an AWE Gold, and at least one clone.

fSBsetup% ()
  This function is called once to get all the variables ready.
  RETURNS: -1 if all went well
            0 if no BLASTER info was found in the ENVIRONMENT and
              the 'default' setup was used
           -2 if the whole thing went bust

fSBplay% (BYVAL FileSpec$, BYVAL FileNo%)
  FileSpec$ is the name of the file to be played.
    if the incoming value is NULL then it is assumed that the file
      is already opened and the pointer is correctly positioned
    else the file is opened and closed locally
  FileNo% is the PB file number to be used.
    if the incoming value is ZERO then the next unused file number
      will be assigned.
  RETURNS: -1 if file was played to completion
            0 if the file was NOT a valid .WAV file
           +n DOS error

fSBvolumeGet? (BYVAL Which%)
 SBvolumeSet  (BYVAL Which%,BYVAL Volume?)
   Which%   SEE the list of constants for volume controls
   Volume%  The right and left speaker volume is controlled by the high and
            low nibble.
            Lft? = ( Volume?  \  &h10 )
            Rgt? = ( Volume? AND &h0F )
   NOTE: only the master volume is used by this code.

$endif

'.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø.ø
' ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø ø
%Testing    = -1        ' set to 0 to make play unstoppable from keybrd
%SpeakerOn  = &hD1      '
%SpeakerOff = &hD3      '
%VersionCmd = &hE1      ' not used
%MstrVolume = &h22      '
%FMvolume   = &h26      ' not used
%CDvolume   = &h28      ' not used
%VOCvolume  = &h04      ' not used
%MICvolume  = &h0A      ' not used
%LINEvolume = &h2E      ' not used

'PUBLIC ROUTINES
  DECLARE FUNCTION fSBplay%      (BYVAL FileSpec$, BYVAL FileNo%)
  DECLARE FUNCTION fSBsetup%     ()
  DECLARE FUNCTION fSBvolumeGet? (BYVAL Which%)
  DECLARE      SUB  SBvolumeSet  (BYVAL Which%,BYVAL Volume?)

'PRIVATE STUFF
  DECLARE FUNCTION fSBread?      ()
  DECLARE FUNCTION fSBdone%      (BYVAL Chunk%)
  DECLARE      SUB  SBwrite      (BYVAL Cmd?)
  DECLARE      SUB  SBreset      ()

TYPE WaveHeaderType
  RiffID         AS STRING * 4   ' should be 'RIFF'
  RiffLength     AS LONG         '
  WaveID         AS STRING * 4   ' should be 'WAVE'
  FmtID          AS STRING * 4   '
  FmtLength      AS LONG         ' FMT chunk - common fields
  FormatTag      AS WORD         ' format category e.g. 0x0001=PCM
  Stereo         AS WORD         ' number of Channels 1=mono 2=stereo
  Freq           AS LONG         ' dword - sampling rate e.g. 44100Hz
  BytesPerSec    AS DWORD        ' dword - to estimate buffer size
  Blockalign     AS WORD         ' buffer size must be int. multiple of this
  BitsPer        AS INTEGER      ' either 8 (FALSE) or 16 (TRUE)
  DataID         AS STRING * 4   ' should be 'DATA'
  DataLen        AS DWORD        '
END TYPE

DIM sWritePort   AS SHARED INTEGER
DIM sWaitPort    AS SHARED INTEGER
DIM sReadPort    AS SHARED INTEGER
DIM sResetPort   AS SHARED INTEGER
DIM sLenPort     AS SHARED INTEGER
DIM sPagePort    AS SHARED INTEGER
DIM sAddrPort    AS SHARED INTEGER
DIM sVolumePort  AS SHARED INTEGER
DIM sVolumeData  AS SHARED INTEGER
DIM sModeReg     AS SHARED INTEGER
DIM sDRQ         AS SHARED INTEGER
DIM sDMA         AS SHARED INTEGER
DIM sDMAmask     AS SHARED INTEGER
DIM sDMAmode     AS SHARED INTEGER
DIM sDMAclear    AS SHARED INTEGER
DIM sDMAstatus   AS SHARED INTEGER
DIM sDMAterminal AS SHARED INTEGER

$if %Testing
'-------------------------------------------------------------------------
'----------------------- test code ---------------------------------------
'-------------------------------------------------------------------------
DIM F AS STRING                       ' just using these 2 for the test
DIM X AS INTEGER                      '
                                      '
CLS                                   '
IF %Testing THEN                      '
    F$ = "\SBPLAY\FANFARE2.WAV"       '
  ELSE                                '
    F$ = COMMAND$                     '
END IF                                '
                                      '
IF LEN(DIR$(F$)) = 0 THEN             '
  PRINT "GOTTA GIVE ME A FILE NAME."  '
  END                                 '
END IF                                '
                                      '
X% = fSBsetup%                        '
IF X% = -2 THEN                       ' this is called once to set
    PRINT "something went wrong!"     ' shared parameters, etc.
    BEEP                              '
    END                               '
  ELSEif X% = 0 THEN                  ' no BLASTER in environment
    PRINT "default setup used"        '
END IF                                '
PRINT "Smack a key to quit."          '
                                      '
fSBplay F$, 0                         '
fSBplay F$, 0                         '
                                      '
END                                   ' end of program
'-------------------------------------------------------------------------
'----------------------- end of test -------------------------------------
'-------------------------------------------------------------------------
$endif
FUNCTION fSBsetup () LOCAL PUBLIC AS INTEGER

  DIM BasePort AS LOCAL INTEGER
  DIM Blaster  AS LOCAL STRING
  DIM I        AS LOCAL INTEGER
  DIM Ok       AS LOCAL INTEGER
  DIM P        AS LOCAL INTEGER

  BasePort% = &h220                ' default
  sDMA%     = &h001                ' setup
  Blaster$  = ENVIRON$("BLASTER")

  IF LEN(Blaster$) > 0 THEN
    P% = INSTR( Blaster$, "A" )
    IF P% > 0 THEN BasePort% = VAL("&h" + MID$(Blaster$, P%+1, 3))
    P% = INSTR( Blaster$, "D" )
    IF P% > 0 THEN sDMA% = ASC( Blaster$, P%+1 ) - 48
    Ok% = 1
  END IF

  SELECT CASE sDMA%
    CASE 0      : sPagePort% = &h87
                  sAddrPort% = &h0
                  sLenPort%  = &h1
    CASE 1      : sPagePort% = &h83
                  sAddrPort% = &h2
                  sLenPort%  = &h3
    CASE 2      : sPagePort% = &h81
                  sAddrPort% = &h4
                  sLenPort%  = &h5
    CASE 3      : sPagePort% = &h82
                  sAddrPort% = &h6
                  sLenPort%  = &h7
    CASE 4      : sPagePort% = &h8F
                  sAddrPort% = &hC0
                  sLenPort%  = &hC2
    CASE 5      : sPagePort% = &h8B
                  sAddrPort% = &hC4
                  sLenPort%  = &hC6
    CASE 6      : sPagePort% = &h89
                  sAddrPort% = &hC8
                  sLenPort%  = &hCA
    CASE ELSE   : FUNCTION = -2
                  EXIT FUNCTION
  END SELECT

  SELECT CASE sDMA%
    CASE 0 TO 3 : sDMAmask%   = &H0A
                  sDMAmode%   = &H0B
                  sDMAclear%  = &H0C
                  sDMAstatus% = &H08
    CASE 4 TO 7 : sDMAmask%   = &HD4
                  sDMAmode%   = &HD6
                  sDMAclear%  = &HD8
                  sDMAstatus% = &HD0
  END SELECT

  SELECT CASE sDMA%
    CASE 0, 4   : sDMAterminal% = 1   'bit 0 of status register (&H08 or &HD0)
    CASE 1, 5   : sDMAterminal% = 2   'bit 1
    CASE 2, 6   : sDMAterminal% = 4   'bit 2
    CASE 3, 7   : sDMAterminal% = 8   'bit 3
  END SELECT

  sDRQ%        = sDMA%     + &h04
  sModeReg%    = sDMA%     + &h48
  sVolumePort% = BasePort% + &h04
  sVolumeData% = BasePort% + &h05
  sResetPort%  = BasePort% + &h06
  sReadPort%   = BasePort% + &h0A
  sWritePort%  = BasePort% + &h0C
  sWaitPort%   = BasePort% + &h0E

  SBreset
  IF fSBvolumeGet?( %MstrVolume ) = 0 THEN SBvolumeSet %MstrVolume, &hCC

  FUNCTION = Ok%

END FUNCTION

'-------------------------------------------------------------------------

FUNCTION fSBplay (BYVAL FileSpec AS STRING, _
                  BYVAL FileNo   AS INTEGER ) LOCAL PUBLIC AS INTEGER

  DIM  Address      AS LOCAL LONG
  DIM  Addr(1,2)    AS LOCAL BYTE
  DIM  B            AS LOCAL INTEGER
  DIM  Buf(1,15999) AS LOCAL INTEGER
  DIM  B_ptr(1)     AS LOCAL STRING PTR * 32000
  DIM  C            AS LOCAL INTEGER
  DIM  Chunk        AS LOCAL INTEGER
  DIM  ChunkHi      AS LOCAL BYTE
  DIM  ChunkLo      AS LOCAL BYTE
  DIM  LocalOpen    AS LOCAL INTEGER
  DIM tW            AS LOCAL WaveHeaderTYPE

  ON LOCAL ERROR GOTO SBplayOOPS

  IF LEN(FileSpec$) > 0 THEN                       'if a file name is given
    IF LEN(DIR$(FileSpec$)) = 0 THEN ERROR 53      ' check for existence
    IF FileNo% = 0 THEN FileNo% = FREEFILE         'no file number given
    LocalOpen% = -1                                ' set local open flag
    OPEN "B", #FileNo%, FileSpec$                  ' open the file
  END IF                                           '
  GET #FileNo%, ,tW

  IF UCASE$(tW.RiffID) <> "RIFF" THEN GOTO SBplayEXIT
  IF UCASE$(tW.WaveID) <> "WAVE" THEN GOTO SBplayEXIT
  IF UCASE$(tW.DataID) <> "DATA" THEN GOTO SBplayEXIT
  IF tW.FormatTag      <> 1      THEN GOTO SBplayEXIT
  IF tW.BitsPer        <> 8      THEN GOTO SBplayEXIT
  IF tW.DataLen         = 0      THEN GOTO SBplayEXIT

  FOR B% = 0 TO 1                            '
    B_ptr(B%) = VARPTR32(Buf%(B%,0))         '
    Address& = (VARSEG(Buf%(B%,0)) * 16) + _ '
                VARPTR(Buf%(B%,0))           '
    FOR C% = 0 TO 2                          '
      Addr?(B%,C%) = ( Address& AND &h00FF ) '
      SHIFT RIGHT Address&, 8                '
    NEXT                                     '
  NEXT                                       '
                                             '
  C% = (1000000 \ ( tW.Freq * tW.Stereo) )   '
  B% = 256 - C%                              '
  SBwrite &h40                               '
  SBwrite B%                                 '
  B%     = 0                                 '
  Chunk% = UBOUND( Buf%(2) ) * 2             '
  IF tW.Stereo = 2 THEN                      '
    OUT sVolumePort%, &h0E                   '
    OUT sVolumeData%, &h21                   '
  END IF                                     '
  IF tW.Freq > 22000 THEN                    '
      tW.Freq = &h48                         '
    ELSE                                     '
      tW.Freq = &h14                         '
  END IF                                     '
  GOSUB SBloadBuffer                         '
  SBwrite %SpeakerON                         '
                                             '
  DO                                         '
    OUT sDMAmask% , sDRQ%                    'mask the dma channel
    OUT sDMAclear%, 0                        '(clear internal DMA flip/flop)
    OUT sDMAmode% , sModeReg%                '
    OUT sAddrPort%, Addr?(B%,0)              'bits 00-07 of the 20bit address
    OUT sAddrPort%, Addr?(B%,1)              'bits 08-15 of the 20bit address
    OUT sPagePort%, Addr?(B%,2)              'bits 16-19 of the 20bit address
    OUT sLenPort% , ChunkLo?                 'bits 00-07 of size%
    OUT sLenPort% , ChunkHi?                 'bits 08-15 of size%
    OUT sDMAmask% , sDMA%                    'enable channel
    SBwrite tW.Freq                          '
    SBwrite ChunkLo?                         '
    SBwrite ChunkHi?                         '
    IF tW.Freq = &h48 THEN SBwrite &h91      '
                                             '
    C% = INP(sDMAstatus%)                    'Read status byte once to make sure DMA is going
    IF Chunk% > 0 THEN GOSUB SBloadBuffer    '
    WAIT sDMAstatus%, sDMAterminal%          'Loop until terminal count bit set in DMA status register
    C% = INP(sWaitPort%)                     'DSP Available address
    $if %Testing                             '
      IF INSTAT THEN EXIT LOOP               '
    $endif                                   '
  LOOP UNTIL Chunk% = 0                      '
                                             '
  FUNCTION = -1                              '  RETURN
  '------------------------------------------'
                                             '
  SBplayExit:                                '
    SBwrite %SpeakerOFF                      '
    IF LocalOpen%           AND _            '
       FILEATTR(FileNo%,0) THEN              '
      CLOSE #FileNo%                         '
    END IF                                   '
    EXIT FUNCTION                            '
                                             '
  SBloadBuffer:                              '
    BIT TOGGLE B%, 0                         '
    IF tW.DataLen < Chunk% THEN              '
      Chunk% = tW.DataLen                    '
      IF Chunk% = 0 THEN RETURN              '
    END IF                                   '
    GET #FileNo%, , @B_ptr(B%)               '
    DECR tW.DataLen, Chunk%                  '
    ChunkLo? = MAX%(0,(Chunk% AND &hFF) - 1) '
    ChunkHi? = (Chunk% \ &h100)              '
  RETURN                                     '
                                             '
  SBplayOOPS:                                '
    RESUME SBplayExit                        '
                                             '
END FUNCTION                                 '
                                             '
' --------------------------------------------------------------------------

SUB SBvolumeSet (BYVAL Which  AS INTEGER, _
                 BYVAL Volume AS BYTE     ) LOCAL PUBLIC

  OUT sVolumePort%, Which%
  OUT sVolumeData%, Volume?

END SUB

' --------------------------------------------------------------------------

FUNCTION fSBvolumeGet (BYVAL Which AS INTEGER) LOCAL PUBLIC AS BYTE

  OUT sVolumePort%, Which%

  FUNCTION = INP(sVolumeData%)


END FUNCTION

'---------------------------------------------------------------------------
'-------------- private routines -------------------------------------------
'---------------------------------------------------------------------------

SUB SBwrite (BYVAL Cmd AS BYTE) LOCAL PRIVATE

  WAIT sWritePort%, &h80, &h80
  OUT  sWritePort%, Cmd?

END SUB

' --------------------------------------------------------------------------

FUNCTION fSBread () LOCAL PRIVATE AS BYTE

  DIM ByteIn AS LOCAL BYTE

  WAIT sWaitPort%, &h80              'wait for bit 7 on pollport
  DO
    ByteIn? = INP(sReadPort%)
  LOOP UNTIL ByteIn? <> &hAA

  FUNCTION = ByteIn?

END FUNCTION

' --------------------------------------------------------------------------

SUB SBreset () LOCAL PRIVATE

  OUT sResetPort%, 1
  DELAY .1
  OUT sResetPort%, 0
  IF INP(sReadPort%) <> &hAA THEN WAIT sWaitPort%, &h80

END SUB

