Pb WASAPI

Share your advanced PureBasic knowledge/code with the community.
AndyMK
Enthusiast
Enthusiast
Posts: 582
Joined: Wed Jul 12, 2006 4:38 pm
Location: UK

Pb WASAPI

Post by AndyMK »

Hi. This is very basic code to show how you can interface with Windows WASAPI. I chose Event driver Exclusive mode because it's the hardest one to do. Shared mode is easy. This code connects to the default audio capture device and request a 256 sample period. There are many validation checks for various things like samplerate, min/max buffer size supported etc, none of which are included here. Have fun :D

Code: Select all

EnableExplicit

; -----------------------------------------------------------------------------
;  Author: AndyMK
;  Date  : 29/01/2025
;  PB 6.20 Beta 3 (x64)
;  WASAPI Exclusive Capture (Event driven Mode)
; -----------------------------------------------------------------------------
;  COM & WASAPI Constants
; -----------------------------------------------------------------------------

#AUDCLNT_SHAREMODE_EXCLUSIVE= 1
#AUDCLNT_STREAMFLAGS_EVENTCALLBACK = $00040000
#WAVE_FORMAT_PCM = 1
#WAIT_OBJECT_0 = 0
#S_OK   = 0

; -----------------------------------------------------------------------------
;  COM Interfaces
; -----------------------------------------------------------------------------
Interface IMMDeviceEnumerator Extends IUnknown
  EnumAudioEndpoints(dataFlow.l, dwStateMask.l, *ppDevices)
  GetDefaultAudioEndpoint(dataFlow.l, role.l, *ppEndpoint)
  GetDevice(pwstrId, ppDevice)
  RegisterEndpointNotificationCallback(pClient)
  UnregisterEndpointNotificationCallback(pClient)
EndInterface

Interface IMMDevice Extends IUnknown
  Activate(iid.i, dwClsCtx.l, pActivationParams.i, ppInterface.i)
  OpenPropertyStore(stgmAccess.l, ppProperties.i)
  GetId(ppstrId.i)
  GetState(pdwState.i)
EndInterface

Interface IAudioClient Extends IUnknown
  Initialize(ShareMode.l, StreamFlags.l, hnsBufferDuration.q, hnsPeriodicity.q, pFormat.i, pSessionGuid.i)
  GetBufferSize(*pNumBufferFrames)
  GetStreamLatency(*phnsLatency.q)
  GetCurrentPadding(*pNumPaddingFrames)
  IsFormatSupported(ShareMode.l, pFormat.i, ppClosestMatch.i)
  GetMixFormat(*ppDeviceFormat)
  GetDevicePeriod(*phnsDefaultDevicePeriod.q, *phnsMinimumDevicePeriod.q)
  Start()
  Stop()
  Reset()
  SetEventHandle(EventHandle.i)
  GetService(riid.i, *ppv)
EndInterface

Interface IAudioClient2 Extends IAudioClient
  IsOffloadCapable(Category.l, *pbOffloadCapable.l)
  SetClientProperties(*pProperties)  ; AUDIO_CLIENT_PROPERTIES
  GetBufferSizeLimits(pFormat.i, bEventDriven.l, *phnsMinBufferDuration.q, *phnsMaxBufferDuration.q)
EndInterface

Interface IAudioClient3 Extends IAudioClient2
  GetSharedModeEnginePeriod(pFormat.i, *pPeriodInFrames.l, *pFrameSizeInBytes.l)
  GetCurrentSharedModeEnginePeriod(*ppFormat.i, *pCurrentPeriodInFrames.l, *pFrameSizeInBytes.l)
  InitializeSharedAudioStream(StreamFlags.l, PeriodInFrames.l, pFormat.i, AudioSessionGuid.i)
EndInterface

Interface IAudioCaptureClient Extends IUnknown
  GetBuffer(*ppData, *pNumFramesToRead, *pdwFlags, *pu64DevicePosition.q, *pu64QPCPosition.q)
  ReleaseBuffer(NumFramesRead.l)
  GetNextPacketSize(*pNumFramesInNextPacket)
EndInterface

; -----------------------------------------------------------------------------
;  Helper: Fill a WAVEFORMATEX with 16-bit PCM, stereo, at 44100 Hz
; -----------------------------------------------------------------------------
Procedure SetWaveFormatPCM44100Stereo16(*wf.WAVEFORMATEX)
  *wf\wFormatTag       = #WAVE_FORMAT_PCM
  *wf\nChannels        = 2
  *wf\nSamplesPerSec   = 44100
  *wf\wBitsPerSample   = 16
  *wf\nBlockAlign      = *wf\nChannels * (*wf\wBitsPerSample / 8) ; e.g. 2*(16/8) = 4
  *wf\nAvgBytesPerSec  = *wf\nSamplesPerSec * *wf\nBlockAlign      ; e.g. 44100*4=176400
  *wf\cbSize           = 0
EndProcedure

; -----------------------------------------------------------------------------
;  Helper: Convert Frames to Hns
; -----------------------------------------------------------------------------
Procedure.q FramesToHns(frames.l, sampleRate.l)
  ; frames    = number of audio frames
  ; sampleRate= frames per second
  ; returns a 64-bit integer in 100-ns units
  Protected.q hns
  hns = (frames * 10000000) / sampleRate
  ProcedureReturn hns
EndProcedure

; -----------------------------------------------------------------------------
;  Procedure: CaptureMic_CustomFormat_Exclusive()
;    - Fallback: IAudioClient3 -> 2 -> 1
;    - Uses a manually-specified WAVEFORMATEX (44.1k, 16-bit, stereo).
;    - Event-driven capture in exclusive mode.
; -----------------------------------------------------------------------------
Procedure CaptureMic_CustomFormat_Exclusive(durationMs.l)
  Protected hr.l

  Protected deviceEnumerator.IMMDeviceEnumerator
  Protected defaultMic.IMMDevice

  ; Fallback interface pointers
  Protected audioClient1.IAudioClient  = #Null
  Protected audioClient2.IAudioClient2 = #Null
  Protected audioClient3.IAudioClient3 = #Null

  Protected baseClient.IAudioClient    = #Null
  Protected captureClient.IAudioCaptureClient

  Protected waveFmt.WAVEFORMATEX

  Protected bufferFrames.l
  Protected framesAvailable.l
  Protected flags.l
  Protected *data
  Protected startTime.l = ElapsedMilliseconds()

  Protected period.q = FramesToHns(256, 44100)  ; 256 frames means 0.0058049886621315 ms of latency@44100hz (256/44100). Device dependent.
  Protected bufferDuration.q = period  ; must be equal or a multiple in Exclusive mode;

  ; 1) Initialize COM
  hr = CoInitialize_(#Null)
  If hr <> #S_OK
    Debug "CoInitialize_ failed. hr=" + Hex(hr)
    ProcedureReturn
  EndIf

  ; 2) Create device enumerator
  hr = CoCreateInstance_(?uuidof_MMDeviceEnumerator, #Null, #CLSCTX_INPROC_SERVER, ?uuidof_IMMDeviceEnumerator, @deviceEnumerator)
  If hr <> #S_OK
    Debug "CoCreateInstance_(MMDeviceEnumerator) failed. hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  ; 3) Get default capture device
  hr = deviceEnumerator\GetDefaultAudioEndpoint(1, 0, @defaultMic) ; eCapture=1, eConsole=0
  If hr <> #S_OK
    Debug "GetDefaultAudioEndpoint() failed. hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  ; 4) Attempt IAudioClient3 => IAudioClient2 => IAudioClient
  hr = defaultMic\Activate(?uuidof_IAudioClient3, #CLSCTX_INPROC_SERVER, #Null, @audioClient3)
  If hr = #S_OK
    Debug "Activated IAudioClient3"
    baseClient = audioClient3
  Else
    Debug "IAudioClient3 not available hr=" + Hex(hr) + " => trying IAudioClient2..."

    hr = defaultMic\Activate(?uuidof_IAudioClient2, #CLSCTX_INPROC_SERVER, #Null, @audioClient2)
    If hr = #S_OK
      Debug "Activated IAudioClient2"
      baseClient = audioClient2
    Else
      Debug "IAudioClient2 not available hr=" + Hex(hr) + " => trying IAudioClient..."

      hr = defaultMic\Activate(?uuidof_IAudioClient, #CLSCTX_INPROC_SERVER, #Null, @audioClient1)
      If hr = #S_OK
        Debug "Activated IAudioClient (legacy)"
        baseClient = audioClient1
      Else
        Debug "All attempts failed, hr=" + Hex(hr)
        Goto Cleanup
      EndIf
    EndIf
  EndIf

  ; 5) Fill waveFmt with 44100/16-bit/stereo PCM
  SetWaveFormatPCM44100Stereo16(@waveFmt)

  ; 6) Check if this format is supported in exclusive mode
  hr = baseClient\IsFormatSupported(#AUDCLNT_SHAREMODE_EXCLUSIVE, @waveFmt, #Null)
  If hr = #S_OK
    Debug "Format is supported in exclusive mode."
  Else
    Debug "Format is NOT supported (hr=" + Hex(hr) + ") => likely won't Initialize() properly."
    ; Could fallback or bail here
  EndIf

  ; 7) Initialize in exclusive mode + event callback
  hr = baseClient\Initialize(#AUDCLNT_SHAREMODE_EXCLUSIVE, #AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, period, @waveFmt, #Null)
  If hr <> #S_OK
    Debug "Initialize(exclusive) failed, hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  ; 8) Create an event handle
  Protected hEvent.i = CreateEvent_(#Null, 0, 0, #Null)
  If hEvent = 0
    Debug "CreateEvent_ failed"
    Goto Cleanup
  EndIf

  hr = baseClient\SetEventHandle(hEvent)
  If hr <> #S_OK
    Debug "SetEventHandle() failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf

  ; 9) Get the buffer size in frames
  hr = baseClient\GetBufferSize(@bufferFrames)
  If hr <> #S_OK
    Debug "GetBufferSize() failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf
  Debug "BufferFrames = " + Str(bufferFrames) + " at 44100/16/stereo exclusive."

  ; 10) Get IAudioCaptureClient
  hr = baseClient\GetService(?uuidof_IAudioCaptureClient, @captureClient)
  If hr <> #S_OK
    Debug "GetService(IAudioCaptureClient) failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf

  ; 11) Start capturing
  hr = baseClient\Start()
  If hr <> #S_OK
    Debug "Start() failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf

  Debug "Capturing exclusively @ 44.1kHz/16-bit stereo for " + Str(durationMs) + "ms..."

  ; 12) Event-driven capture loop
  While ElapsedMilliseconds() - startTime < durationMs
    Protected waitRes = WaitForSingleObject_(hEvent, 500)
    If waitRes = #WAIT_OBJECT_0
      ; event signaled
      hr = captureClient\GetNextPacketSize(@framesAvailable)
      If hr = #S_OK And framesAvailable > 0
        hr = captureClient\GetBuffer(@*data, @framesAvailable, @flags, #Null, #Null)
        If hr = #S_OK
                   
          ; *data => PCM samples
          ; framesAvailable => how many frames
          ; For demo, we discard them
          ; you would typically setup a callback system here a la Portaudio.
          
          Debug framesAvailable
          
          captureClient\ReleaseBuffer(framesAvailable) ; Release the buffer and move on
        EndIf
      EndIf
    EndIf
  Wend

  baseClient\Stop()
  Debug "Capture finished."

  CloseHandle_(hEvent)

  Cleanup:
    If captureClient
      captureClient\Release()
    EndIf
    If audioClient3
      audioClient3\Release()
    EndIf
    If audioClient2
      audioClient2\Release()
    EndIf
    If audioClient1
      audioClient1\Release()
    EndIf
    If defaultMic
      defaultMic\Release()
    EndIf
    If deviceEnumerator
      deviceEnumerator\Release()
    EndIf

    CoUninitialize_()
EndProcedure


; -----------------------------------------------------------------------------
;  DataSection: All Required GUIDs
; -----------------------------------------------------------------------------
DataSection
  ; -- MMDeviceEnumerator
  uuidof_MMDeviceEnumerator:
  Data.l $BCDE0395
  Data.w $E52F,$467C
  Data.b $8E,$3D,$C4,$57,$92,$91,$69,$2E

  uuidof_IMMDeviceEnumerator:
  Data.l $A95664D2
  Data.w $9614,$4F35
  Data.b $A7,$46,$DE,$8D,$B6,$36,$17,$E6

  ; -- IAudioClient
  uuidof_IAudioClient:
  Data.l $1CB9AD4C
  Data.w $DBFA,$4C32
  Data.b $B1,$78,$C2,$F5,$68,$A7,$03,$B2

  ; -- IAudioClient2
  uuidof_IAudioClient2:
  Data.l $726778CD
  Data.w $F60A,$4EDA
  Data.b $82,$DE,$E4,$76,$10,$CD,$78,$AA

  ; -- IAudioClient3
  uuidof_IAudioClient3:
  Data.l $F4B1A599
  Data.w $7266,$4319
  Data.b $A8,$CA,$E7,$0A,$CB,$11,$E8,$CD

  ; -- IAudioCaptureClient
  uuidof_IAudioCaptureClient:
  Data.l $C8ADBD64
  Data.w $E71E,$48A0
  Data.b $A4,$DE,$18,$5C,$39,$5C,$D3,$17
EndDataSection

; -----------------------------------------------------------------------------
;  Example: Capture for 5 seconds, using 44100/16-bit/stereo in exclusive mode.
; -----------------------------------------------------------------------------
CaptureMic_CustomFormat_Exclusive(5000)
End
Shared Mode Render Example.

Code: Select all

EnableExplicit

; -----------------------------------------------------------------------------
;  Author: AndyMK
;  Date  : 29/01/2025
;  PB 6.20 Beta 3 (x64)
;  WASAPI Shared Render (Event driven Mode)
; -----------------------------------------------------------------------------
;  COM & WASAPI Constants
; -----------------------------------------------------------------------------
#CLSCTX_INPROC_SERVER  = $01
#CLSCTX_INPROC_HANDLER = $02
#CLSCTX_LOCAL_SERVER   = $04
#CLSCTX_REMOTE_SERVER  = $10

#CLSCTX_ALL    = #CLSCTX_INPROC_SERVER | #CLSCTX_INPROC_HANDLER | #CLSCTX_LOCAL_SERVER | #CLSCTX_REMOTE_SERVER
#CLSCTX_INPROC = #CLSCTX_INPROC_SERVER | #CLSCTX_INPROC_HANDLER
#CLSCTX_SERVER = #CLSCTX_INPROC_SERVER | #CLSCTX_LOCAL_SERVER | #CLSCTX_REMOTE_SERVER

#AUDCLNT_SHAREMODE_SHARED          = 0
#AUDCLNT_STREAMFLAGS_EVENTCALLBACK = $00040000
#S_OK  = 0

#WAVE_FORMAT_PCM = 1

#WAIT_OBJECT_0 = 0

; -----------------------------------------------------------------------------
;  COM Interfaces
; -----------------------------------------------------------------------------
Interface IMMDeviceEnumerator Extends IUnknown
  EnumAudioEndpoints(dataFlow.l, dwStateMask.l, *ppDevices)
  GetDefaultAudioEndpoint(dataFlow.l, role.l, *ppEndpoint)
  GetDevice(pwstrId, ppDevice)
  RegisterEndpointNotificationCallback(pClient)
  UnregisterEndpointNotificationCallback(pClient)
EndInterface

Interface IMMDevice Extends IUnknown
  Activate(iid.i, dwClsCtx.l, pActivationParams.i, ppInterface.i)
  OpenPropertyStore(stgmAccess.l, ppProperties.i)
  GetId(ppstrId.i)
  GetState(pdwState.i)
EndInterface

Interface IAudioClient Extends IUnknown
  Initialize(ShareMode.l, StreamFlags.l, hnsBufferDuration.q, hnsPeriodicity.q, pFormat.i, pSessionGuid.i)
  GetBufferSize(*pNumBufferFrames)
  GetStreamLatency(*phnsLatency.q)
  GetCurrentPadding(*pNumPaddingFrames)
  IsFormatSupported(ShareMode.l, pFormat.i, ppClosestMatch.i)
  GetMixFormat(*ppDeviceFormat)
  GetDevicePeriod(*phnsDefaultDevicePeriod.q, *phnsMinimumDevicePeriod.q)
  Start()
  Stop()
  Reset()
  SetEventHandle(EventHandle.i)
  GetService(riid.i, *ppv)
EndInterface

Interface IAudioClient2 Extends IAudioClient
  IsOffloadCapable(Category.l, *pbOffloadCapable.l)
  SetClientProperties(*pProperties)  ; AUDIO_CLIENT_PROPERTIES
  GetBufferSizeLimits(pFormat.i, bEventDriven.l, *phnsMinBufferDuration.q, *phnsMaxBufferDuration.q)
EndInterface

Interface IAudioClient3 Extends IAudioClient2
  GetSharedModeEnginePeriod(pFormat.i, *pPeriodInFrames.l, *pFrameSizeInBytes.l)
  GetCurrentSharedModeEnginePeriod(*ppFormat.i, *pCurrentPeriodInFrames.l, *pFrameSizeInBytes.l)
  InitializeSharedAudioStream(StreamFlags.l, PeriodInFrames.l, pFormat.i, AudioSessionGuid.i)
EndInterface

Interface IAudioRenderClient Extends IUnknown
  GetBuffer(numFramesRequested.l, *ppData)
  ReleaseBuffer(numFramesWritten.l, dwFlags.l)
EndInterface

; -----------------------------------------------------------------------------
;  Helper: Fill a WAVEFORMATEX with 44.1kHz, 16-bit, stereo PCM
; -----------------------------------------------------------------------------
Procedure SetWaveFormatPCM44100Stereo16(*wf.WAVEFORMATEX)
  *wf\wFormatTag       = #WAVE_FORMAT_PCM
  *wf\nChannels        = 2
  *wf\nSamplesPerSec   = 44100
  *wf\wBitsPerSample   = 16
  *wf\nBlockAlign      = *wf\nChannels * (*wf\wBitsPerSample / 8) ; 2*(16/8)=4
  *wf\nAvgBytesPerSec  = *wf\nSamplesPerSec * *wf\nBlockAlign      ; 44100*4=176400
  *wf\cbSize           = 0
EndProcedure

Procedure.q FramesToHns(frames.l, sampleRate.l)
  ; frames    = number of audio frames
  ; sampleRate= frames per second
  ; returns a 64-bit integer in 100-ns units
  Protected.q hns
  hns = (frames * 10000000) / sampleRate
  ProcedureReturn hns
EndProcedure

; -----------------------------------------------------------------------------
;  Procedure: PlaybackSineWave_WithFallback()
;    - Fallback: IAudioClient3 -> 2 -> 1
;    - Plays a simple sine wave for 'durationMs' in SHARED mode, event-driven.
; -----------------------------------------------------------------------------
Procedure PlaybackSineWave_WithFallback(durationMs.l)
  Protected hr.l

  Protected deviceEnumerator.IMMDeviceEnumerator
  Protected defaultRender.IMMDevice

  Protected audioClient1.IAudioClient  = #Null
  Protected audioClient2.IAudioClient2 = #Null
  Protected audioClient3.IAudioClient3 = #Null
  Protected baseClient.IAudioClient    = #Null
  Protected renderClient.IAudioRenderClient

  Protected waveFmt.WAVEFORMATEX
  Protected *mixFormat.WAVEFORMATEX

  Protected bufferFrames.l
  Protected numPadding.l
  Protected numFramesAvailable.l
  Protected *data
  Protected flags.l = 0

  Protected startTime.l = ElapsedMilliseconds()

  ; We do SHARED mode, so typically hnsPeriodicity=0 => OS picks period
  ; bufferDuration ~ 100ms => 1,000,000 in 100ns
  Protected hnsBufferDuration.q = 1000000 ;FramesToHns(441, 44100)

  ; 1) Initialize COM
  hr = CoInitialize_(#Null)
  If hr <> #S_OK
    Debug "CoInitialize_ failed, hr=" + Hex(hr)
    ProcedureReturn
  EndIf

  ; 2) Create device enumerator
  hr = CoCreateInstance_(?uuidof_MMDeviceEnumerator, #Null, #CLSCTX_INPROC_SERVER, ?uuidof_IMMDeviceEnumerator, @deviceEnumerator)
  If hr <> #S_OK
    Debug "CoCreateInstance_(MMDeviceEnumerator) failed, hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  ; 3) Get default RENDER device (speakers/headphones)
  ;    dataFlow=0 => eRender, role=0 => eConsole
  hr = deviceEnumerator\GetDefaultAudioEndpoint(0, 0, @defaultRender)
  If hr <> #S_OK
    Debug "GetDefaultAudioEndpoint(eRender) failed, hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  ; 4) Try IAudioClient3 => fallback => IAudioClient2 => fallback => IAudioClient
  hr = defaultRender\Activate(?uuidof_IAudioClient3, #CLSCTX_INPROC_SERVER, #Null, @audioClient3)
  If hr = #S_OK
    Debug "Activated IAudioClient3"
    baseClient = audioClient3
  Else
    Debug "IAudioClient3 not available hr=" + Hex(hr) + " => trying IAudioClient2..."
    hr = defaultRender\Activate(?uuidof_IAudioClient2, #CLSCTX_INPROC_SERVER, #Null, @audioClient2)
    If hr = #S_OK
      Debug "Activated IAudioClient2"
      baseClient = audioClient2
    Else
      Debug "IAudioClient2 not available hr=" + Hex(hr) + " => trying IAudioClient..."
      hr = defaultRender\Activate(?uuidof_IAudioClient, #CLSCTX_INPROC_SERVER, #Null, @audioClient1)
      If hr = #S_OK
        Debug "Activated IAudioClient (legacy)"
        baseClient = audioClient1
      Else
        Debug "All attempts failed hr=" + Hex(hr)
        Goto Cleanup
      EndIf
    EndIf
  EndIf
    
  ; For SHARED mode, we can either use:
  ;   a) baseClient\GetMixFormat(@*mixFormat), or
  ;   b) set our own waveFmt. 
  ; Typically you'd just do GetMixFormat() and let OS handle conversions.

  ; custom waveFmt for 44.1/16/stereo, and let OS resample if needed.
  SetWaveFormatPCM44100Stereo16(@waveFmt)
  
  ; 5) Initialize in SHARED mode + event callback
  Protected streamFlags.l = #AUDCLNT_STREAMFLAGS_EVENTCALLBACK
  hr = baseClient\Initialize(#AUDCLNT_SHAREMODE_SHARED, streamFlags, hnsBufferDuration, 0, @waveFmt, #Null)
  If hr <> #S_OK
    Debug "Initialize(shared) failed, hr=" + Hex(hr)
    Goto Cleanup
  EndIf

  ; 6) Create an event for event-driven playback
  Protected hEvent.i = CreateEvent_(#Null, 0, 0, #Null)
  If hEvent = 0
    Debug "CreateEvent_ failed"
    Goto Cleanup
  EndIf

  hr = baseClient\SetEventHandle(hEvent)
  If hr <> #S_OK
    Debug "SetEventHandle() failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf

  ; 7) Get buffer size (in frames)
  hr = baseClient\GetBufferSize(@bufferFrames)
  If hr <> #S_OK
    Debug "GetBufferSize() failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf
  Debug "Render BufferFrames = " + Str(bufferFrames)

  ; 8) Get IAudioRenderClient
  hr = baseClient\GetService(?uuidof_IAudioRenderClient, @renderClient)
  If hr <> #S_OK
    Debug "GetService(IAudioRenderClient) failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf

  ; 9) Start the stream
  hr = baseClient\Start()
  If hr <> #S_OK
    Debug "IAudioClient\Start() failed hr=" + Hex(hr)
    CloseHandle_(hEvent)
    Goto Cleanup
  EndIf

  Debug "Playing a sine wave for " + Str(durationMs) + " ms..."

  ; We'll generate and write a 440 Hz sine wave at 44.1 kHz, stereo, 16-bit
  Protected freq.f = 440.0
  Protected twoPi.f = 6.283185307
  Protected t.f = 0.0
  Protected increment.f = freq * twoPi / 44100.0

  Protected currentMs.l
  Protected sampleCount.l
  Protected maxSamples.l = 44100 * (durationMs / 1000.0) ; approximate total samples for the entire play

  ; 10) Main playback loop
  While ElapsedMilliseconds() - startTime < durationMs

    ; Wait ~ up to 500ms for event
    Protected waitRes = WaitForSingleObject_(hEvent, 500)
    If waitRes = #WAIT_OBJECT_0
      ; The event is signaled, means we can fill more data
      ; Check how many frames are "already used" => padding
      hr = baseClient\GetCurrentPadding(@numPadding)
      If hr = #S_OK
        numFramesAvailable = bufferFrames - numPadding
        If numFramesAvailable > 0
          ; Get buffer from IAudioRenderClient
          hr = renderClient\GetBuffer(numFramesAvailable, @*data)
          If hr = #S_OK
            Debug numFramesAvailable
            ; Fill that buffer with our sine wave
            ; each frame => 2 channels => 2 x 16-bit
            Protected frame.i
            Protected *ptr.Word = *data
            For frame = 0 To numFramesAvailable - 1
              Protected s.f = Sin(t) * 0.5  ; amplitude
              Protected sample.w = Round(s * 32767.0, #PB_Round_Nearest)

              ; stereo => left, then right channel
              *ptr\w = sample : *ptr + SizeOf(Word)
              *ptr\w = sample : *ptr + SizeOf(Word)

              t + increment
              sampleCount + 1
              If sampleCount >= maxSamples
                ; We can fade out or just keep going with silence
                ; For demo, let's keep going with sine, ignoring duration
              EndIf
            Next

            renderClient\ReleaseBuffer(numFramesAvailable, 0)
          EndIf
        EndIf
      EndIf
    EndIf

  Wend

  ; 11) Stop playing
  baseClient\Stop()
  Debug "Playback finished."

  If hEvent
    CloseHandle_(hEvent)
  EndIf

  ; Cleanup
  Cleanup:
    If renderClient
      renderClient\Release()
    EndIf
    If audioClient3
      audioClient3\Release()
    EndIf
    If audioClient2
      audioClient2\Release()
    EndIf
    If audioClient1
      audioClient1\Release()
    EndIf
    If defaultRender
      defaultRender\Release()
    EndIf
    If deviceEnumerator
      deviceEnumerator\Release()
    EndIf

    CoUninitialize_()
EndProcedure


; -----------------------------------------------------------------------------
;  DataSection: All Required GUIDs
; -----------------------------------------------------------------------------
DataSection
  ; MMDeviceEnumerator
  uuidof_MMDeviceEnumerator:
  Data.l $BCDE0395
  Data.w $E52F,$467C
  Data.b $8E,$3D,$C4,$57,$92,$91,$69,$2E

  uuidof_IMMDeviceEnumerator:
  Data.l $A95664D2
  Data.w $9614,$4F35
  Data.b $A7,$46,$DE,$8D,$B6,$36,$17,$E6

  ; IAudioClient {1CB9AD4C-DBFA-4c32-B178-C2F568A703B2}
  uuidof_IAudioClient:
  Data.l $1CB9AD4C
  Data.w $DBFA,$4C32
  Data.b $B1,$78,$C2,$F5,$68,$A7,$03,$B2

  ; IAudioClient2 {726778CD-F60A-4eda-82DE-E47610CD78AA}
  uuidof_IAudioClient2:
  Data.l $726778CD
  Data.w $F60A,$4EDA
  Data.b $82,$DE,$E4,$76,$10,$CD,$78,$AA

  ; IAudioClient3 {F4B1A599-7266-4319-A8CA-E70ACB11E8CD}
  uuidof_IAudioClient3:
  Data.l $F4B1A599
  Data.w $7266,$4319
  Data.b $A8,$CA,$E7,$0A,$CB,$11,$E8,$CD

  ; IAudioRenderClient {F294ACFC-3146-4483-A7BF-ADDCA7C260E2}
  ; Official: DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2)
  uuidof_IAudioRenderClient:
  Data.l $F294ACFC
  Data.w $3146,$4483
  Data.b $A7,$BF,$AD,$DC,$A7,$C2,$60,$E2
EndDataSection

; -----------------------------------------------------------------------------
;  Demo: Play 5 seconds of a 440Hz sine wave
; -----------------------------------------------------------------------------
PlaybackSineWave_WithFallback(5000)
End
Last edited by AndyMK on Wed Jan 29, 2025 3:20 pm, edited 4 times in total.
Quin
Addict
Addict
Posts: 1132
Joined: Thu Mar 31, 2022 7:03 pm
Location: Colorado, United States
Contact:

Re: Pb WASAPI Event driven Exclusive Mode

Post by Quin »

Very interesting, thanks for sharing! :)
Post Reply