Buffered audio streaming via MMIO/PB and WinMM/DirX Routines

Developed or developing a new product in PureBasic? Tell the world about it.
inc.
Enthusiast
Enthusiast
Posts: 406
Joined: Thu May 06, 2004 4:28 pm
Location: Cologne/GER

Buffered audio streaming via MMIO/PB and WinMM/DirX Routines

Post by inc. »

An example of streaming PCM Wave data to WaveOut
The MMIO routines are just used for reading the information and the uncompressed audiodata out of a wave file. If you have other routines (DirectShow, Avisynth, Libavcodec, etc.) for decompressing to obtain uncompressed PCM audiodata from a given file (ac3, mp3, mp4, etc.) you can just replace the MMIO routines with the needed specific ones.

EDIT: Codeupdate. Now Refillingrequest is watched by a PB Windowcallback- makes it independand from user Windowaccess.

Code: Select all

; Mini PB example for audio streaming using MMIO/WINMM routines, 2006 by Inc.
; Based on an MMIO example from "API-Guide" at All-API(dot)net.

; EnableExplicit

#NUM_BUFFERS = 5;8 ; 8 should be enough.
#BUFFER_SECONDS = 0.1;0.5 ; the bigger the buffer, the higher the latency; the lower the buffer the bigger the risk of stuttering.

Structure WAVEFORMATEX Extends WAVEFORMAT
  cbSize.w   
EndStructure

Global rc.l                     ; Return code
Global hmmioIn.l                ; file handle
Global DataOffset.l             ; start of audio data in wave file
Global audioLength.l            ; number of bytes in audio data
Global startPos.l               ; sample where we started playback from
Global Format.WAVEFORMATEX      ; waveformat structure
Global Dim hmem.l(#NUM_BUFFERS-1)          ; memory handles
Global Dim pmem.l(#NUM_BUFFERS-1)          ; memory pointers
Global Dim hdr.WAVEHDR(#NUM_BUFFERS-1)     ; wave headers
Global bufferSize.l             ; size of output buffers
Global fPlaying.b               ; is file currently playing
Global fFileOpen.b              ; is file currently open
Global hWaveOut.l               ; waveout handle
Global msg.s=Space(250)         ; message buffer
Global hwnd.l                   ; window handle


Procedure Position()
  Protected tm.MMTIME, Position.l
  tm\wType = #TIME_BYTES
  rc = waveOutGetPosition_(hWaveOut, @tm, SizeOf(MMTIME))
  If rc = #MMSYSERR_NOERROR
    Position = (startPos + tm\u\cb) / Format\nBlockAlign
  Else
    Position = (mmioSeek_(hmmioIn, 0, #SEEK_CUR) - DataOffset + bufferSize * #NUM_BUFFERS) / Format\nBlockAlign
  EndIf
  ProcedureReturn Position
EndProcedure

Procedure ServiceBuffers(*wavhdr.WAVEHDR)
  Protected dataRemaining.l, i.l
  If fPlaying = #True
    Debug "refilling buffers ... at second "+Str(Position()/Format\nSamplesPerSec)
    dataRemaining = (DataOffset + audioLength - mmioSeek_(hmmioIn, 0, #SEEK_CUR))
    If bufferSize < dataRemaining
      rc = mmioRead_(hmmioIn, *wavhdr\lpData, bufferSize)
    Else
      rc = mmioRead_(hmmioIn, *wavhdr\lpData, dataRemaining)
      fPlaying = #False
      Debug "... rest remaining data: "+Str(dataRemaining)
    EndIf
    *wavhdr\dwBufferLength = rc
    rc = waveOutWrite_(hWaveOut, *wavhdr, SizeOf(WAVEHDR))
  Else
    Debug "... finishing buffering ... at second "+Str(Position()/Format\nSamplesPerSec)
    For i = 0 To #NUM_BUFFERS-1
      waveOutUnprepareHeader_( hWaveOut, @hdr(i), SizeOf(WAVEHDR))
    Next
    waveOutClose_( hWaveOut)
  EndIf
EndProcedure

Procedure CloseTheFile()
  Protected i.l
  For i = 0 To (#NUM_BUFFERS-1)
    GlobalFree_(hmem(i))
    ;Debug "Memory "+Str(i)+" allocated at "+Str(pmem(i))+ " released"
  Next
  If fPlaying = #True
    For i = 0 To #NUM_BUFFERS-1
      waveOutUnprepareHeader_( hWaveOut, @hdr(i), SizeOf(WAVEHDR))
    Next
    waveOutClose_( hWaveOut)
  EndIf
  mmioClose_(hmmioIn, 0)
  fFileOpen = #False
EndProcedure

Procedure OpenTheFile(soundfile.s, WinNo.l)
  Protected total.f, i.l, mmckinfoParentIn.MMCKINFO, mmckinfoSubchunkIn.MMCKINFO, mmioinf.MMIOINFO
  hwnd = WindowID(WinNo)
  fPlaying = #False
  startPos = 0
  
  ; checking if still playing
  If fPlaying
    fPlaying = #False
  EndIf
  ; close previously open file (if any)
  If fFileOpen = #True
    CloseTheFile()
  EndIf 
  If soundfile = "" 
    Debug "File not found"
    ProcedureReturn #False
  EndIf
  
  ; Open the input file
  hmmioIn = mmioOpen_(@soundfile, @mmioinf, #MMIO_READ)
  If hmmioIn = 0
    MessageRequester("Error", "Error opening input File, rc = "+PeekS(@mmioinf\wErrorRet))
    ProcedureReturn #False
  EndIf
  
  ; Check if this is a wave file
  mmckinfoParentIn\fccType = mmioStringToFOURCC_("WAVE", 0)
  rc = mmioDescend_(hmmioIn, @mmckinfoParentIn, 0, #MMIO_FINDRIFF)
  If rc <> #MMSYSERR_NOERROR
    CloseTheFile()
    MessageRequester("Error", "Not a wave File")
    ProcedureReturn #False
  EndIf
  
  ; Get format info
  mmckinfoSubchunkIn\ckid = mmioStringToFOURCC_("fmt", 0)
  rc = mmioDescend_(hmmioIn, @mmckinfoSubchunkIn, @mmckinfoParentIn, #MMIO_FINDCHUNK)
  If rc <> #MMSYSERR_NOERROR 
    CloseTheFile()
    MessageRequester("Error", "Couldn;t get format chunk")
    ProcedureReturn #False
  EndIf
  rc = mmioRead_(hmmioIn, @Format, mmckinfoSubchunkIn\ckSize)
  If rc = -1
    CloseTheFile()
    MessageRequester("Error", "Error reading format")
    ProcedureReturn #False
  EndIf
  Debug " "
  Debug "FormatTag: "+Str(Format\wFormatTag)
  Debug "Channels: "+Str(Format\nChannels)
  Debug "SamplesPerSec: "+Str(Format\nSamplesPerSec)+" Hz"
  Debug "AvgBytesPerSec: "+Str(Format\nAvgBytesPerSec)
  Debug "BlockAlign: "+Str(Format\nBlockAlign)+" bytes"
  Debug "Resolution: "+Str(Format\cbSize)+" bits"
  Debug " "
  
  rc = mmioAscend_(hmmioIn, @mmckinfoSubchunkIn, 0)
  
  ; Find the data subchunk
  mmckinfoSubchunkIn\ckid = mmioStringToFOURCC_("data", 0)
  rc = mmioDescend_(hmmioIn, @mmckinfoSubchunkIn, @mmckinfoParentIn, #MMIO_FINDCHUNK)
  If rc <> #MMSYSERR_NOERROR
    CloseTheFile()
    MessageRequester("Error", "Couldn't get data chunk")
    ProcedureReturn #False
  EndIf
  DataOffset = mmioSeek_(hmmioIn, 0, #SEEK_CUR)
  
  ; Get the length of the audio
  audioLength = mmckinfoSubchunkIn\ckSize
  
  ; Allocate audio buffers
  bufferSize = Format\nSamplesPerSec * Format\nBlockAlign * Format\nChannels * #BUFFER_SECONDS
  bufferSize = bufferSize - (bufferSize % Format\nBlockAlign)
  For i = 0 To (#NUM_BUFFERS-1)
    GlobalFree_(hmem(i))
    hmem(i) = GlobalAlloc_(#GMEM_ZEROINIT|#GMEM_MOVEABLE, bufferSize)
    pmem(i) = GlobalLock_(hmem(i))
    ;Debug Str(bufferSize/1024)+"kb allocated at Memory "+Str(i)+" at adress "+Str(pmem(i))
    total+(bufferSize/1024/1024)
  Next
  Debug StrF(total,3)+" MBs in total for "+Str(#NUM_BUFFERS)+" Buffers used"
  Debug " "
  fFileOpen = #True
  ProcedureReturn #True
EndProcedure

Procedure Play()
  Protected i.l
  If fPlaying
    ProcedureReturn #True
  EndIf
  rc = waveOutOpen_(@hWaveOut, #WAVE_MAPPER, @Format, hwnd, #Null, #CALLBACK_WINDOW)
  If rc <> #MMSYSERR_NOERROR 
    waveOutGetErrorText_(rc, @msg, Len(msg))
    Debug msg
    ProcedureReturn #False
  EndIf
  
  For i = 0 To #NUM_BUFFERS-1
    hdr(i)\lpData = pmem(i)
    hdr(i)\dwBufferLength = bufferSize
    hdr(i)\dwFlags = 0
    hdr(i)\dwLoops = 0
    rc = waveOutPrepareHeader_(hWaveOut, @hdr(i), SizeOf(WAVEHDR))
    If rc <> #MMSYSERR_NOERROR
      waveOutGetErrorText_(rc, @msg, Len(msg))
      Debug msg
      ProcedureReturn #False
    EndIf
  Next
  
  fPlaying = #True
  startPos = mmioSeek_(hmmioIn, 0, #SEEK_CUR) - DataOffset
  
  ; send a MM_WOM_DONE message to the callback so the buffers get filled and playback starts
  For i = 0 To #NUM_BUFFERS-1
    PostMessage_(hwnd, #MM_WOM_DONE, 0, @hdr(i))
  Next
  
  ProcedureReturn #True
EndProcedure

Procedure FileSeekTo(Position.l)
  Protected bytepos.l
  bytepos = Position * Format\nBlockAlign
  If fFileOpen = #False Or bytepos <0>= audioLength
    ProcedureReturn #False
  EndIf
  rc = mmioSeek_(hmmioIn, bytepos + DataOffset, #SEEK_SET)
  If rc = #MMSYSERR_NOERROR
    startPos = rc
    ProcedureReturn #True
  EndIf
EndProcedure

Procedure StopPlay()
  fPlaying = #False
  FileSeekTo(Position())
  waveOutReset_( hWaveOut)
EndProcedure

Procedure pause()
  If fPlaying
    fPlaying = #False
    FileSeekTo(Position())
    waveOutPause_(hWaveOut)
  Else
    fPlaying = #True
    WaveOutRestart_(hWaveOut)
  EndIf
EndProcedure

Procedure.f Length() ; ... in Audioblocks!
  ProcedureReturn audioLength / Format\nBlockAlign
EndProcedure

Procedure Playing()
  Protected tm.MMTIME
  tm\wType = #TIME_BYTES
  rc = waveOutGetPosition_(hWaveOut, @tm, SizeOf(MMTIME))
  If rc = #MMSYSERR_NOERROR
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

Procedure WaveCallback(WindowID, message, wParam, lParam)
  Protected result = #PB_ProcessPureBasicEvents
  If message = #MM_WOM_DONE ; Buffers need to be refilled?
    ServiceBuffers(lParam)
  EndIf
  ProcedureReturn result
EndProcedure 

;DisableExplicit

;-------------------------------------  Example --------------------------------

CompilerIf #PB_Compiler_Debugger = #False
  MessageRequester("Info","Please run this example using the debugger and see its output while choosing a media file")
  End
CompilerEndIf

OpenWindow(0,20,20,400,100,"PB audio streaming example using MMIO/WINMM routines.", #PB_Window_ScreenCentered|#PB_Window_SystemMenu )   
SetWindowCallback(@WaveCallback())
File.s = OpenFileRequester("Choose a PCM Wave file","","Wave files (*.wav)|*.wav",0)
If File
  If OpenTheFile(File,0)
    Debug "Lenght: "+StrF(Length()/Format\nSamplesPerSec,3)+" sec"
    Debug""
    If Play()
      
      Repeat
        event = WaitWindowEvent()
        lprm  = EventlParam()
        wprm  = EventwParam()
        
        If event = #WM_KEYDOWN
          
          If wprm = #VK_ESCAPE ; Quit appl.
            StopPlay()
            CloseTheFile()
            End
          ElseIf wprm = #VK_LEFT ; Seeking backwards in 5% steps
            temp = fPlaying
            pause()
            FileSeekTo(Position()-Int(Length()/20))
            If temp
              Play()
            EndIf
          ElseIf wprm = #VK_RIGHT; Seeking forward in 5% steps
            temp = fPlaying
            pause()
            FileSeekTo(Position()+Int(Length()/20))
            If temp
              Play()
            EndIf
          ElseIf wprm = #VK_P
            Debug Position()
          EndIf
          
        EndIf 
        
      Until event = #PB_Event_CloseWindow
      StopPlay()
      CloseTheFile()
      End 
      
    Else
      CloseTheFile()
      Debug "error playing"
    EndIf 
    End
    
  Else
    Debug "error initializing"
  EndIf 
  
Else
  Debug "error opening"
EndIf
I hope the code isn't messed up by the forum engine while posting cause of its lenght.
Last edited by inc. on Sat Aug 05, 2006 8:44 pm, edited 2 times in total.
Check out OOP support for PB here!
inc.
Enthusiast
Enthusiast
Posts: 406
Joined: Thu May 06, 2004 4:28 pm
Location: Cologne/GER

Post by inc. »

And here is an example almost the same as above but using PBs File() Routines instead of the MMIO ones. The advantage is that largefile support is given. Means, there do exist many Moviesoundtracks in 5.1DolbyDigital @48Khz and when decoded to a 6ch PCM Wave these do often take more than 4GBs, so beside large file support also 6ch audio PCM playback is supported via WaveFormatExtensible.

http://home.arcor.de/ffvfw/PCMwaveStreaming.pb

Forum search keywords:
WAVEFORMATEX
WAVEFORMATEXTENSIBLE
KSDATAFORMAT_SUBTYPE_PCM
6 channel audio
5.1 dolby digital
WAVEFILEHEADER
RIFF
WAVE
Check out OOP support for PB here!
User avatar
yoxola
Enthusiast
Enthusiast
Posts: 386
Joined: Sat Feb 25, 2006 4:23 pm

Post by yoxola »

This is pretty useful, thanks.

Is it possible for a DSound version?
(Maybe it's hard to implement tho..)
KarLKoX
Enthusiast
Enthusiast
Posts: 681
Joined: Mon Oct 06, 2003 7:13 pm
Location: France
Contact:

Post by KarLKoX »

I modified this code from Zapman, it originally allow playing buffer data from memory, i altered the code to stream chunk of data using dsound, added a better (i hope) wav loader, note that it is virtually very simple to add other loader as the playing code need a function pointer wich feel buffer data full of pcm data.
Note that this is old code and was written for pb 3.94, it should be easy to convert it to pb 4 :

Code: Select all

; PlayDirectSound8
;
; Authors: Zapman from a Danilo program "example of playing a sound buffer with DirectX 8 by Danilo in co-operation with www.MasterCreating.de" 
; 
#DD_OK = 0 
#DS_OK = 0 
;
#DSBCAPS_LOCHARDWARE   = $4
#DSBCAPS_LOCSOFTWARE   = $8 
#DSBCAPS_CTRLFREQUENCY = $20 
#DSBCAPS_CTRLVOLUME    = $80 
#DSBCAPS_CTRLPAN       = $40
;
#DSSCL_EXCLUSIVE = 3    ; Accorde l'usage exclusif du periférique de son. Les autres apps sont muettes.
#DSSCL_NORMAL = 1       ; Coopération maximale avec les autres apps. (à ne pas utiliser avec les jeux)
#DSSCL_PRIORITY = 2     ; Permet modifier le format principal (c'est le meilleur mode)
#DSSCL_WRITEPRIMARY = 4 ; Accorde l'accès au tampon principal pour faire du mixage personnalisé (expert)
;
#DSBPLAY_LOOPING = $1
#DSBLOCK_ENTIREBUFFER = $2
;
#DSBCAPS_GLOBALFOCUS    = $8000
;
#DSOUND_BUFFERSIZE     = $10000
#DSOUND_MAXFILLAMOUNT  = 4096
#DSOUND_NUMBUFFERS     = 8

Global m_pUserCallback.l
Global tmpBuf.l
Global m_dwTheadID.l, m_bThreadRunning.l
Global m_writePos.l
Global m_isPlaying.l

Global Sound_Frequency ; to give to the main application the frequency of the opened sound
;
; Structure WAVEFORMAT
  ; wfBlockType.l
  ; wfBlockSize.l
  ; wFormatTag.w
  ; nChannels.w
  ; nSamplesPerSec.l
  ; nAvgBytesPerSec.l
  ; nBlockAlign.w
  ; wBitsPerSample.w
; EndStructure 
;
Structure WAVEFORMATEX 
  wFormatTag.w        ; Waveform-audio format type. A complete list of format tags can be found in the Mmreg.h header file. For one- Or two-channel PCM data, this value should be WAVE_FORMAT_PCM. 
  nChannels.w         ; Number of channels in the waveform-audio data. Monaural data uses one channel and stereo data uses two channels. 
  nSamplesPerSec.l    ; Sample rate, in samples per second (hertz). If wFormatTag is WAVE_FORMAT_PCM, then common values for nSamplesPerSec are 8.0 kHz, 11.025 kHz, 22.05 kHz, and 44.1 kHz. For non-PCM formats, this member must be computed according to the manufacturer's specification of the format tag. 
  nAvgBytesPerSec.l   ; Required average data-transfer rate, in bytes per second, for the format tag. If wFormatTag is WAVE_FORMAT_PCM, nAvgBytesPerSec should be equal to the product of nSamplesPerSec and nBlockAlign. For non-PCM formats, this member must be computed according to the manufacturer's specification of the format tag. 
  nBlockAlign.w       ; Block alignment, in bytes. The block alignment is the minimum atomic unit of data for the wFormatTag format type. If wFormatTag is WAVE_FORMAT_PCM or WAVE_FORMAT_EXTENSIBLE, nBlockAlign must be equal to the product of nChannels and wBitsPerSample divided by 8 (bits per byte). For non-PCM formats, this member must be computed according to the manufacturer's specification of the format tag.  :Software must process a multiple of nBlockAlign bytes of Data at a time. Data written To And Read from a device must always start at the beginning of a block. For example, it is illegal To start playback of PCM Data in the middle of a sample (that is, on a non-block-aligned boundary). 
  wBitsPerSample.w    ; Bits per sample for the wFormatTag format type. If wFormatTag is WAVE_FORMAT_PCM, then wBitsPerSample should be equal to 8 or 16. For non-PCM formats, this member must be set according to the manufacturer's specification of the format tag. If wFormatTag is WAVE_FORMAT_EXTENSIBLE, this value can be any integer multiple of 8. Some compression schemes cannot define a value for wBitsPerSample, so this member can be zero. 
  cbSize.w            ; Size, in bytes, of extra format information appended to the end of the WAVEFORMATEX structure. This information can be used by non-PCM formats to store extra attributes for the wFormatTag. If no extra information is required by the wFormatTag, this member must be set to zero. For WAVE_FORMAT_PCM formats (and only WAVE_FORMAT_PCM formats), this member is ignored. 
EndStructure 
;
Structure DSBUFFERDESC 
  dwSize.l            ; Size of the Structure 
  dwFlags.l           ; Flags specifying the capabilities of the buffer 
  dwBufferBytes.l     ; Size of the new buffer, in bytes. This value must be 0 when creating a buffer with the DSBCAPS_PRIMARYBUFFER flag. For secondary buffers, the minimum and maximum sizes allowed are specified by DSBSIZE_MIN and DSBSIZE_MAX, defined in Dsound.h. 
  dwReserved.l        ; Must be 0 
  *lpwfxFormat        ; Address of a WAVEFORMATEX or WAVEFORMATEXTENSIBLE structure specifying the waveform format for the buffer. 
  guid3DAlgorithm.GUID; Unique identifier of the two-speaker virtualization algorithm to be used by DirectSound3D hardware emulation. If DSBCAPS_CTRL3D is not set in dwFlags, this member must be GUID_NULL (DS3DALG_DEFAULT). 
EndStructure 
;
Structure DirectSoundAndInterface
  Sound.l
  pDSBPrimary.l
  RA_DirectSound.l
EndStructure
;
Procedure MAKE_DSHRESULT(code)
  ProcedureReturn ( (1 << 31) | ($878 << 16) | (code) ) 
EndProcedure
;
Procedure Delete(*obj.IUnknown) 
  ProcedureReturn *obj\Release() 
EndProcedure 
;
Procedure Error_Msg(String.s) 
  MessageRequester("Error",String.s,0) 
  End 
EndProcedure 
;
Structure bbyte
  contenu.b
EndStructure
;
Procedure.s ReadStringlFM(*pointer,length.l)
  ; by Zapman
  ; (Read string Length from memory)
  ; Lit "Length" caractères en mémoire à partir de "*pointer" et retourne le résultat
  ; sous forme d'une chaine de caractere
  
  ; Read "Length" caracteres from "*pointer" and return the result
  ; as a string.
  
  *bbyte.bbyte = *pointer
  compt.l=0
  s$=""
  While compt<length
    s$=s$+Chr(*bbyte\contenu)
    compt + 1
    *bbyte + 1
  Wend
  ProcedureReturn s$
EndProcedure
;
Procedure UserCallback(*pSoundBuffer.l, bufferLen)
  
  bytesRead = ReadData(*pSoundBuffer, bufferLen) 
  ProcedureReturn bytesRead
  
EndProcedure

Procedure.l FillDX8Sound(*DX8Sound.DirectSoundAndInterface)
 
  If *DX8Sound
    *DSB8.IDirectSoundBuffer8 = *DX8Sound\Sound
  EndIf
  
  Protected playPos, unusedWriteCursor
  Protected writeLen
  Protected p1,p2
  Protected l1,l2
  Protected hRes 
  
  If *DSB8 = 0
    ProcedureReturn 0
  EndIf
  
  DSERR_BUFFERLOST = MAKE_DSHRESULT(150)
  
  hRes = *DSB8\GetCurrentPosition(@playPos, @unusedWriteCursor)
  If (hRes <> #DS_OK) : playPos = 0 : EndIf
  
  If (m_writePos < playPos)
    writeLen = playPos - m_writePos
  Else
    writeLen = #DSOUND_BUFFERSIZE - (m_writePos - playPos)
  EndIf

  While (*DSB8\Lock(m_writePos, writeLen, @p1, @l1, @p2, @l2,0) <> #DS_OK)
    *DSB8\Restore()
    *DSB8\Play(0, 0, #DSBPLAY_LOOPING)
  Wend
    
  If (m_pUserCallback)
    If ((p1) And (l1>0))   : CallFunctionFast(m_pUserCallback, p1, l1) : EndIf
    If ((p2) And (l2>0))   : CallFunctionFast(m_pUserCallback, p2, l2) : EndIf
    ;If ((p1) And (l1>0))   : UserCallback(p1, l1) : EndIf
    ;If ((p2) And (l2>0))   : UserCallback(p2, l2) : EndIf    
  EndIf
  
  *DSB8\UnLock(p1,l1,p2,l2)
  m_writePos = m_writePos + writeLen
  
  If (m_writePos >= #DSOUND_BUFFERSIZE) : m_writePos = m_writePos - #DSOUND_BUFFERSIZE : EndIf
  If (m_bThreadKillRequired) : m_bThreadRunning = #False : EndIf
  
  Debug "m_writePos = " + Str(m_writePos)
  
  ProcedureReturn (m_bThreadKillRequired ! 1)
  
EndProcedure

Procedure FillDX8Sound0(*DX8Sound.DirectSoundAndInterface)
  
  If *DX8Sound
    *DSB8.IDirectSoundBuffer8 = *DX8Sound\Sound
  EndIf
  
    If *DSB8 <> 0
    
    ;Debug "DSPUSHBUFFER"
    DSERR_BUFFERLOST = MAKE_DSHRESULT(150)
    
    hr = *DSB8\GetCurrentPosition(@dwCurrentPlayCursor, @Unused)
    If (hr <> #DS_OK)
      dwCurrentPlayCursor = 0
    EndIf
    If (hr = DSERR_BUFFERLOST)
      *DSB8\Restore()
      *DSB8\GetCurrentPosition(@dwCurrentPlayCursor, @Unused)
    EndIf
    
    If (dwCurrentPlayCursor < m_writePos)
      dwCurrentPlayCursor = dwCurrentPlayCursor + #DSOUND_BUFFERSIZE
    EndIf
    dwAmountToFill = dwCurrentPlayCursor - m_writePos
    If (dwAmountToFill > #DSOUND_MAXFILLAMOUNT)
      dwAmountToFill = #DSOUND_MAXFILLAMOUNT
    EndIf
    
    While (*DSB8\Lock(m_writePos, #DSOUND_MAXFILLAMOUNT, @lpvWrite, @dwLength, @lpvWrite2, @dwLength2,0) <> #DS_OK)
        *DSB8\Restore()
        *DSB8\Play(0, 0, #DSBPLAY_LOOPING)
    Wend
    
    ;If *DSB8\Lock(m_writePos, #DSOUND_MAXFILLAMOUNT, @lpvWrite, @dwLength, @lpvWrite2, @dwLength2, 0) = #DS_OK
    ;   If (hr = DSERR_BUFFERLOST)
    ;      *DSB8\Restore()
    ;      *DSB8\Lock(m_writePos, dwAmountToFill, @lpvWrite, @dwLength, @lpvWrite2, @dwLength2,0 )
    ;  EndIf
      If (dwAmountToFill <= 0)
        ;Debug "ZEROBUFFER" 
        RtlZeroMemory_(lpvWrite, dwLength)
        RtlZeroMemory_(lpvWrite2, dwLength2)
      Else
        ;Debug "FILLBUFFER"          
        bytesRead = UserCallback(tmpBuf, dwLength)
        If (bytesRead And dwLength)
          CopyMemory(tmpBuf, lpvWrite, dwLength)
        EndIf
         If (bytesRead And dwLength2)
          CopyMemory(tmpBuf+dwLength, lpvWrite2, dwLength2)
        EndIf
      EndIf
      *DSB8\UnLock(lpvWrite,dwLength,lpvWrite2,dwLength2)
      m_writePos = (m_writePos + dwLength + dwLength2) % #DSOUND_BUFFERSIZE
    EndIf  
    
  ;EndIf
  
  If (m_bThreadKilled) : m_bThreadRunning = #False : EndIf
  

EndProcedure

Procedure DX8Sound_Thread(Value)
 
  Repeat
    *DX8Sound.DirectSoundAndInterface = Value
   
    If *DX8Sound <> 0
      FillDX8Sound(*DX8Sound)
      ;FillDX8Sound0(*DX8Sound)
    EndIf
    
    Delay(20)
    
  ForEver
  
  ExitThread_(0)
  ProcedureReturn 0
EndProcedure


Procedure InitDX8Sound(*wfx.WAVEFORMATEX)
  ; create DS8 Sound Object
  FResult = 0
  Result.l = CallFunction(0,"DirectSoundCreate8",0,@*RA_DirectSound.IDirectSound8,0)
  If Result.l <> #DD_OK 
    Error_Msg("Can't do DirectSoundCreate8 : " + Str(Result.l)) 
  Else
    ;
    ; Set Coop Level
    
    Result.l = *RA_DirectSound\SetCooperativeLevel(WindowID(),#DSSCL_NORMAL) ; hwnd is the WindowID
    If Result.l <> #DD_OK 
      Error_Msg("Can't Set Coop Level : " + Str(Result.l))
      Delete(*RA_DirectSound)
    Else 
      ;
      ; Setting up Primary Buffer 
      dsbd.DSBUFFERDESC                          ; Set up structure 
      dsbd\dwSize        = SizeOf(DSBUFFERDESC)  ; Save structure size 
      dsbd\dwFlags       = 1                     ; It is the primary Buffer (see DSound.h) 
      dsbd\dwBufferBytes = 0                     ; NULL ? Because primary Buffer must be Null 
      dsbd\lpwfxFormat   = 0                     ; NULL ? ? ? <- ist ein Pointer 
      ;
      Sound_Frequency = *wfx\nSamplesPerSec
      *wfx\cbSize = 0
      ;
      ; secondary Buffer (see DSound.h)
      tmpBuf = AllocateMemory(#DSOUND_BUFFERSIZE<<1)
      
      dsbd\dwFlags              = #DSBCAPS_LOCHARDWARE|#DSBCAPS_CTRLVOLUME|#DSBCAPS_GLOBALFOCUS 
      dsbd\dwBufferBytes  = #DSOUND_BUFFERSIZE
      dsbd\lpwfxFormat      = *wfx
      ;
      ; CREATE Secondary Buffer 
      Result.l = *RA_DirectSound\CreateSoundBuffer(@dsbd,@*pDSB.IDirectSoundBuffer,0) 
      If Result.l <> #DD_OK
        Error_Msg("Can't Set up secondary sound buffer : " + Str(Result))
        Delete(*RA_DirectSound)
        Delete(*pDSBPrimary) 
      EndIf 
      ;
      ; ASK for DirectSoundBuffer8 Interface 
      *DSB8.IDirectSoundBuffer8 = 0 
      *pDSB\QueryInterface(?IID_DirectSoundBuffer8,@*DSB8) 
      Delete(*pDSB) 
      ;
      If *DSB8 = 0 
        Error_Msg("Can't get DirectSoundBuffer8 Interface")
        Delete(*RA_DirectSound)
        Delete(*pDSBPrimary)
      Else
        ; Empty sound buffer
          If *DSB8\Lock(0,0,@lpvWrite,@dwLength,0,0,0) = #DS_OK 
          
            If lpvWrite
              RtlZeroMemory_(@lpvWrite, dwLength)
            EndIf
          
          *DSB8\UnLock(lpvWrite,dwLength,0,0) 
          EndIf
          
          ;
          *DSAI.DirectSoundAndInterface = AllocateMemory(SizeOf(DirectSoundAndInterface))
          *DSAI\Sound = *DSB8
          *DSAI\pDSBPrimary = *pDSBPrimary
          *DSAI\RA_DirectSound = *RA_DirectSound
          FResult = *DSAI
          
          If *DSB8\Play(0,0,#DSBPLAY_LOOPING) = #DS_OK
            m_writePos = 0
            m_isPlaying = 0
            m_pUserCallback = @UserCallback()
            m_dwTheadID = CreateThread(@DX8Sound_Thread(), *DSAI)
            If (m_dwTheadID)
              m_bThreadRunning = #True
            EndIf
            ;PauseThread(m_dwTheadID)
         EndIf
          
          
        ;EndIf
        
      EndIf
    EndIf
  EndIf

ProcedureReturn FResult

EndProcedure
;
Procedure LoadFromMemDX8Sound (*mem)
  ; create DS8 Sound Object
  FResult = 0
  Result.l = CallFunction(0,"DirectSoundCreate8",0,@*RA_DirectSound.IDirectSound8,0)
  If Result.l <> #DD_OK 
    Error_Msg("Can't do DirectSoundCreate8 : " + Str(Result.l)) 
  Else
    ;
    ; Set Coop Level
    
    Result.l = *RA_DirectSound\SetCooperativeLevel(WindowID(),#DSSCL_NORMAL) ; hwnd is the WindowID
    If Result.l <> #DD_OK 
      Error_Msg("Can't Set Coop Level : " + Str(Result.l))
      Delete(*RA_DirectSound)
    Else 
      ;
      ; Setting up Primary Buffer 
      dsbd.DSBUFFERDESC                          ; Set up structure 
      dsbd\dwSize        = SizeOf(DSBUFFERDESC)  ; Save structure size 
      dsbd\dwFlags       = 1                     ; It is the primary Buffer (see DSound.h) 
      dsbd\dwBufferBytes = 0                     ; NULL ? Because primary Buffer must be Null 
      dsbd\lpwfxFormat   = 0                     ; NULL ? ? ? <- ist ein Pointer 
      ;
      Result.l = *RA_DirectSound\CreateSoundBuffer(@dsbd,@*pDSBPrimary.IDirectSoundBuffer,0) 
      If Result.l <> #DD_OK 
        Error_Msg("Can't Set up primary sound buffer : " + Str(Result))
        Delete(*RA_DirectSound)
      Else 
        wfx.WAVEFORMATEX                ; Wave Format Structure
        s$ =  ReadStringlFM(*mem,4)
        If s$<>"RIFF"
          Error_Msg("Unknown format! ('RIFF' not found:"+s$+")")
          ProcedureReturn 0
        EndIf
        TSize.l=PeekL(*mem+4)
        PosInFile = 12
        s$ =  ReadStringlFM((*mem+PosInFile),4)
        vl.l =  PeekL(*mem+PosInFile+4)
        While s$<>"fmt " And PosInFile < TSize
          PosInFile + 8 + vl
          If PosInFile < TSize
            s$ =  ReadStringlFM((*mem+PosInFile),4)
            vl.l =  PeekL(*mem+PosInFile+4)
          EndIf
        Wend
        If s$<>"fmt "
          Error_Msg("Unknown format! ('fmt ' not found: "+s$+")")
          ProcedureReturn 0
        EndIf
        PosInFile + 8
        CopyMemory(*mem+PosInFile,@wfx,16)
        PosInFile + vl
        s$ =  ReadStringlFM((*mem+PosInFile),4)
        vl.l =  PeekL(*mem+PosInFile+4)
        While s$<>"data" And PosInFile < TSize
          PosInFile + 8 + vl
          If PosInFile < TSize
            s$ =  ReadStringlFM((*mem+PosInFile),4)
            vl.l =  PeekL(*mem+PosInFile+4)
          EndIf
        Wend
        If s$<>"data"
          Error_Msg("Unknown format! ('data' not found: "+s$+")")
          ProcedureReturn 0
        EndIf
        Sound_Frequency = wfx\nSamplesPerSec
        wfx\cbSize = vl
        ;
        ; secondary Buffer (see DSound.h) 
        dsbd\dwFlags       = #DSBCAPS_LOCHARDWARE|#DSBCAPS_CTRLVOLUME|#DSBCAPS_CTRLFREQUENCY|#DSBCAPS_CTRLPAN 
        dsbd\dwBufferBytes = vl;10 * wfx\nAvgBytesPerSec ; alloc 10 Seconds 
        dsbd\lpwfxFormat   = @wfx 
        ;
        ; CREATE Secondary Buffer 
        Result.l = *RA_DirectSound\CreateSoundBuffer(@dsbd,@*pDSB.IDirectSoundBuffer,0) 
        If Result.l <> #DD_OK
          Error_Msg("Can't Set up secondary sound buffer : " + Str(Result))
          Delete(*RA_DirectSound)
          Delete(*pDSBPrimary) 
        EndIf 
        ;
        ; ASK for DirectSoundBuffer8 Interface 
        *DSB8.IDirectSoundBuffer8 = 0 
        *pDSB\QueryInterface(?IID_DirectSoundBuffer8,@*DSB8) 
        Delete(*pDSB) 
        ;
        If *DSB8 = 0 
          Error_Msg("Can't get DirectSoundBuffer8 Interface")
          Delete(*RA_DirectSound)
          Delete(*pDSBPrimary)
        Else 
          ;
          If *DSB8\Lock(0,0,@lpvWrite,@dwLength,0,0,#DSBLOCK_ENTIREBUFFER) = #DS_OK 
            
            CopyMemory(*mem+44,lpvWrite,dwLength)
            ;
            ; LOAD SOUND END 
            *DSB8\UnLock(lpvWrite,dwLength,0,0) 
            ;
            *DSAI.DirectSoundAndInterface = AllocateMemory(SizeOf(DirectSoundAndInterface))
            *DSAI\Sound = *DSB8
            *DSAI\pDSBPrimary = *pDSBPrimary
            *DSAI\RA_DirectSound = *RA_DirectSound
            FResult = *DSAI
          EndIf
        EndIf
      EndIf
    EndIf
  EndIf
  ProcedureReturn FResult
EndProcedure
;
Procedure LoadFromFileDX8Sound (Filename$)
  Result = 0
  If Filename$
    FSize = FileSize(Filename$)
    If FSize>0
      If ReadFile(0,Filename$)
        ; header may be corrupt
        If FSize < (8 + 4 + 8 + 14 + 8)
          Debug "HEADER CORRUPT"
          ProcedureReturn Result
        EndIf
      
        temp = ReadLong()
        ; $46464952 = 'RIFF'
        If temp <> $46464952
          Debug "NO RIFF"
          ProcedureReturn Result
        EndIf
      
        temp = ReadLong()
        ; possible incomplete files
        ;If temp <> FSize - 8
        ;  ProcedureReturn Result
        ;EndIf
      
        temp = ReadLong()
        ; $45564157 = 'WAVE'
        If temp <> $45564157
          Debug "NO WAVE"
          ProcedureReturn Result
        EndIf
      
        Debug "WAVE FOUND"
      
        Repeat 
          temp = ReadLong()
          If (SizeOf(temp) <> 4)
            ProcedureReturn Result
          EndIf
          ; success
          If temp = $20746D66
            Break
          EndIf
          ; already got Data chunk but no fmt
          If temp = $61746164
            ProcedureReturn Result
          EndIf
        
          temp = ReadLong()
          If (SizeOf(temp) <> 4)
            ProcedureReturn Result
          EndIf
          If temp & 1
            temp + 1
          EndIf
          If temp = 0 Or (Loc() + temp > FSize - 8)
            ProcedureReturn Result
          EndIf
        ForEver

        curpos = Loc()
        temp = ReadLong()
        If (SizeOf(temp) <> 4)
          ProcedureReturn Result
        EndIf
        If (temp < 14 Or temp > 64*1024)
          ProcedureReturn Result
        EndIf

        ; Wave Format Structure
        ptr.WAVEFORMATEX  
        RtlZeroMemory_(@ptr,SizeOf(WAVEFORMATEX))
        ; read wave header        
        bytesRead = ReadData(ptr, temp)
        If (bytesRead <> temp)
          ProcedureReturn Result
        EndIf
        ; verify
        If ptr\wFormatTag <> #WAVE_FORMAT_PCM
          Debug "IEEE FLOAT ?"
          ProcedureReturn Result
        EndIf
        If (ptr\nChannels > 0 And ptr\nChannels <= 256) And (ptr\nSamplesPerSec >= 100 And ptr\nSamplesPerSec <= 1000000) And ptr\nAvgBytesPerSec > 0 And ptr\nBlockAlign > 0
          Result = 1
        EndIf
        
        bitrate = ptr\nAvgBytesPerSec / 125
        bps     = ptr\wBitsPerSample
        rate    = ptr\nSamplesPerSec
        nch     = ptr\nChannels
        
        ptr\nBlockAlign     = (ptr\wBitsPerSample / 8 * ptr\nChannels)
        ptr\nAvgBytesPerSec = (ptr\nSamplesPerSec * ptr\nBlockAlign)
      
        Debug " bitrate = " + Str(bitrate) + " bps = " + Str(bps) + " rate = " + Str(rate) + " nch = " + Str(nch)
      
        InitDX8Sound(ptr)
        
      EndIf ; if ReadFile
    EndIf ; if FSize > 0
  EndIf ; if filename$
    
    ProcedureReturn Result
    
EndProcedure

Procedure LoadFromFileDX8Sound1 (Filename$)
  Result = 0
  If Filename$
    FSize = FileSize(Filename$)
    If FSize>0
      *mem=AllocateMemory(FSize )
      If ReadFile(0,Filename$)
        RSize = ReadData(*mem,FSize)
        CloseFile(0)
        If RSize = FSize 
          Result = LoadFromMemDX8Sound (*mem)
        EndIf
        FreeMemory(*mem)
      EndIf
    EndIf
  EndIf
  ProcedureReturn Result
EndProcedure
;
Procedure PlayDX8Sound (*DX8Sound.DirectSoundAndInterface,mode)
  If *DX8Sound
    *Sound.IDirectSoundBuffer8 = *DX8Sound\Sound
    *Sound\SetCurrentPosition(0)
    *Sound\Play(0,0,mode)
    ResumeThread(m_dwTheadID)
    Delay(1000)            
  EndIf
EndProcedure
;
Procedure SetFrequencyDX8Sound (*DX8Sound.DirectSoundAndInterface,Frequ)
  If *DX8Sound
    *Sound.IDirectSoundBuffer8 = *DX8Sound\Sound
    *Sound\SetFrequency(Frequ)
  EndIf
EndProcedure
;
Procedure SetPanDX8Sound (*DX8Sound.DirectSoundAndInterface,Pan)
  If *DX8Sound
    *Sound.IDirectSoundBuffer8 = *DX8Sound\Sound
    *Sound\SetPan(Pan)
  EndIf
EndProcedure
;
Procedure SetVolumeDX8Sound (*DX8Sound.DirectSoundAndInterface,volume)
  If *DX8Sound
    *Sound.IDirectSoundBuffer8 = *DX8Sound\Sound
    *Sound\SetVolume(volume)
  EndIf
EndProcedure
;
Procedure StopDX8Sound (*DX8Sound.DirectSoundAndInterface)
  If *DX8Sound
    If m_dwTheadID
      KillThread(m_dwTheadID)
      m_dwTheadID = 0
    EndIf
      
    *Sound.IDirectSoundBuffer8 = *DX8Sound\Sound
    *Sound\Stop ()
  EndIf
EndProcedure
;
Procedure ReleaseDX8Sound (*DX8Sound.DirectSoundAndInterface)
  ; Release/Delete Objects 
  ; (reversed order of creation)
  If *DX8Sound
    *Sound.IDirectSoundBuffer8 = *DX8Sound\Sound
    *Sound\Stop ()
    Delete(*DX8Sound\Sound)
    Delete(*DX8Sound\pDSBPrimary) 
    Delete(*DX8Sound\RA_DirectSound)
    FreeMemory(*DX8Sound)
  EndIf
EndProcedure

Procedure Demo()
  hWnd = OpenWindow(0,0,0,200,200,#PB_Window_SystemMenu|#PB_Window_ScreenCentered,"Sound")
  If hWnd 
    CreateGadgetList(hWnd) 
    TextGadget    (1,10,10,180,15,"Frequency: 44100") 
    TrackBarGadget(2,10,25,180,20,0,441) 
    SetGadgetState(2,441) 
    TextGadget    (3,10,50,180,15,"Pan: 0") 
    TrackBarGadget(4,10,65,180,20,0,200) 
    SetGadgetState(4,100) 
    TextGadget    (5,10,90,180,15,"Volume: 10000") 
    TrackBarGadget(6,10,105,180,20,0,10000) 
    SetGadgetState(6,10000) 
    ButtonGadget(7, 10, 130, 50, 20, "Play") 
    ButtonGadget(8, 10, 160, 50, 20, "Stop") 
    ;
    If OpenLibrary(0,"DSOUND.DLL") = 0 
      Error_Msg("Can't open direct Sound DLL")
    Else
      Filename$ = OpenFileRequester("","n:\sebekmaj - Turrican II Level 1 (remake).wav",".wav",1)
      ;Filename2$ = OpenFileRequester("","n:\sebekmaj - Turrican II Level 1 (remake) 8khz.wav",".wav",1)
      DX8Sound = LoadFromFileDX8Sound (Filename$)
      ;DX8Sound2 = LoadFromFileDX8Sound (Filename2$)
      ;PlayDX8Sound (DX8Sound,#DSBPLAY_LOOPING) ; #DSBPLAY_LOOPING can be used as second parametre
      If DX8Sound Or DX8Sound2
        Repeat 
          Select WaitWindowEvent() 
            Case #PB_Event_CloseWindow 
              Quit = 1 
            Case #PB_Event_Gadget 
              Select EventGadgetID() 
                Case 2 ; Frequency Control 
                  Frequ = GetGadgetState(2) 
                  SetGadgetText(1,"Frequency: "+Str(Frequ*100)) 
                  SetFrequencyDX8Sound (DX8Sound,Frequ*100)
                Case 4 ; Pan: Left <> Right 
                  Pan = GetGadgetState(4)*100-10000 
                  SetGadgetText(3,"Pan: "+Str(Pan)) 
                  SetPanDX8Sound (DX8Sound,Pan)
                Case 6 ; Volume 
                  Vol = GetGadgetState(6) 
                  SetGadgetText(5,"Volume: "+Str(Vol)) 
                  SetVolumeDX8Sound (DX8Sound,Vol-10000) 
                Case 7 ; Play 
                 PlayDX8Sound (DX8Sound,#DSBPLAY_LOOPING)
                Case 8 ; Stop
                  StopDX8Sound (DX8Sound)
              EndSelect 
          EndSelect 
        Until Quit 
        
        If DX8Sound
          ReleaseDX8Sound (DX8Sound)
        EndIf
        If DX8Sound2
          ReleaseDX8Sound (DX8Sound2)
        EndIf
        CloseLibrary(0)
        CloseFile(0)
        
      EndIf
    EndIf
  EndIf
EndProcedure
;
Demo()
;
;End 

DataSection 
IID_DirectSoundBuffer8:  ; DSOUND.h 
Data.l $6825A449 
Data.w $7524,$4D82 
Data.b $92,$0F,$50,$E3,$6A,$B3,$AB,$1E 
EndDataSection

"Qui baise trop bouffe un poil." P. Desproges

http://karlkox.blogspot.com/
inc.
Enthusiast
Enthusiast
Posts: 406
Joined: Thu May 06, 2004 4:28 pm
Location: Cologne/GER

Post by inc. »

yoxola wrote:Is it possible for a DSound version?
(Maybe it's hard to implement tho..)
Here it is:
http://home.arcor.de/ffvfw/ThreadBasedD ... reaming.pb
A CodeExample for buffered DirectSound Audio streaming.
Although its threadbased it runs w/o problems.

Its quickn'dirty so no further commands are added like in my examples above.

Note this DSound based one still only supports 2ch PCM Data!. A support of 6ch 5.1 Audiostreams via WaveFormatExtensible will also follow.
Last edited by inc. on Sat Aug 05, 2006 8:55 pm, edited 1 time in total.
Check out OOP support for PB here!
KarLKoX
Enthusiast
Enthusiast
Posts: 681
Joined: Mon Oct 06, 2003 7:13 pm
Location: France
Contact:

Post by KarLKoX »

;/ /// Threadbased DirectSound Streaming Play by Inc.
;/ /// Main parts do base on a Directsound playback example by Tomijan
;/ /// For PB4
;/ /// Normally you should not use DirectX within threads, but here no problems do occur
;/ /// Please do report any bugs.
This is not true, Microsoft never said that DirectX should never be used withing threads, it the opposite, as the notification are events based, it force the coder to implicitly use threads as events are thread related and should be use in that context.
For a prove about what i say, here is a sampling tutorial from windows experts coders.
Ok they use winmm but the same thing apply to dsound ;)
"Qui baise trop bouffe un poil." P. Desproges

http://karlkox.blogspot.com/
inc.
Enthusiast
Enthusiast
Posts: 406
Joined: Thu May 06, 2004 4:28 pm
Location: Cologne/GER

Post by inc. »

KarLKoX wrote:
;/ /// Threadbased DirectSound Streaming Play by Inc.
;/ /// Main parts do base on a Directsound playback example by Tomijan
;/ /// For PB4
;/ /// Normally you should not use DirectX within threads, but here no problems do occur
;/ /// Please do report any bugs.
This is not true, Microsoft never said that DirectX should never be used withing threads, it the opposite, as the notification are events based, it force the coder to implicitly use threads as events are thread related and should be use in that context.
For a prove about what i say, here is a sampling tutorial from windows experts coders.
Ok they use winmm but the same thing apply to dsound ;)
Oh, ok :)
As far as I remember that "DirectX and Threads" subject once it has been mentioned in here.

PS: Most Directsoundstreaming sources in the www dont use DSound routines within threads/events but force events to notify if a DsoundRoutine should be accessed.
Like ...

Code: Select all

hEvent = CreateEvent_(0,#True,0,0)
....

Code: Select all

WaitForSingleObject_(hEvent,#INFINITE)
*Sound\TheDSoundMethod(...)
BTW: Thanks for that link :)

Also an interesting one where a TimerEvent is used:
http://www.codeproject.com/audio/Direct ... Stream.asp
Check out OOP support for PB here!
ricardo
Addict
Addict
Posts: 2438
Joined: Fri Apr 25, 2003 7:06 pm
Location: Argentina

Re: Buffered audio streaming via MMIO/PB and WinMM/DirX Rout

Post by ricardo »

inc. wrote:An example of streaming PCM Wave data to WaveOut
The MMIO routines are just used for reading the information and the uncompressed audiodata out of a wave file. If you have other routines (DirectShow, Avisynth, Libavcodec, etc.) for decompressing to obtain uncompressed PCM audiodata from a given file (ac3, mp3, mp4, etc.) you can just replace the MMIO routines with the needed specific ones.

EDIT: Codeupdate. Now Refillingrequest is watched by a PB Windowcallback- makes it independand from user Windowaccess.
Its only a 'one song' perception or this waveout way sound better than other way to play sounds?
I just test this with one wav file, but gave me the impression to a clearer sound in my edifier speakers.

Im interested on hear another opinions.
Post Reply