Repository: freebasic/fbc
Pull request #440: examples: Add portaudio sine wave example
Author: login: thekhalifa
Created: 2025-02-22T17:17:07Z | Merged: 2025-03-15T17:48:58Z

PortAudio already has a FB header, this adds an example usage to play a sine wave from a buffer.


Diff:
diff --git a/examples/Makefile b/examples/Makefile
index 579b697002..61fd2c8d9f 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -46,7 +46,8 @@ NETWORK		:= $(subst .bas,,$(wildcard network/*.bas network/*/*.bas \
 			network/*/*/*.bas))
 #OPTIMIZE	:= $(subst .bas,,$(wildcard OptimizePureAbstractTypes/*.bas))
 #REGEX		:= $(subst .bas,,$(wildcard regex/*/*.bas))
-SOUND		:= $(subst .bas,,$(wildcard sound/*/*.bas))
+SOUND		:= $(subst .bas,,$(wildcard sound/*.bas)) \
+			$(subst .bas,,$(wildcard sound/*/*.bas))
 THREADS		:= $(subst .bas,,$(wildcard threads/*.bas)) \
 			threads/timer-lib/libtimer$(DLLEXT) threads/timer-lib/test
 UNICODE		:= $(subst .bas,,$(wildcard unicode/*.bas))
diff --git a/examples/sound/portaudio-sine.bas b/examples/sound/portaudio-sine.bas
new file mode 100644
index 0000000000..c116912868
--- /dev/null
+++ b/examples/sound/portaudio-sine.bas
@@ -0,0 +1,92 @@
+''
+'' PortAudio Sine wave example
+''
+'' Requires libportaudio2
+
+#include "portaudio.bi"
+
+Const Pi as Double = 3.14159265358979323846
+Const SampleRate as Integer = 44100
+Const SineSamples as Integer = SampleRate / 10
+
+Type PlayPayload
+    frequency As Single
+End Type
+
+Function PlayCallback cdecl (InputBuff as Const Any Ptr, OutputBuff as Any Ptr, _
+                    FrameCount as CUlong, TimeInfo as Const PaStreamCallbackTimeInfo Ptr, _
+                    StatusFlags as PaStreamCallbackFlags, UserData as Any ptr) as Long
+
+    Dim as Long Ptr outBuff = OutputBuff
+    Dim as PlayPayload Ptr payload = UserData
+    Dim as Single frequency = payload->frequency
+    For i as Integer = 0 To FrameCount - 1
+        outBuff[i] = CLng(&h7FFFFFFF * 0.5 * Sin(2 * Pi * frequency * i / SampleRate))
+    Next
+    Function = paContinue
+End Function
+
+Sub Play(Frequency as Single, Duration as Single)
+    Dim as PaError pe
+    Dim as PaStream Ptr stream
+
+    Dim as PlayPayload payload
+    payload.frequency = Frequency
+
+    pe = Pa_OpenDefaultStream(@stream, 0, 1, paInt32, SampleRate, SineSamples, @PlayCallback, @payload)
+    If pe <> paNoError Then
+        Print "Error: "; *Pa_GetErrorText(pe)
+        End 1
+    End If
+
+    Print "Playing "; Frequency; "Hz for "; Duration; "ms"
+    pe = Pa_StartStream(stream)
+    If pe <> paNoError Then
+        Print "Error: "; *Pa_GetErrorText(pe)
+        End 1
+    End If
+
+    'Sleep 2
+    Pa_Sleep(Duration)
+
+    pe = Pa_StopStream(stream)
+    If pe <> paNoError Then
+        Print "Error: "; *Pa_GetErrorText(pe)
+        End 1
+    End If
+
+    pe = Pa_CloseStream(stream)
+    If pe <> paNoError Then
+        Print "Error: "; *Pa_GetErrorText(pe)
+        End 1
+    End If
+End Sub
+
+Dim as PaError pe
+Print "Initialize PortAudio ("; *Pa_GetVersionText(); ")"
+
+pe = Pa_Initialize()
+If pe <> paNoError Then
+    Print "Error: "; *Pa_GetErrorText(pe)
+    End 1
+End If
+
+Dim defdevice as Integer = Pa_GetDefaultOutputDevice()
+If defdevice = paNoDevice Then
+    Print "Error: no default output device"
+    End 1
+End If
+
+Dim devinfo as const PaDeviceInfo Ptr = Pa_GetDeviceInfo(defdevice)
+Dim apiinfo as const PaHostApiInfo Ptr = Pa_GetHostApiInfo(Pa_GetDefaultHostApi())
+Print "Using "; *apiinfo->name; " api on default output device: "; *devinfo->name
+
+'' Play from A4 (440Hz) for 440ms until A3 (220Hz) for 220ms
+For i as Integer = 440 to 220 Step -110
+    Play(i, i)
+Next
+
+Print "Shutdown PortAudio"
+Pa_Terminate()
+
+End 0


Reviews:
user: login: jayrm
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T02:30:44Z
user: login: jayrm
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T02:33:16Z

Review comments:
diff_hunk: @@ -0,0 +1,82 @@
+''
+'' PortAudio Sine wave example
+''
+'' Requires libportaudio2
+
+#include "portaudio.bi"
+
+Const Pi as Double = 3.14159265358979323846
+Const SampleRate as Integer = 44100
+Const SineSamples as Integer = SampleRate / 10
+
+Type PlayPayload
+    frequency As Single
+End Type
+
+Function PlayCallback (InputBuff as Const Any Ptr, OutputBuff as Any Ptr, _
path: examples/sound/portaudio-sine.bas
user: login: jayrm
body: On windows, the calling the convention will default to `stdcall` but the `portaudio.bi` API expects `cdecl`
This callback function should be defined with `cdecl` calling convention as in `Function PlayCallback cdecl ( ...` for portability to windows.
created_at: 2025-03-03T02:30:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,82 @@
+''
+'' PortAudio Sine wave example
+''
+'' Requires libportaudio2
+
+#include "portaudio.bi"
+
+Const Pi as Double = 3.14159265358979323846
+Const SampleRate as Integer = 44100
+Const SineSamples as Integer = SampleRate / 10
+
+Type PlayPayload
+    frequency As Single
+End Type
+
+Function PlayCallback (InputBuff as Const Any Ptr, OutputBuff as Any Ptr, _
+                    FrameCount as CUlong, TimeInfo as Const PaStreamCallbackTimeInfo Ptr, _
+                    StatusFlags as PaStreamCallbackFlags, UserData as Any ptr) as Long
+
+    Dim as Long Ptr outBuff = OutputBuff
+    Dim as PlayPayload Ptr payload = UserData
+    Dim as Single frequency = payload->frequency
+    For i as Integer = 0 To FrameCount - 1
+        outBuff[i] = CLng(&h7FFFFFFF * 0.5 * Sin(2 * Pi * frequency * i / SampleRate))
+    Next
+    Function = paContinue
+End Function
+
+Sub Play(Frequency as Single, Duration as Single)
+    Dim as PaError pe
+    Dim as PaStream Ptr stream
+
+    Dim as PlayPayload payload
+    payload.frequency = Frequency
+
+    pe = Pa_OpenDefaultStream(@stream, 0, 1, paInt32, SampleRate, SineSamples, @PlayCallback, @payload)
+    If pe <> paNoError Then
+        Print "Error: "; Pa_GetErrorText(pe)
path: examples/sound/portaudio-sine.bas
user: login: jayrm
body: `Pa_GetErrorText(pe)` here and a few other locations below are missing the dereference `*` to print the pointed to string.  i.e. `*Pa_GetErrorText(pe)`
created_at: 2025-03-03T02:33:15Z
author_association: MEMBER

PR comments:
user: login: jayrm
created_at: 2025-03-03T02:41:47Z
body: I tried this example on windows after hacking together a static library compiled from sources from https://github.com/PortAudio/portaudio 

This example worked for me on windows 32 bit.  I didn't try 64-bit.  Only issue I had with the example itself is that it worked on 48000Hz samplerate, but not 44100Hz.  Don't know if that issue is windows thing or a hardware thing or I missed something building the library.


author_association: MEMBER
user: login: thekhalifa
created_at: 2025-03-03T19:47:14Z
body: > I tried this example on windows after hacking together a static library compiled from sources from https://github.com/PortAudio/portaudio
> 
> This example worked for me on windows 32 bit. I didn't try 64-bit. Only issue I had with the example itself is that it worked on 48000Hz samplerate, but not 44100Hz. Don't know if that issue is windows thing or a hardware thing or I missed something building the library.

Fished out a Win32 vm and I think it was a latency issue where the stream was stopped before it started playing. The callback wasn't called at all when testing portaudio with the Win MM API (I couldn't compile it with WASAPI or DX).

So now it's 3 notes that play for longer and prints out the API name. Sounds a bit clippy on Windows, but should be good enough as an API example. The original sequence was meant as the game over sound, but the buffer playback needs to be more clever to keep the stream open.
author_association: CONTRIBUTOR
user: login: jayrm
created_at: 2025-03-15T17:48:48Z
body: Thank-you for the fix ups and the extra testing.  I agree is good enough for a start point and should not be too difficult for users to play test on various windows versions and hardware to find what works with error messages shown.  Thanks.
author_association: MEMBER