How to get a pointer to the Waveform data of CatchSound()?

Just starting out? Need help? Post your questions and find answers here.
User avatar
Kurzer
Enthusiast
Enthusiast
Posts: 670
Joined: Sun Jun 11, 2006 12:07 am
Location: Near Hamburg

How to get a pointer to the Waveform data of CatchSound()?

Post by Kurzer »

Hello,

I want to play around with audiomanipulation and realtime creation of sounds.
But I have a serious problem: How to play an endless, realtime generated sound with PureBasic nativ commands?

Is it possible to get the memorypointer to the waveform of a purebasic sound?
CatchSound() and LoadSound() returns some values which looks like pointer... maybe pointers to an internal PureBasic structure where I can find the address of the corresponding waveform values?

Does anybody know somoething about this?

I want to realize a double buffer playback of a realtime calculated soundstream, but without dealing with directX and Windows API. Precalculate the waveform to a memoryblock and catching it afterwards is not an option for me. I want to realize this with only two small soundbuffers - or with one memory block of an catched Purebasic sound which will be filled periodically in realtime after one half of the buffer is completely played.

To determine the 'playcursor-position' in a currently played waveform would be also helpful for this job. But for this task PureBasic provides GetSoundPosition(). This should do the job.

I did not understand too much of the PureBasic internas, so I have no idea how to handle this problem.
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520, User age in 2024: 56y
"Happiness is a pet." | "Never run a changing system!"
User avatar
JHPJHP
Addict
Addict
Posts: 2253
Joined: Sat Oct 09, 2010 3:47 am

Re: How to get a pointer to the Waveform data of CatchSound(

Post by JHPJHP »

Have you looked at the following examples (may contain some of what you're looking for):
- quick Google search revealed these and a bunch more
http://www.forums.purebasic.com/english ... 6&start=15
http://www.purebasic.fr/french/viewtopic.php?f=6&t=9891
http://www.purebasic.fr/english/viewtop ... 12&t=54421

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: How to get a pointer to the Waveform data of CatchSound(

Post by BasicallyPure »

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.

BP

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
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
Kurzer
Enthusiast
Enthusiast
Posts: 670
Joined: Sun Jun 11, 2006 12:07 am
Location: Near Hamburg

Re: How to get a pointer to the Waveform data of CatchSound(

Post by Kurzer »

Hello JHPJHP,

yes I've used the forum search and looked for this topic, but I found only API-based examples - or I missed the important little thing that hides in the last two links which you provided in your post. :) This two posts are hitting the nail on the head.

It seems that IsSound () is the command which returns the address of a sound structure. I have always thought that CatchSound () or LoadSound () returns this pointer.

Unfortunately I am currently not at my own computer (no PB here), but what I see in the examples looks exactly like what I need.

Thank you very much JHPJHP!

And also to you, BasicallyPure, a big thank you for your sample code. Even if it is based entirely on API, but I will surely use the routines in the future. Thank you for your effort!
PB 6.02 x64, OS: Win 7 Pro x64 & Win 11 x64, Desktopscaling: 125%, CPU: I7 6500, RAM: 16 GB, GPU: Intel Graphics HD 520, User age in 2024: 56y
"Happiness is a pet." | "Never run a changing system!"
Post Reply