News:

Welcome to RetroCoders Community

Main Menu

qbx 7.1 8 bit (16 bit?) wav player code

Started by ron77, Aug 08, 2023, 01:20 PM

Previous topic - Next topic

ron77

you need the qbx lib linked so start the ide by from dos (dosbox) "qbx /l qbx" to start qbx 7.1 for it to work

DECLARE FUNCTION SoundUpdate% ()
DECLARE SUB PlayWav (sFile AS STRING)
DECLARE SUB SoundStop ()
DECLARE SUB LoadWav (sFile AS STRING)
DECLARE SUB ReadBlock (BYVAL iFile AS INTEGER, BYVAL iBlock AS INTEGER)
DECLARE SUB ReadBlock16 (BYVAL iFile AS INTEGER, BYVAL iBlock AS INTEGER)
DECLARE SUB sbDetect ()
DECLARE SUB sbDma8AutoInit (lLength AS LONG)
DECLARE SUB sbFrequency (lFrequency AS LONG)
DECLARE SUB sbIntAck8 ()
DECLARE SUB sbMixerReset ()
DECLARE SUB sbMixerStereo (BYVAL iStereo AS INTEGER)
DECLARE SUB sbReset ()
DECLARE SUB sbSpeakerOn (BYVAL iOn AS INTEGER)
DECLARE SUB sbWait ()
DECLARE SUB SetDma (lSeg AS LONG, lPtr AS LONG, lLength AS LONG)
DECLARE SUB SetDmaNotify (pwSeg AS LONG, pwPtr AS LONG)
DECLARE SUB SoundInit ()
DECLARE FUNCTION u& (iNum AS INTEGER)

DEFSTR A-Z:  DEFDBL D:  DEFINT I:  DEFSNG F: DEFLNG L: DEFSTR S

TYPE WaveFormat
  wTag  AS INTEGER
  wCh   AS INTEGER
  lHz   AS LONG
  lSz   AS LONG
  wAg   AS INTEGER
  wBits AS INTEGER
  iFN   AS INTEGER
  iSeek AS INTEGER
  iLeft AS INTEGER
  lSec  AS LONG
  lPos  AS LONG
END TYPE

CONST SoundBufferSize = 2048 '512,1024,2048 or 4096
CONST SoundBufferSize2 = SoundBufferSize * 2
CONST SoundBufferPages = SoundBufferSize \ 256
TYPE StreamBuffer
  part0 AS STRING * SoundBufferSize
  part1 AS STRING * SoundBufferSize
  part2 AS STRING * SoundBufferSize
  part3 AS STRING * SoundBufferSize
END TYPE

DIM SHARED iSbPort AS INTEGER, iSbIrq AS INTEGER, iSbDma AS INTEGER
DIM SHARED lSndLen AS LONG, lSndFreq AS LONG
DIM SHARED Isr(5)  AS LONG, l16to8(6) AS LONG
DIM SHARED iInit AS INTEGER, iFlag AS INTEGER
DIM SHARED tBuff AS StreamBuffer
DIM SHARED iBasePart AS INTEGER, iBlock AS INTEGER, iBlksLeft  AS INTEGER
DIM SHARED lSeg AS LONG, lPtr AS LONG
DIM SHARED tWave AS WaveFormat

sEndMsg = "Emergency closing?"
KEY(1) ON: ON KEY(1) GOSUB Done

INPUT "select WAV file to be played: "; sFile  '"song3.wav" 'COMMAND$
IF sFile = "" THEN
  sFile = "M1-22-1M.WAV" '"M5-22-2M.WAV"
  PRINT "Syntax: "; "wavplay file.wav";
  'END
  CLS
END IF

SoundInit
PlayWav (sFile)


PRINT STR$(tWave.lHz \ 1000); "khz";
PRINT STR$(tWave.wBits); "bit ";
IF tWave.wCh = 2 THEN PRINT "stereo";  ELSE PRINT "mono";

DO

  IF SoundUpdate <> 1 THEN
    lWavSec = (tWave.lSz + (tWave.lSec - 1) - tWave.lPos) \ tWave.lSec
    LOCATE , 42
    PRINT RIGHT$("0" + MID$(STR$(lWavSec \ 3600), 2), 2); ":";
    PRINT RIGHT$("0" + MID$(STR$((lWavSec \ 60) MOD 60), 2), 2); ":";
    PRINT RIGHT$("0" + MID$(STR$(lWavSec MOD 60), 2), 2);
    PRINT STR$((tWave.lPos * 100&) \ tWave.lSz); "%";
    IF SoundUpdate = 0 THEN sEndMsg = "Done...": EXIT DO
  END IF

  WAIT &H3DA, 8:  WAIT &H3DA, 8, 8
  IF INKEY$ = CHR$(27) THEN
    sEndMsg = "Cancelled!": EXIT DO
  END IF
LOOP

Done:
KEY(1) OFF
SoundStop
IF POS(0) > 1 THEN PRINT
COLOR 7: PRINT sEndMsg;
LOCATE , 1: COLOR 7: PRINT sEndMsg;
sbReset
END

SUB PlayWav (sFile AS STRING)
  iFN = FREEFILE: iSuccess = 0
  ON LOCAL ERROR GOTO failed
  OPEN sFile FOR INPUT AS #iFN
  CLOSE #iFN: iSuccess = 1
failed:
  IF iSuccess = 0 THEN PRINT "Failed to open "; sFile: SLEEP: END
  ON ERROR GOTO 0

  OPEN sFile FOR BINARY ACCESS READ AS #iFN
  sHeader = INPUT$(512, iFN): sHDR = UCASE$(sHeader)
  iPos = 0
  IF LEFT$(sHDR, 4) = "RIFF" THEN
    iPos = INSTR(sHDR, "FMT ")
    IF iPos THEN
      GET #iFN, iPos + 8, tWave
      IF tWave.wCh < 1 OR tWave.wCh > 2 THEN iPos = 0
      IF tWave.lHz < 2000 OR tWave.lHz > 44100 THEN iPos = 0
      IF tWave.wAg <> ((tWave.wCh * tWave.wBits) \ 8) THEN iPos = 0
      IF tWave.lSz <> (tWave.wAg * tWave.lHz) THEN iPos = 0
      IF tWave.wBits < 8 OR tWave.wBits > 16 THEN iPos = 0
    END IF
    IF iPos THEN
      iPos = INSTR(sHDR, "DATA")
      IF iPos THEN iPos = iPos + 8
    END IF

  END IF
  sHeader = "": sHDR = ""
  IF iPos = 0 THEN
    COLOR 12: PRINT "Bad WAV file..."
    COLOR 7: RESET: END
  END IF

  lSndFreq = tWave.lHz * tWave.wCh
  lSndLen = (LOF(iFN) - iPos) \ (tWave.wBits \ 8)
  iSndBlks = (lSndLen + (SoundBufferSize - 1)) \ SoundBufferSize
  tWave.lSz = lSndLen:  tWave.lSec = lSndFreq * (tWave.wBits \ 8)
  tWave.lPos = 0:  tWave.iFN = iFN:  tWave.iLeft = iSndBlks
  tWave.iSeek = iPos

  SEEK #iFN, iPos

  iBlock = 0: iBlksLeft = tWave.iLeft
  IF tWave.wBits = 8 THEN ReadBlock tWave.iFN, iBlock ELSE ReadBlock16 iFN, iBlock
  iBlock = iBlock XOR 1: iBlksLeft = iBlksLeft - 1
  IF iBlksLeft THEN
    IF tWave.wBits = 8 THEN ReadBlock tWave.iFN, iBlock ELSE ReadBlock16 iFN, iBlock
    iBlock = iBlock XOR 1: iBlksLeft = iBlksLeft - 1
  END IF

  IF tWave.wCh = 2 THEN
    sbMixerReset
    sbMixerStereo 1
  END IF
  sbSpeakerOn 1

  iFlag = 0
  SetDmaNotify u(VARSEG(iFlag)), u(VARPTR(iFlag))
  sbFrequency lSndFreq

  sbDma8AutoInit SoundBufferSize
  'lSndLen \ 2

  'iCurBlk = INP(iSbDma + iSbDma + 1) 'we dont care for the low byte
  'iCurBlk = (INP(iSbDma + iSbDma + 1) \ SoundBufferPages)


END SUB

DEFSNG A-E, G-H, J-R, T-Z
SUB ReadBlock (BYVAL iFile AS INTEGER, BYVAL iBlock AS INTEGER)
  SELECT CASE iBasePart + (iBlock AND 1)
  CASE 0
    IF (SEEK(iFile) + SoundBufferSize) > LOF(iFile) THEN tBuff.part0 = STRING$(SoundBufferSize, 128)
    GET #iFile, , tBuff.part0
  CASE 1
    IF (SEEK(iFile) + SoundBufferSize) > LOF(iFile) THEN tBuff.part1 = STRING$(SoundBufferSize, 128)
    GET #iFile, , tBuff.part1
  CASE 2
    IF (SEEK(iFile) + SoundBufferSize) > LOF(iFile) THEN tBuff.part2 = STRING$(SoundBufferSize, 128)
    GET #iFile, , tBuff.part2
  CASE 3
    IF (SEEK(iFile) + SoundBufferSize) >= LOF(iFile) THEN tBuff.part3 = STRING$(SoundBufferSize, 128)
    GET #iFile, , tBuff.part3
  END SELECT
END SUB

DEFLNG L
SUB ReadBlock16 (BYVAL iFile AS INTEGER, BYVAL iBlock AS INTEGER)
  STATIC sBlock AS STRING * SoundBufferSize2
  IF LEN(sBlock) = 0 OR (SEEK(iFile) + SoundBufferSize2) > LOF(iFile) THEN
    sBlock = STRING$(SoundBufferSize2, 0)
  END IF
  GET #iFile, , sBlock

  DEF SEG = VARSEG(sBlock): lpBlk = VARPTR(sBlock)
  FOR iC = 0 TO 2047
    POKE lpBlk + iC, (PEEK(lpBlk + iC + iC + 1) + 128) AND 255
  NEXT iC
  SELECT CASE iBasePart + (iBlock AND 1)
  CASE 0: tBuff.part0 = sBlock
  CASE 1: tBuff.part1 = sBlock
  CASE 2: tBuff.part2 = sBlock
  CASE 3: tBuff.part3 = sBlock
  END SELECT

  'SELECT CASE iBasePart + (iBlock AND 1)
  'CASE 0: CALL absolute(sBlock, tBuff.Part0, VARPTR(l16to8(0)))
  'CASE 1: CALL absolute(sBlock, tBuff.Part1, VARPTR(l16to8(0)))
  'CASE 2: CALL absolute(sBlock, tBuff.Part2, VARPTR(l16to8(0)))
  'CASE 3: CALL absolute(sBlock, tBuff.Part3, VARPTR(l16to8(0)))
  'END SELECT



END SUB

DEFSNG I, L, S
SUB sbDetect

  DIM sBlast AS STRING
  DIM iPosi AS INTEGER
  DIM iChar AS INTEGER

  sBlast = ENVIRON$("BLASTER")

  IF LEN(sBlast) = 0 THEN
    PRINT "BLASTER environment variable not set..."
    PRINT "Assuming A220 I7 D1 T0"
    iSbPort = &H220: iSbIrq = 7: iSbDma = 1
    EXIT SUB
  END IF
  
  DO
    iPosi = INSTR(sBlast, "A")
    IF iPosi = 0 OR iPosi > LEN(sBlast) - 3 THEN iPosi = 0: EXIT DO
    iChar = ASC(MID$(sBlast, iPosi + 1))
    IF iChar < ASC("1") OR iChar > ASC("9") THEN iPosi = 0: EXIT DO
    IF ASC(MID$(sBlast, iPosi + 3)) <> ASC("0") THEN iPosi = 0: EXIT DO
    iSbPort = VAL("&h" + MID$(sBlast, iPosi + 1, 3))

    iPosi = INSTR(sBlast, "I")
    IF iPosi = 0 OR iPosi > LEN(sBlast) - 1 THEN iPosi = 0: EXIT DO
    iChar = ASC(MID$(sBlast, iPosi + 1))
    IF iChar < ASC("1") OR iChar > ASC("9") THEN iPosi = 0: EXIT DO
    iSbIrq = VAL(MID$(sBlast, iPosi + 1, 2))

    iPosi = INSTR(sBlast, "D")
    IF iPosi = 0 OR iPosi > LEN(sBlast) - 1 THEN iPosi = 0: EXIT DO
    iChar = ASC(MID$(sBlast, iPosi + 1))
    IF iChar < ASC("0") OR iChar > ASC("9") THEN iPosi = 0: EXIT DO
    iSbDma = VAL(MID$(sBlast, iPosi + 1, 2))

    EXIT DO
  LOOP

  IF iPosi = 0 THEN
    PRINT "BLASTER environment is INVALID..."
    PRINT "Assuming A220 I7 D1 T0"
    iSbPort = &H220: iSbIrq = 7: iSbDma = 1
    EXIT SUB
  END IF

  PRINT "PORT=" + HEX$(iSbPort) + " IRQ=" + LTRIM$(STR$(iSbIrq)) + " DMA=" + LTRIM$(STR$(iSbDma));

END SUB

SUB sbDma8AutoInit (lLength AS LONG)
  lLen = (lLength - 1)
  sbWait
  OUT iSbPort + &HC, &H48
  sbWait
  OUT iSbPort + &HC, (lLen AND 255)
  sbWait
  OUT iSbPort + &HC, ((lLen \ 256) AND 255)
  sbWait
  OUT iSbPort + &HC, &H1C
END SUB

SUB sbFrequency (lFrequency AS LONG)
 
  lFreq = 65536 - (256000000 \ (1 * lFrequency))
  sbWait
  OUT iSbPort + &HC, &H40
  OUT iSbPort + &HC, (lFreq \ 256) AND 255

END SUB

SUB sbIntAck8
  DIM iResu AS INTEGER
  iResu = INP(iSbPort + &HE)
END SUB

DEFLNG U
SUB sbMixerReset
  sbWait
  OUT iSbPort + &H4, &H0
  sbWait
  OUT iSbPort + &H5, &HFF
END SUB

SUB sbMixerStereo (BYVAL iStereo AS INTEGER)
  : sbWait: OUT iSbPort + &H4, &HE
  IF iStereo THEN
    : sbWait: OUT iSbPort + &H5, &H22
  ELSE
    : sbWait: OUT iSbPort + &H5, &H20
  END IF
END SUB

DEFSNG U
SUB sbOut (BYVAL iSample AS INTEGER)

  OUT iSbPort + &HC, &H10
  OUT iSbPort + &HC, iSample

END SUB

SUB sbReset

 OUT iSbPort + &H6, 1
 WAIT &H3DA, 8
 OUT iSbPort + &H6, 0
 
 DIM sTimeout AS SINGLE
 sTimeout = TIMER
 DO
   IF INP(iSbPort + &HE) THEN
     IF INP(iSbPort + &HA) = &HAA THEN
       iInit = 1: EXIT DO
     END IF
   END IF
   IF ABS(TIMER - sTimeout) > .5 THEN
     iInit = 0: EXIT DO
   END IF
 LOOP

END SUB

SUB sbSpeakerOn (BYVAL iOn AS INTEGER)

  sbWait
  IF iOn THEN
    OUT iSbPort + &HC, &HD1
  ELSE
    OUT iSbPort + &HC, &HD3
  END IF

END SUB

SUB sbWait

  WAIT iSbPort + &HC, 128, 128

END SUB

DEFLNG L
SUB SetDma (lSeg AS LONG, lPtr AS LONG, lLength AS LONG)

  DEF SEG = lSeg
  IF PEEK(lPtr) <> ASC("*") THEN
    PRINT "Wrong buffer location"
  END IF

  lAddr = lSeg * 16& + lPtr
  lPage = lAddr \ 65536
  lAddr = lAddr AND &HFFFF&
  
  lLent = (lLength) - 1

  iSbDma2 = iSbDma + iSbDma
  OUT &HA, 4 + iSbDma
  OUT &HC, 0
  OUT &HB, &H58 + iSbDma
  
  OUT &HC, &H0
  OUT iSbDma2, (lAddr AND 255)
  OUT iSbDma2, ((lAddr \ 256) AND 255)
 
  OUT &HC, &H0
  OUT iSbDma2 + 1, (lLent AND 255)
  OUT iSbDma2 + 1, ((lLent \ 256) AND 255)

  SELECT CASE iSbDma
  CASE 0: OUT &H87, lPage
  CASE 1: OUT &H83, lPage
  CASE 2: OUT &H81, lPage
  CASE 3: OUT &H82, lPage
  END SELECT

  OUT &HA, iSbDma

END SUB

DEFINT I
DEFDBL D
SUB SetDmaNotify (pwSeg AS LONG, pwPtr AS LONG) STATIC
  
  DIM lOldSegPtr AS LONG, lNewIsr AS LONG
  DIM set(9) AS LONG, rst(5) AS LONG
  IntNum = 8 + iSbIrq
  IntMask = CINT(2 ^ (iSbIrq AND 7))
 
  IF pwSeg = 0 AND pwPtr = 0 THEN
    ' *** Restoring Old ISR ***
    IF rst(0) = 0 THEN
      rst(0) = &HEC8B60FA               'cli / pusha / mov bp,sp
      rst(1) = &H145E8B1E               'push ds / mov bx,[bp+14]
      rst(2) = &H25B417C5               'lds dx,[bx] / mov ah,25h
      rst(3) = &H21CD08B0 + iSbIrq * 256 'mov al,0Fh / int 21h
      rst(4) = &H90FB611F               'pop ds / popa
      rst(5) = &H2CA                    'retf 0002h
    END IF

    OUT &H21, INP(&H21) OR IntMask
    IF lOldSegPtr THEN
      DEF SEG = VARSEG(rst(0))
      CALL absolute(lOldSegPtr, VARPTR(rst(0)))
      lOldSegPtr = 0
    END IF

  ELSE

    OUT &H21, INP(&H21) OR IntMask
    IF lOldSegPtr THEN EXIT SUB

    ' *** Create ISR code ***
    Isr(0) = &H681E60FA          'cli / pusha / push ds / ...
    Isr(1) = pwSeg + &H681F0000  'push 0000h / pop ds / ...
    Isr(2) = pwPtr + &HFE5B0000  'push 0000h / pop bx / ...
    Isr(3) = &H1F20B007          'inc [bx] / mov al,20h / pop ds
    Isr(4) = &HFB6120E6          'out 20,al / popa / sti
    Isr(5) = &HCF                'iret
   
    ' *** Set New ISR ***
    IF set(0) = 0 THEN
      set(0) = &H60EC8B55                 'push bp / mov bp,sp / pusha
      set(1) = &H3508B806 + iSbIrq * 65536 'push es / mov ax,350Fh
      set(2) = &HC28C21CD                 'int 21h / mov dx,es
      set(3) = &H8768B07                  'pop es / mov si,[bp+08]
      set(4) = &H1E025489                 'mov [si+02],dx / push ds
      set(5) = &H25B41C89                 'mov [si],bx / mov ah,25
      set(6) = &HFA065E8B                 'mov bx,[bp+06] / cli
      set(7) = &H21CD17C5                 'lds dx,[bx] / int 21h
      set(8) = &H905D611F                 'pop ds / popa / pop bp
      set(9) = &H2CAFB                    'sti / retf 0002h
    END IF
   
    lN = VARSEG(Isr(0)): DEF SEG = VARSEG(set(0))
    IF lN = &H8000 THEN lN = -2147483647 ELSE lN = (lN * 65536) + 1
    lNewIsr = lN + u(VARPTR(Isr(0))) - 1
    CALL absolute(lOldSegPtr, lNewIsr, VARPTR(set(0)))
    OUT &H21, INP(&H21) AND ((NOT IntMask) AND 255)
   
  END IF

END SUB

DEFSTR A-C, E, G-H, J-K, M-Z
SUB SoundInit
  tBuff.part0 = "*": tBuff.part1 = "*": tBuff.part2 = "*": tBuff.part3 = "*"

  'code to convert 16bit samples to 8bit
  l16to8(0) = &HDC8B071E 'push ds | pop es | mov bx,sp
  l16to8(1) = &H8B06778B 'mov si,[bx+06]
  l16to8(2) = &H748B047F 'mov di,[bx+04] | mov si,[si+02]
  l16to8(3) = &H27D8B02  'mov di,[di+02]
  l16to8(4) = &H460000B9 'mov cx,0?00 | inc si | lodsw
  l16to8(5) = &HAA8004AD 'add al,80 | stosb
  l16to8(6) = &H4CAFAE2  'loop #-3 | retf 4
  l16to8(4) = l16to8(4) + (SoundBufferPages * 65536)

  lSeg = u((VARSEG(tBuff.part0)))
  lPtr = u((VARPTR(tBuff.part0)))
  lChk = lSeg * 16 + lPtr
  iBasePart = ((lChk AND &HFFFF) > (65535 - 4096)) AND 2
  IF iBasePart THEN lSeg = lSeg + (SoundBufferSize \ 8)

  sbDetect
  sbReset
  IF iInit = 0 THEN
    PRINT "Failed to init Sound Blaster": END
  END IF
  SetDma lSeg, lPtr, (SoundBufferSize2)

END SUB

SUB SoundStop
  IF tWave.iFN THEN CLOSE #tWave.iFN: tWave.iFN = 0
  IF iInit THEN
    IF iFlag THEN iFlag = 0: sbIntAck8
    SetDmaNotify 0, 0
    sbSpeakerOn 0
    : sbReset: iInit = 0
  END IF
END SUB

FUNCTION SoundUpdate%
  'get playing block
  SoundUpdate% = 0
  IF iInit = 0 THEN EXIT FUNCTION
  'iCurBlk = INP(iSbDma + iSbDma + 1) 'we dont care for the low byte
  'iCurBlk = (INP(iSbDma + iSbDma + 1) \ SoundBufferPages)
  IF iFlag = 0 THEN SoundUpdate% = 1: EXIT FUNCTION
  WHILE iFlag > 0
    iFlag = iFlag - 1: sbIntAck8
    tWave.lPos = tWave.lPos + SoundBufferSize
    IF iBlksLeft < 0 THEN SoundStop: EXIT FUNCTION
    IF tWave.wBits = 8 THEN ReadBlock tWave.iFN, iBlock ELSE ReadBlock16 tWave.iFN, iBlock
    iBlock = iBlock XOR 1: iBlksLeft = iBlksLeft - 1
  WEND
  SoundUpdate% = 2
  
END FUNCTION

DEFLNG U
DEFSNG A-E, G-T, V-Z
FUNCTION u (iNum AS INTEGER)
  uu = CLNG(iNum) AND &HFFFF&
  u = uu
END FUNCTION

here is an explanation how to convert sound/music files to 8 bit wav with ffmpeg: https://techienotes.blog/2010/08/19/convert-cd-quality-wav-to-8bit-mono-wav-using-ffmpeg/

or to simply this command with ffmpeg

ffmpeg -i input.wav -ar 8000 -ac 1 -acodec pcm_u8 output.wav