Page 2 of 3
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Sat Oct 29, 2011 6:09 pm
by wilbert
Here's another modification
Code: Select all
XIncludeFile "PortAudio.pb"
#MinFreq = 100
#MaxFreq = 10000
#SampleRate = 44100
#PI2 = 6.28318530717958
Enumeration
#WIN_MAIN
#PlayPauseButton
#SelectedWave
#FrequenceText
#FrequenceString
#FrequenceTrack
EndEnumeration
Enumeration
#Sine
#Saw
#Square
EndEnumeration
Procedure Error(err)
Debug Pa_GetErrorText(err)
MessageRequester("", PeekS(Pa_GetErrorText(err)))
End
EndProcedure
Prototype.d WaveFormFnProto(Phh.d)
Global Stream, Streaming, WaveFormFn.WaveFormFnProto
Global Phase.d, PhaseAdd.d
Procedure.d Sine(Phh.d)
ProcedureReturn Phh
EndProcedure
Procedure.d Saw(Phh.d)
If Phh < 0.5
ProcedureReturn 0.35 * Sin(Phh)
Else
ProcedureReturn (0.35 * Sin(1 - Phh)) + 0.5
EndIf
EndProcedure
Procedure.d Sq(Phh.d)
If Phh < 0.5
ProcedureReturn 0.25
Else
ProcedureReturn 0.75
EndIf
EndProcedure
ProcedureC PaStreamCallback(*in, *output.Float, frameCount, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userData)
While frameCount
*output\f = Sin(#PI2 * WaveFormFn(Phase))
Phase + PhaseAdd
If Phase > 1 : Phase = Phase - 1 : EndIf
*output + 4
frameCount - 1
Wend
EndProcedure
err = Pa_Initialize()
If err <> #paNoError : Error(err) : EndIf
op.PaStreamParameters
op\channelCount = 1
op\device = Pa_GetDefaultOutputDevice()
op\sampleFormat = #paFloat32
op\suggestedLatency = 150/1000
err = Pa_OpenStream(@Stream, #Null, @op, #SampleRate, 0, #paNoFlag, @PaStreamCallback(), 0)
If err <> #paNoError : Error(err) : EndIf
If OpenWindow(#WIN_MAIN, 0, 0, 200, 180, "Interaction", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ComboBoxGadget(#SelectedWave, 40, 20, 140, 20)
AddGadgetItem(#SelectedWave, -1, "Sine")
AddGadgetItem(#SelectedWave, -1, "Saw")
AddGadgetItem(#SelectedWave, -1, "Square")
SetGadgetState(#SelectedWave,0)
WaveFormFn = @Sine()
TrackBarGadget(#FrequenceTrack, 10, 10, 20, 110, #MinFreq, #MaxFreq, #PB_TrackBar_Vertical)
TextGadget(#FrequenceText, 40, 70, 80, 20, "Frequency:")
StringGadget(#FrequenceString, 40, 90, 60, 20, "200",#PB_String_Numeric)
SetGadgetState(#FrequenceTrack, 200)
ButtonGadget(#PlayPauseButton, 10, 130, 180, 30, "Play")
Quit = #False
Repeat
Event = WaitWindowEvent()
Select Event
Case #PB_Event_Gadget
Select EventGadget()
Case #SelectedWave
Select GetGadgetState(#SelectedWave)
Case #Sine
WaveFormFn = @Sine()
Case #Saw
WaveFormFn = @Saw()
Case #Square
WaveFormFn = @Sq()
EndSelect
Case #FrequenceTrack
SetGadgetText(#FrequenceString, Str(GetGadgetState(#FrequenceTrack)))
Case #FrequenceString
Select EventType()
Case #PB_EventType_Change
Value = Val(GetGadgetText(#FrequenceString))
If Value < #MinFreq : Value = #MinFreq
ElseIf Value > #MaxFreq : Value = #MaxFreq : EndIf
SetGadgetState(#FrequenceTrack, Value)
Case #PB_EventType_LostFocus
SetGadgetText(#FrequenceString, Str(GetGadgetState(#FrequenceTrack)))
EndSelect
Case #PlayPauseButton
Streaming = ~Streaming
If Streaming
Pa_StartStream(Stream)
SetGadgetText(#PlayPauseButton, "Pause")
Else
Pa_StopStream(Stream)
SetGadgetText(#PlayPauseButton, "Play")
EndIf
EndSelect
Freq = Val(GetGadgetText(#FrequenceString))
PhaseAdd = Freq / #SampleRate
Case #PB_Event_CloseWindow
Quit = #True
EndSelect
Until Quit
EndIf
Pa_Terminate()
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Sat Oct 29, 2011 7:09 pm
by infratec
Best of both worlds
Code: Select all
XIncludeFile "PortAudio.pb"
#MinFreq = 100
#MaxFreq = 10000
#SampleRate = 44100
#PI2 = 6.28318530717958
Enumeration
#WIN_MAIN
#PlayButton
#SelectedWave
#FrequenceText
#FrequenceString
#FrequenceTrack
EndEnumeration
Enumeration
#Sine
#Saw
#Square
EndEnumeration
Prototype.d WaveFormFnProto(Phh.d)
Structure UserDataStr
WaveFormFn.WaveFormFnProto
PhaseAdd.d
EndStructure
Global UserData.UserDataStr
Procedure Error(err)
Debug Pa_GetErrorText(err)
MessageRequester("", PeekS(Pa_GetErrorText(err)))
End
EndProcedure
Procedure.d Sine(Phh.d)
ProcedureReturn Phh
EndProcedure
Procedure.d Saw(Phh.d)
If Phh < 0.5
ProcedureReturn 0.35 * Sin(Phh)
Else
ProcedureReturn (0.35 * Sin(1 - Phh)) + 0.5
EndIf
EndProcedure
Procedure.d Sq(Phh.d)
If Phh < 0.5
ProcedureReturn 0.25
Else
ProcedureReturn 0.75
EndIf
EndProcedure
ProcedureC PaStreamCallback(*in, *output.Float, frameCount, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userData.UserDataStr)
Static Phase.d = 0.0
For i = 1 To frameCount
;PokeF(*output, Sin(#PI2 * *UserData\WaveFormFn(Phase)))
*output\f = Sin(#PI2 * *UserData\WaveFormFn(Phase))
Phase = Phase + *userData\PhaseAdd
If Phase > 1 : Phase = Phase - 1 : EndIf
*output + 4
Next i
EndProcedure
Procedure.l PlayWave(WaveForm, SampleRate.d, Freq)
Protected stream.l
err = Pa_Initialize()
If err <> #paNoError : Error(err) : EndIf
op.PaStreamParameters
op\channelCount = 1
;op\device = 7
op\device = Pa_GetDefaultOutputDevice()
op\sampleFormat = #paFloat32
op\suggestedLatency = 150/1000
err = Pa_OpenStream(@stream, #Null, @op, SampleRate, 0, #paNoFlag, @PaStreamCallback(), @UserData)
If err <> #paNoError : Error(err) : EndIf
Pa_StartStream(stream)
ProcedureReturn stream
EndProcedure
Procedure StopWave(stream.l)
Pa_StopStream(stream)
Pa_Terminate()
EndProcedure
If OpenWindow(#WIN_MAIN, 0, 0, 140, 180, "Interaction", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ComboBoxGadget(#SelectedWave, 40, 20, 60, 20)
AddGadgetItem(#SelectedWave, -1, "Sine")
AddGadgetItem(#SelectedWave, -1, "Saw")
AddGadgetItem(#SelectedWave, -1, "Square")
SetGadgetState(#SelectedWave,0)
TrackBarGadget(#FrequenceTrack, 10, 10, 20, 110, #MinFreq, #MaxFreq, #PB_TrackBar_Vertical)
TextGadget(#FrequenceText, 40, 70, 80, 20, "Frequency:")
StringGadget(#FrequenceString, 40, 90, 60, 20, "200",#PB_String_Numeric)
SetGadgetState(#FrequenceTrack, 200)
ButtonGadget(#PlayButton, 10, 130, 120, 30, "Play")
UserData\WaveFormFn = @Sine()
UserData\PhaseAdd = GetGadgetState(#FrequenceTrack) / #SampleRate
Quit = #False
Repeat
Event = WaitWindowEvent()
Select Event
Case #PB_Event_Gadget
Select EventGadget()
Case #SelectedWave
If EventType() = #PB_EventType_Change
Select GetGadgetState(#SelectedWave)
Case #Sine : UserData\WaveFormFn = @Sine()
Case #Saw : UserData\WaveFormFn = @Saw()
Case #Square : UserData\WaveFormFn = @Sq()
EndSelect
EndIf
Case #FrequenceTrack
SetGadgetText(#FrequenceString, Str(GetGadgetState(#FrequenceTrack)))
UserData\PhaseAdd = GetGadgetState(#FrequenceTrack) / #SampleRate
Case #FrequenceString
Select EventType()
Case #PB_EventType_Change
Value = Val(GetGadgetText(#FrequenceString))
If Value < #MinFreq : Value = #MinFreq
ElseIf Value > #MaxFreq : Value = #MaxFreq : EndIf
SetGadgetState(#FrequenceTrack, Value)
UserData\PhaseAdd = GetGadgetState(#FrequenceTrack) / #SampleRate
Case #PB_EventType_LostFocus
SetGadgetText(#FrequenceString, Str(GetGadgetState(#FrequenceTrack)))
UserData\PhaseAdd = GetGadgetState(#FrequenceTrack) / #SampleRate
EndSelect
Case #PlayButton
If GetGadgetText(#PlayButton) = "Play"
SetGadgetText(#PlayButton, "Stop")
Stream.l = PlayWave(GetGadgetState(#SelectedWave), #SampleRate, Val(GetGadgetText(#FrequenceString)))
Else
StopWave(Stream)
SetGadgetText(#PlayButton, "Play")
EndIf
EndSelect
Case #PB_Event_CloseWindow
Quit = #True
EndSelect
Until Quit
EndIf
Bernd
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Sat Oct 29, 2011 9:45 pm
by infratec
Hi, hi,
if you make
and put
Code: Select all
UserData\PhaseAdd = GetGadgetState(#FrequenceTrack) / #SampleRate
after
Code: Select all
SetGadgetText(#FrequenceString, Str(GetGadgetState(#FrequenceTrack)))
than you can change the frequency 'on the fly'
Bernd
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Sun Oct 30, 2011 12:05 am
by doctornash
thanks infratec and wilbert! before i graduate to understanding some of the 'advanced' concepts in the 'best of both worlds' code (which works great) such as prototyping, infratec when i run your original suggestion, it appears to continue to play the sine even when one of the other waveforms is chosen from the combo box...minor alteration needed?
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Sun Oct 30, 2011 7:42 am
by wilbert
A prototype allows you to use a variable as if it were a function. You can assign the pointer to the function at runtime. It is usually used for external libraries but can also be used to point to another function inside your program.
Two little remarks ...
1. PokeF makes a function call. Using *output\f should be faster.
2. It might be best to use op\device = Pa_GetDefaultOutputDevice() instead of assigning a number directly. I'm using OS X (so it is cross platform) and the device number isn't the same as on Windows.
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Sun Oct 30, 2011 11:07 am
by infratec
Hi doctornash,
I replaced the code in 'Best of both worlds'.
It includes now the latest suggestions from wilbert and I also added the possibility to change the
waveform 'on the fly'.
But: it was only for playing a bit.
I don't think that it is phase correct.
Have fun.
Bernd
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Mon Oct 31, 2011 10:53 am
by doctornash
something a bit weird: I have purebasic+portaudio running on two laptops: an acer Win7 netbook, and a dell latitude d600 WinXP. my original code and wilbert's code suggestion run fine on both machines. the 'best of both worlds' runs fine on the acer, but on the dell the app continues to play the sine even when saw or square are selected from the combo-box

Re: Playing a simple tone with PureBasic and PortAudio
Posted: Tue Nov 01, 2011 9:50 pm
by doctornash
How would one go about playing a pre-filled array of waveform data through the portaudio callback? For example, if I do this:
Code: Select all
Global Fase.d
Global Fase_Add.d
Fase_Add = 200/44100
Global Dim QArr.d(88200)
For i = 0 To 88200
Qarr(i) = Sin(Pi2*Sine(Fase))
Fase = Fase + Fase_Add
If Fase > 1
Fase = Fase - 1
EndIf
Next
I've just filled an array with 2 seconds worth of 200Hz sine wave data sampled at 44.1kHz, right?
Now when I try to stream this through the portaudio callback like so:
Code: Select all
ProcedureC PaStreamCallback(*in, *output.Float, frameCount, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userData)
For I = 1 To frameCount
*output\f = QArr(I)
*output+4
Next
EndProcedure
I hear a tone, but it is distorted. What am I doing wrong?
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Tue Nov 01, 2011 10:21 pm
by infratec
Hi,
try something like this
Code: Select all
ProcedureC PaStreamCallback(*in, *output.Float, frameCount, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userData.UserDataStr)
Static n = 0
For i = 1 To frameCount
*output\f = QArr(n)
*output+4
n + 1
If n = 88200 : n = 0 : EndIf
Next
EndProcedure
And don't forget to tell PlayWave the right frequency.
Bernd
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Wed Nov 02, 2011 7:31 am
by wilbert
You could use
Pa_WriteStream
Code: Select all
XIncludeFile "PortAudio.pb"
#PI2 = 6.28318530717958
#SampleRate = 44100
#NumSamples = #SampleRate * 2
#LastSample = #NumSamples - 1
Dim SampleArr.f(#LastSample)
Procedure ErrorCheck(err)
If err <> #paNoError
MessageRequester("", PeekS(Pa_GetErrorText(err), -1, #PB_Ascii))
End
EndIf
EndProcedure
Procedure.d Sine(Phh.d)
ProcedureReturn Phh
EndProcedure
ErrorCheck(Pa_Initialize())
op.PaStreamParameters
op\channelCount = 1
op\device = Pa_GetDefaultOutputDevice()
op\sampleFormat = #paFloat32
op\suggestedLatency = 150 / 1000
ErrorCheck(Pa_OpenStream(@Stream, #Null, @op, #SampleRate, 0, 0, 0, 0))
Fase.d = 0
Fase_Add.d = 200 / #SampleRate
For i = 0 To #LastSample
SampleArr(i) = Sin(#PI2 * Sine(Fase))
Fase = Mod(Fase + Fase_Add, 1)
Next
ErrorCheck(Pa_StartStream(Stream))
ErrorCheck(Pa_WriteStream(Stream, @SampleArr(0), #NumSamples))
Pa_Terminate()
Here's an example of a 5 sec gliding tone
Code: Select all
XIncludeFile "PortAudio.pb"
#PI2 = 6.28318530717958
#SampleRate = 44100
#NumSamples = #SampleRate * 5
#LastSample = #NumSamples - 1
Dim SampleArr.f(#LastSample)
Procedure ErrorCheck(err)
If err <> #paNoError
MessageRequester("", PeekS(Pa_GetErrorText(err), -1, #PB_Ascii))
End
EndIf
EndProcedure
Procedure.d Sine(Phh.d)
ProcedureReturn Phh
EndProcedure
ErrorCheck(Pa_Initialize())
op.PaStreamParameters
op\channelCount = 1
op\device = Pa_GetDefaultOutputDevice()
op\sampleFormat = #paFloat32
op\suggestedLatency = 150 / 1000
ErrorCheck(Pa_OpenStream(@Stream, #Null, @op, #SampleRate, 0, 0, 0, 0))
StartFreq.d = 100
DeltaFreq.d = 400
Fase.d = 0
For i = 0 To #LastSample
Freq.d = StartFreq + DeltaFreq * i / #LastSample
Fase_Add.d = Freq / #SampleRate
SampleArr(i) = Sin(#PI2 * Sine(Fase))
Fase = Mod(Fase + Fase_Add, 1)
Next
ErrorCheck(Pa_StartStream(Stream))
ErrorCheck(Pa_WriteStream(Stream, @SampleArr(0), #NumSamples))
Pa_Terminate()
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Wed Nov 02, 2011 1:20 pm
by doctornash
Brilliant! Infratec, your suggestion worked like charm, and wilbert the pa_writestream method yields fine results too
btw, when i code directsound directly with vb6, I have to specify the bits per sample when creating the soundbuffer (eg 16 bits in which case i'd multiply the waveform data by 32767 before buffering); how is bit depth being set here?
Now that we have the basics of playing waveform data from an array and control from a simple GUI sorted out, I'd like to verify how the synthesized audio from an application I previously created, sounds through purebasic->portaudio (once I've done that, I will proceed to build and enhance that app in purebasic itself). So, to verify playback I have output a textfile of samples in the range [-1,1] from that application. Now, I can import this file into a string array like so:
Code: Select all
Global x.l
Global T.l
Dim MyArr.s(0)
x = -1
If ReadFile(1, "TestAudio.txt")
While Eof(1) = #False
x = x+1
ReDim myarr(x)
myarr(x) = ReadString(1)
Wend
CloseFile(1)
EndIf
but how to convert the strings representing each value to an array of doubles (or floats), in order to play the file?
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Wed Nov 02, 2011 1:32 pm
by doctornash
ahh...answered my own question: use ValD
Dim MyArr.d(0)
MyArr(x) = ValD(ReadString(1))
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Wed Nov 02, 2011 1:42 pm
by wilbert
The bit depth is specified by the sampleFormat.
op\sampleFormat = #paFloat32
Other possibilities are
#paInt32,
#paInt24,
#paInt16,
#paInt8 and
#paUInt8 .
So for the 16 bit integer like you had, it would be
op\sampleFormat = #paInt16
The callback of course also would have to be changed in this case
Code: Select all
ProcedureC PaStreamCallback(*in, *output.Word, frameCount, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userData.UserDataStr)
Static n = 0
For i = 1 To frameCount
*output\w = QArr(n) * 32767
*output+2
n + 1
If n = 88200 : n = 0 : EndIf
Next
EndProcedure
Edit:
#paFloat32 might be the best format to use.
If QArr(n) would contain a value outside of [-1, 1] it handles this rather well. When you use #paInt16 and multiply such a value with 32767, it distorts the sound.
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Wed Nov 02, 2011 8:54 pm
by doctornash
thanks for the tip wilbert. i tried with #paInt16, and it works fine for my specific application case, because I apply a normalization algorithm to the audio before outputting it, thereby constraining it to within Int limits. as an aside, the test: output text file of audio data from my vb6 synthesis/sequencing application -> import the text file into purebasic array and play it, went well!
Re: Playing a simple tone with PureBasic and PortAudio
Posted: Wed Nov 02, 2011 9:08 pm
by infratec
Hi doctornash,
but why you want to save the data in text files ???
Why not in binary files?
It is faster and smaller.
Bernd