I don't know if we can do this native until we get some help from the PB team.
This is something close what you are asking for but with API calls.
I pieced this together a while back using code I found here on the forum.
Maybe something here is useful to you.
Code: Select all
; SoundOutContinuousWrite.pb
; by BasicallyPure, 11/18/2013
; windows only
; PB 5.20 LTS
EnableExplicit
;{ Constants
#MainWin = 0
#OutDevice_1 = 0
#PIx2 = 2*#PI
#Mono = 1
#Stereo = 2
#ScopeBkgndColor = $276724
#TraceLeftColor = $27CF24
#TraceRightColor = $27E4D3
#ScopeWidth = 512
#ScopeHeight = 256
#SpinLostFocus = 512
#Input_Finished = #PB_EventType_FirstCustomValue
; gadgets
#BtnHop = 0
#BtnPause = 1
#Canvas_1 = 2
#SpinLeft = 3
#SpinRight = 4
#TrackLeft = 5
#TrackRight = 6
; menus
#Menu_1 = 0
;}
;{ Procedure declarations
Declare WinCallback(hwnd, uMsg, wParam, lParam)
Declare StartSoundOutput()
Declare StopSoundOutput()
Declare CalcWave(*SBuf)
Declare Init_GUI()
Declare EventLoop()
Declare ProcessSpin(nGad,*Freq)
;}
;{ Global variables
Global SampleClock = 44100 ; Sampling frequency in 'samples per second'
Global BlockSize = 8192 ; Number of samples in block
Global BytesPerSample = 2 ; Number of bytes needed for each sample, don't change this
Global Channels = #Stereo ; Number of channels, 1 for mono, 2 for stereo.
Global nBuf = 8 ; Number of buffers
Global DevOut = 1 ; default audio output device
Global Frequency_Left = 1000
Global Frequency_Right = 440
Global Volume_Left.f = 0.25
Global Volume_Right.f = 0.25
Global hWaveOut
Global Hop = #False
Global Pause = #False
Global NumOutDevs
Global PlayFormat.WAVEFORMATEX
Global MyOutDevs.WAVEOUTCAPS
Global Dim outHdr.WAVEHDR(nBuf)
;}
If Init_GUI()
EventLoop()
StopSoundOutput()
EndIf
End
Procedure Init_GUI()
Protected n, result = 1
If OpenWindow(#MainWin,0,0,#ScopeWidth+20,#ScopeHeight+90,"Continuous sound out write test",#PB_Window_SystemMenu |#PB_Window_ScreenCentered)
ButtonGadget(#BtnHop,10,10,50,25,"Hop",#PB_Button_Toggle)
ButtonGadget(#BtnPause,70,10,50,25,"Pause",#PB_Button_Toggle)
SpinGadget(#SpinLeft,130,02,70,25,100,5000)
SetGadgetState(#SpinLeft,Frequency_Left) : SetGadgetText(#SpinLeft,Str(Frequency_Left))
SetGadgetColor(#SpinLeft,#PB_Gadget_BackColor,#TraceLeftColor)
SpinGadget(#SpinRight,235,02,70,25,100,5000)
SetGadgetState(#SpinRight,Frequency_Right) : SetGadgetText(#SpinRight,Str(Frequency_Right))
SetGadgetColor(#SpinRight,#PB_Gadget_BackColor,#TraceRightColor)
TrackBarGadget(#TrackLeft,125,30,100,25,0,100) : SetGadgetState(#TrackLeft,Volume_Left * 100)
TrackBarGadget(#TrackRight,230,30,100,25,0,100) : SetGadgetState(#TrackRight,Volume_Right * 100)
CanvasGadget(#Canvas_1,8,58,#ScopeWidth+4,#ScopeHeight+4,#PB_Canvas_Border)
StartDrawing(CanvasOutput(#Canvas_1))
Box(0,0,#ScopeWidth,#ScopeHeight,#ScopeBkgndColor)
StopDrawing()
CreateMenu(#Menu_1, WindowID(#MainWin))
; locate all sound output devices
OpenSubMenu("Sound Output Devices")
NumOutDevs = waveOutGetNumDevs_()
If NumOutDevs <> 0
For n = 0 To NumOutDevs - 1
If waveOutGetDevCaps_(n,@MyOutDevs,SizeOf(WAVEOUTCAPS)) = 0
MenuItem(n + #OutDevice_1,PeekS(@MyOutDevs\szPname))
EndIf
Next
CloseSubMenu()
SetMenuItemState(#Menu_1,#OutDevice_1,#True)
SetWindowCallback(@WinCallback()) ; Handle Sound Output callback
StartSoundOutput()
Else
MessageRequester("Error!","No audio output device found.")
result = 0
EndIf
Else
result = 0
EndIf
ProcedureReturn result
EndProcedure
Procedure EventLoop()
Protected menuSelection, ActiveGadget, n, Quit = #False
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Quit = #True
Case #PB_Event_Gadget
Select EventGadget()
Case #BtnHop
Hop = GetGadgetState(#BtnHop)
Frequency_Left = GetGadgetState(#SpinLeft)
Frequency_Right = GetGadgetState(#SpinRight)
Case #BtnPause
Pause = GetGadgetState(#BtnPause)
If Pause
waveOutPause_(hWaveOut)
Else
waveOutRestart_(hWaveOut)
EndIf
Case #SpinLeft
ProcessSpin(#SpinLeft, @Frequency_Left)
Case #SpinRight
ProcessSpin(#SpinRight, @Frequency_Right)
Case #TrackLeft
Volume_Left = GetGadgetState(#TrackLeft) / 100
Case #TrackRight
Volume_Right = GetGadgetState(#TrackRight) / 100
EndSelect
Case #PB_Event_Menu
menuSelection = EventMenu()
If GetMenuItemState(#Menu_1,menuSelection) = #False
Select menuSelection
Case #OutDevice_1 To #OutDevice_1 + NumOutDevs - 1 ; Output device selection
For n = #OutDevice_1 To #OutDevice_1 + NumOutDevs - 1
If n = menuSelection
SetMenuItemState(#Menu_1,menuSelection,#True)
Else
SetMenuItemState(#Menu_1,n,#False)
EndIf
Next
DevOut = menuSelection - #OutDevice_1 + 1
StopSoundOutput()
StartSoundOutput()
EndSelect
EndIf
Case #WM_KEYUP
ActiveGadget = GetActiveGadget()
Select ActiveGadget
Case #SpinLeft To #SpinRight
If EventwParam() = #VK_RETURN
PostEvent(#PB_Event_Gadget,#MainWin,ActiveGadget,#Input_Finished)
EndIf
EndSelect
EndSelect
Until Quit = #True
EndProcedure
Procedure WinCallback(hwnd, uMsg, wParam, lParam)
; Window callback to service sound output message
Static *hWaveO.WAVEHDR
Select uMsg
Case #MM_WOM_DONE ; Sound output, a play buffer has been returned.
*hWaveO.WAVEHDR = lParam ; lParam has the address of WAVEHDR
CalcWave(*hWaveO\lpData) ; send pointer where to write NEW data
*hWaveO\dwBytesRecorded = BlockSize ; Number of bytes written into buffer
waveOutWrite_(hWaveOut,lParam, SizeOf(WAVEHDR)) ; Send to sound device => jack socket => cable =>
EndSelect
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
Procedure StartSoundOutput()
Protected T,i, *P
Static *OutBufMem
With PlayFormat
\wFormatTag = #WAVE_FORMAT_PCM
\nChannels = Channels
\wBitsPerSample = BytesPerSample * 8
\nSamplesPerSec = SampleClock
\nBlockAlign = Channels * BytesPerSample
\nAvgBytesPerSec = \nSamplesPerSec * \nBlockAlign
EndWith
If *OutBufMem : FreeMemory(*OutBufMem) : EndIf ; Free a prior assignement
*OutBufMem = AllocateMemory(BlockSize * nBuf) ; Reserve memory for all the buffers
T = waveOutOpen_(@hWaveOut, #WAVE_MAPPER+DevOut, @PlayFormat, WindowID(#MainWin), #True, #CALLBACK_WINDOW | #WAVE_FORMAT_DIRECT)
If T = #MMSYSERR_NOERROR
*P = *OutBufMem ; Pointer to start of memory
For i = 0 To nBuf-1 ; For each buffer
outHdr(i)\lpData = *P ; start of buffer
outHdr(i)\dwBufferLength = BlockSize ; size of buffer
outHdr(i)\dwFlags = 0
outHdr(i)\dwLoops = 0
T | waveOutPrepareHeader_(hWaveOut, outHdr(i), SizeOf(WAVEHDR))
*P + BlockSize
Next
For i = 0 To nBuf-1
PostMessage_(WindowID(#MainWin),#MM_WOM_DONE,0,outHdr(i))
Next
EndIf
If T = #MMSYSERR_NOERROR : ProcedureReturn 1 : Else : ProcedureReturn 0 : EndIf
EndProcedure
Procedure StopSoundOutput()
Protected i
waveOutReset_(hWaveOut)
For i = 0 To nBuf - 1
waveOutUnprepareHeader_(hWaveOut, outHdr(i), SizeOf(WAVEHDR))
Next
waveOutClose_(hWaveOut)
EndProcedure
Procedure CalcWave(*SBuf)
; This routine generates Left and Right waveforms.
Static.d Angle, Kl, Kr, La, Ra
Static.i sample, Sgn, scope_X, Trig
Static.l Vl, Vr, Ls = 1, Rs
Static HSH = #ScopeHeight/2 ; one half of the scope height
Static SW = #ScopeWidth -1
Static scaler = $10000 / #ScopeHeight
Static Dim eraser(#ScopeWidth-1,1)
Protected Trace_L = 1
If Hop
Frequency_Left = Random(1220,220)
Frequency_Right = Random(1220,220)
EndIf
; Calculate the frequency scaling factors
Kl = #PIx2 * Frequency_Left / SampleClock
Kr = #PIx2 * Frequency_Right / SampleClock
StartDrawing(CanvasOutput(#Canvas_1))
LineXY(0,HSH,SW,HSH,0)
sample = 1
Repeat ; Generate waveform data
; Left sample
Vl = Sin(La) * 32767 * Volume_Left
La + Kl ; Calculate angle for next time
If La > #PIx2 : La - #PIx2 : EndIf ; limit to 2*PI radians
PokeW(*SBuf,Vl) ; Put point in buffer
*SBuf + BytesPerSample ; move buffer pointer to next sample
; Right sample
If Channels = #Stereo
Vr = Sin(Ra) * 32767 * Volume_Right
Ra + Kr
If Ra > #PIx2 : Ra - #PIx2 : EndIf
PokeW(*SBuf,Vr)
*SBuf + BytesPerSample
EndIf
; draw the scope display
; trace drawing will alternate left then right so each can be triggered
If Trig ; trigger is active so continue drawing left or right trace
Vl/scaler : Vr/scaler
If Trace_L ; draw the left trace
Plot(scope_X,HSH + eraser(scope_X,0),#ScopeBkgndColor) ; erase old
eraser(scope_X,0) = Vl
Plot(scope_X,HSH + Vl,#TraceLeftColor)
ElseIf Channels = #Stereo ; draw the right trace
Plot(scope_X,HSH + eraser(scope_X,1),#ScopeBkgndColor) ; erase old
eraser(scope_X,1) = Vr
Plot(scope_X,HSH + Vr,#TraceRightColor)
EndIf
scope_X + 1 ; move 1 pixel right
If scope_X > SW ; trace has reached the right edge
scope_X = 0 ; move back to left edge
Trig = #False ; reset the trigger
; capture the sign of the next trace sample
Ls = Sign(Vl) : If Ls = 0 : Ls + 1 : EndIf
If channels = #Stereo
Rs = Sign(Vr) : If Rs = 0 : Rs + 1 : EndIf
Trace_L ! 1
EndIf
EndIf
Else ; wait for zero crossing to enable trigger
If Trace_L And Vl <> 0
If Ls <> Sign(Vl) ; the sign has changed (zero crossing)
If Ls < 0 : Trig = #True : Else : Ls = -Ls : EndIf
EndIf
ElseIf Channels = #Stereo
If Rs <> Sign(Vr) ; the sign has changed (zero crossing)
If Rs < 0 : Trig = #True : Else : Rs = -Rs : EndIf
EndIf
EndIf
EndIf
sample + PlayFormat\nBlockAlign
Until sample > BlockSize
StopDrawing()
EndProcedure
Procedure ProcessSpin(nGad,*Freq)
Select EventType()
Case #PB_EventType_Up, #PB_EventType_Down
SetGadgetText(nGad, Str(GetGadgetState(nGad)))
PokeI(*Freq, GetGadgetState(nGad))
Case #Input_Finished, #SpinLostFocus
SetGadgetState(nGad,Val(GetGadgetText(nGad)))
SetGadgetText(nGad, Str(GetGadgetState(nGad)))
PokeI(*Freq, GetGadgetState(nGad))
EndSelect
EndProcedure