News:

Welcome to RetroCoders Community

Main Menu

dos Sound/PlayWav

Started by mysoft, Jan 08, 2024, 07:31 PM

Previous topic - Next topic

mysoft

this is a freebasic for dos quick and dirty implementation of qbasic SOUND command (PC speaker)
and LoadWav/FreeWav/DssPlayWav functions to play .wav files over the DSS (mainly dosbox)
i may add another SpkPlayWav to play the .wav over PC speaker... DSS was just simpler to implement without using any interrupt... (i can read the timer registers to get SB/PC Speaker, but it's less stable, since it's not buffered)

sub Sound( fFreq as single , fDuration as single , bKeepOn as byte = 0 )  
  const PitFreq = 1193180
  out &h43, &b10110110 'cnt 2 | LSB+MSB | squarewave | binary
  dim as long iFreq = PitFreq/fFreq  
  if iFreq>65535 then iFreq=65536 else if iFreq<0 then iFreq=0
  iFreq = (iFreq+1) and 65535  
  out &H42, (iFreq and 255)
  out &H42, (iFreq shr 8)    
  out &H61, inp(&H61) or 3
  dim as long iDuration = fDuration*(60/(PitFreq/65536))  
  if fDuration=0 then iDuration=0 else if iDuration < 1 then iDuration = 1
  for iL as long = 0 to iDuration-1
    wait &H3DA, 8: wait &H3DA, 8, 8
  next iL
  if bKeepOn=0 then out &H61, inp(&H61) and (not 1)
end sub

const LptPort = &h378
type WaveFile
  iMagic as long
  iChan  as ushort
  iBits  as ushort
  iFreq  as long
  iSize  as ulong  
end type
function LoadWav( sFile as string ) as WaveFile ptr
  var f = freefile(), iOff=4
  if open(sFile for binary access read as #f) then return 0
  dim as ubyte uHeader(255)
  get #f,,uheader()
  #define u4(_off) *cptr( ulong ptr , @uHeader(_off))
  #define u2(_off) *cptr( ushort ptr , @uHeader(_off))
  #macro LocateChunk( _C4 )
  for iOff = iOff to 255 
    if u4(iOff) = cvl(_C4) then exit for 
  next iOff
  #endmacro
  if u4(0) <> cvl("RIFF") then print "Not a .wav file": return 0
  LocateChunk("WAVE")  
  if iOff >= 255 then print "Not a .wav file": return 0
  LocateChunk("fmt ")  
  if iOff >= 255 orelse u2(iOff+8)<>1 then print "Not a PCM .wav file": return 0
  var iChan = u2(iOff+10) , iFreq = u4(iOff+12) , iBits = u2(iOff+22)
  if iChan<>1 and iChan<>2 then print "Unsupported channel amount": return 0
  if iFreq<3500 or iFreq>48000 then print "Unsupported frequency": return 0
  if iBits<>8 and iBits<>16 then print "Unsupported bits per sample": return 0
  LocateChunk("data")
  if iOff >= 255 then print "no playable data": return 0
  var iSize = u4(iOff+4)   
  dim as WaveFile ptr pWave = allocate(iSize+sizeof(WaveFile)+16)
  with *pWave
    .iMagic = cvl("pWAV")
    .iChan = iChan : .iBits = iBits
    .iFreq = iFreq : .iSize = iSize
  end with
  get #f,iOff+9,*cptr(ubyte ptr,pWave+1),iSize  
  close #f
  return pWave
end function  
sub FreeWav( pWav as WaveFile ptr )
  if pWav=0 then exit sub
  if pWav->iMagic <> cvl("pWAV") then exit sub
  deallocate(pWav)
end sub
sub DssPlayWav( pWav as WaveFile ptr )
  if pWav=0 then exit sub
  var pSample = cptr(ubyte ptr,pWav+1)
  dim as integer iPos,iDelta,iNextChan,iNextSam=1,iAdd=0,iOutSam
  
  with *pWav
    if .iBits = 16 then iNextSam = 2 : iAdd = 128 : iPos += 1
    iNextChan = iNextSam : iNextSam *= .iChan
  
    'turn on
    out LptPort+2,inp(LptPort+2) and (not 8)
    
    'emit samples
    do
      iOutSam += 1 
      if iOutSam=700 then iOutSam=0:if multikey(1) then exit do      
      while (inp(LptPort+1) and &h40): wend    
      'wait LptPort+1,&h40,&h40 'wait for next sample
      if .iChan>1 then
        out LptPort, (cubyte((pSample[iPos]+iAdd))+cubyte((pSample[iPos+iNextChan]+iAdd))) shr 1
      else
        out LptPort, cubyte(pSample[iPos]+iAdd)
      end if      
      out LptPort+2,inp(LptPort+2) or 8
      out LptPort+2,inp(LptPort+2) and (not 8)
      iDelta += .iFreq
      while iDelta>7000 : iDelta -= 7000 : iPos += iNextSam : wend
    loop until iPos >= .iSize
    
    'turn off
    out LptPort+2,inp(LptPort+2) or 8
  end with
  
end sub

for N as long = 37 to 1023
  print !"\r";N;: sound N,.03,1  
next N
sound 0,0

var pWav = LoadWav("sample.wav")
DssPlayWav(pWav)
FreeWav(pWav)

Sound have an optional 3rd parameter that can be used for background play... where it sets the frequency and start play... but does not stop, (useful for bg music handled every game frame))
so
sound 440,0,1
sleep
sound 440,0
will keep playing the 440hz until a key is pressed...
but it's also useful to prevent unecessary stops after sounds get played as the sample on the implementation code shows, (this is also because QBASIC implemented a sound stack... so it playted in background using interrupts, and queded 1 sound statement at least, similar as PLAY in background mode), but this implementation delay is basically synchronous so the only way to have it async is to keep it playing and change the frequency/pauses in your game (even at 30hz is good enough for background play)