PortAudio for PB

Share your advanced PureBasic knowledge/code with the community.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

This is what I got from Dmitry:
I installed PB trial again and managed to attach to process with VS Debugger of script you sent me (console one).

Before choosing device I activated debugger and put break point in Pa_IsFormatSupported (as I was getting exception on its call).

I got breakpoint triggered and saw the following problem:

- outputParameters 0x0042456c {device=15 channelCount=2 sampleFormat=8 ...} const PaStreamParameters *
device 15 int
channelCount 2 int
sampleFormat 8 unsigned long
suggestedLatency 5.328626210314e-315#DEN double
hostApiSpecificStreamInfo 0x0000000b void *

If you see suggestedLatency contains 5.328626210314e-315#DEN and pointer
hostApiSpecificStreamInfo contains 0x0000000b. 0x0000000b was actually a cause of crash and exception in PB.

I got it with PB:
;DESTINATION STRUCTURE VALUES
out_streamparms\device = dest_device ;SELECT OUTPUT DEVICE HERE
out_streamparms\channelCount = channels
out_streamparms\sampleFormat = #paInt16 ;SampleFormat
out_streamparms\suggestedLatency = 50 ;*Devices(dest_device)\defaultHighOutputLatency
;out_streamparms\suggestedLatency = *Devices(dest_device)\defaultHighOutputLatency
out_streamparms\hostApiSpecificStreamInfo = 0

If I change to 'out_streamparms\suggestedLatency = 0' then suggestedLatency becomes 0, ok so far but
0x0000000b remains for pointer and causes crash.
I suspect PB has bug in mangling C structure types or in passing proper structure back to DLL.

I suspect that all the bugs you were describing could be due to corrupted parameters/data passing.
Finally I found the root of problem - Structure members alignment. I was compiling with Default, which in fact was 16 bytes.

PB can mangle structure which is aligned to 4 bytes, e.g. to compile workable DLL: C/C++ => Code Generation => Struct Member Alignment = 4 bytes must be set.

I succesfully launched your examples and audio for WASAPI comes without artifacts. To me all looks quite well.

Please try again fresh compile with alignment as I advised and check results from your side.
You need to change the include file if you use 4.
Do I pad each variable out to 4 bytes?
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

Check out this test program:

Code: Select all

XIncludeFile "PortAudio.pb"

OpenConsole(): PrintN("")

;Structure PaHostApiInfo
PrintN(" PaHostApiInfo\structVersion: " + Str(SizeOf(PaHostApiInfo\structVersion)))
PrintN(" PaHostApiInfo\type: " + Str(SizeOf(PaHostApiInfo\type)))
PrintN(" PaHostApiInfo\name: " + Str(SizeOf(PaHostApiInfo\name)))
PrintN(" PaHostApiInfo\deviceCount: " + Str(SizeOf(PaHostApiInfo\deviceCount)))
PrintN(" PaHostApiInfo\defaultInputDevice: " + Str(SizeOf(PaHostApiInfo\defaultInputDevice)))
PrintN(" PaHostApiInfo\defaultOutputDevice: " + Str(SizeOf(PaHostApiInfo\defaultOutputDevice)))
;EndStructure

;Structure PaHostErrorInfo
PrintN(" PaHostErrorInfo\hostApiType: " + Str(SizeOf(PaHostErrorInfo\hostApiType)))
PrintN(" PaHostErrorInfo\errorCode: " + Str(SizeOf(PaHostErrorInfo\errorCode)))        
PrintN(" PaHostErrorInfo\errorText: " + Str(SizeOf(PaHostErrorInfo\errorText)))
;EndStructure

;Structure PaDeviceInfo
PrintN(" PaDeviceInfo\structVersion: " + Str(SizeOf(PaDeviceInfo\structVersion)))
PrintN(" PaDeviceInfo\name: " + Str(SizeOf(PaDeviceInfo\name)))
PrintN(" PaDeviceInfo\hostApi: " + Str(SizeOf(PaDeviceInfo\hostApi)))
PrintN(" PaDeviceInfo\maxInputChannels: " + Str(SizeOf(PaDeviceInfo\maxInputChannels)))
PrintN(" PaDeviceInfo\maxOutputChannels: " + Str(SizeOf(PaDeviceInfo\maxOutputChannels)))
PrintN(" PaDeviceInfo\defaultLowInputLatency: " + Str(SizeOf(PaDeviceInfo\defaultLowInputLatency)))
PrintN(" PaDeviceInfo\defaultLowOutputLatency: " + Str(SizeOf(PaDeviceInfo\defaultLowOutputLatency)))
PrintN(" PaDeviceInfo\defaultHighInputLatency: " + Str(SizeOf(PaDeviceInfo\defaultHighInputLatency)))
PrintN(" PaDeviceInfo\defaultHighOutputLatency: " + Str(SizeOf(PaDeviceInfo\defaultHighOutputLatency)))
PrintN(" PaDeviceInfo\defaultSampleRate: " + Str(SizeOf(PaDeviceInfo\defaultSampleRate)))
;EndStructure

;Structure PaStreamParameters
PrintN(" PaStreamParameters\device: " + Str(SizeOf(PaStreamParameters\device)))
PrintN(" PaStreamParameters\channelCount: " + Str(SizeOf(PaStreamParameters\channelCount)))
PrintN(" PaStreamParameters\sampleFormat: " + Str(SizeOf(PaStreamParameters\sampleFormat)))
PrintN(" PaStreamParameters\suggestedLatency: " + Str(SizeOf(PaStreamParameters\suggestedLatency)))
PrintN(" PaStreamParameters\hostApiSpecificStreamInfo: " + Str(SizeOf(PaStreamParameters\hostApiSpecificStreamInfo)))
;EndStructure

;Structure PaStreamCallbackTimeInfo
PrintN(" PaStreamCallbackTimeInfo\inputBufferAdcTime: " + Str(SizeOf(PaStreamCallbackTimeInfo\inputBufferAdcTime)))
PrintN(" PaStreamCallbackTimeInfo\currentTime: " + Str(SizeOf(PaStreamCallbackTimeInfo\currentTime)))
PrintN(" PaStreamCallbackTimeInfo\outputBufferDacTime: " + Str(SizeOf(PaStreamCallbackTimeInfo\outputBufferDacTime)))
;EndStructure

;Structure PaStreamInfo
PrintN(" PaStreamInfo\structVersion: " + Str(SizeOf(PaStreamInfo\structVersion)))
PrintN(" PaStreamInfo\inputLatency: " + Str(SizeOf(PaStreamInfo\inputLatency)))
PrintN(" PaStreamInfo\outputLatency: " + Str(SizeOf(PaStreamInfo\outputLatency)))
PrintN(" PaStreamInfo\sampleRate: " + Str(SizeOf(PaStreamInfo\sampleRate)))
;EndStructure

h: Delay(1): Goto h
All of the variables are 4 bytes except for the doubles which are 8 bytes, so why wouldn't a structure member alignment of 4 work? I'm not trying to be argumentative, I'm just trying to get PortAudio working properly on PB. BTW, Dmitry has been very generous with his help both in implementing WASAPI in PortAudio and in helping me getting it going on PureBasic.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Re: PortAudio for PB

Post by Trond »

All of the variables are 4 bytes except for the doubles which are 8 bytes, so why wouldn't a structure member alignment of 4 work?
You're right - I misread the include file. I thought *name.b was a byte, but it's of course a pointer.
Thus, we can compile with 4 byte alignment. However, if the default is 16-byte this can't be used, as it will break.

IMO all other struct alignment than 1 is just evil as it ALWAYS creates compatibility problems (as we see here). Fortunately, all members are 4 bytes, so 4-byte alignment doesn't break anything (but it's just pure luck that the PortAudio developers never used bytes or words in their structs).
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

This will work even with 8-bit doubles?

I'm glad we got this settled as there are still some issues with the PortAudio WASAPI implementation which I am working with Dmitry on.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Re: PortAudio for PB

Post by Trond »

chris319 wrote:This will work even with 8-bit doubles?

I'm glad we got this settled as there are still some issues with the PortAudio WASAPI implementation which I am working with Dmitry on.
It should work with 8-byte doubles, yes.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

Trond wrote:It should work with 8-byte doubles, yes.
That's what I meant to say :)
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

There is an interesting anomaly involving PortAudio and WASAPI. When running a PortAudio program from the PureBasic IDE on a non-administrator account, the WASAPI devices are not enumerated at all. However, when the exact same code is run as an .exe (outside of the IDE) on the same non-administrator account, the WASAPI devices are properly enumerated. When the code is run on an administrator account, the WASAPI devices are properly enumerated whether the program is run in the IDE or as an .exe. On a non-administrator account, changing purebasic.exe to run as administrator does not change this behavior. The issue seems to be with the PureBasic IDE.

Code: Select all

;PortAudio device enumeration program
;chris319
;Updated on 3/21/2010

XIncludeFile "PortAudio.pb"

;- Variables
Dim *Devices.PaDeviceInfo(0)
DeviceCount.i

Procedure.s Pae_PaHostApiIndexToString(Ind)
  *info.PaHostApiInfo = Pa_GetHostApiInfo(Ind)
  ProcedureReturn PeekS(*info\name)
EndProcedure

Procedure.s DeviceString(*dev.PaDeviceInfo)
  ProcedureReturn PeekS(*dev\name) + " (" + Pae_PaHostApiIndexToString(*dev\hostApi) + ")"
EndProcedure

OpenConsole()

;- PA Initialization
Pa_Initialize()

;- Enumerate devices
DeviceCount = Pa_GetDeviceCount()
PrintN("There are " + Str(DeviceCount) + " devices." + #CRLF$)

Dim *Devices(DeviceCount)

For I = 0 To DeviceCount - 1
  *Devices(I) = Pa_GetDeviceInfo(i)
Next

For I = 0 To DeviceCount - 1
  ;DESTINATION DEVICES
  If *Devices(I)\maxOutputChannels > 0
    PrintN(Str(I) + " " + "Dest " + DeviceString(*Devices(I)))
  ;INPUT DEVICES
  ElseIf *Devices(I)\maxInputChannels > 0
    PrintN(Str(I) + " " + "Source " + DeviceString(*Devices(I)))
  EndIf
Next

Pa_Terminate()
Repeat: Delay(10): ForEver
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

Edit: 8/12/2013
Below is an updated version of PortAudio.pb which has been tested on PB v5.11 for Windows x86 and Linux. Be sure to use the correct library for your operating system (see ImportC commands).

Edit 2/15/12 NOTE: The pad bytes in the PaStreamParameters structure are apparently no longer needed for the latest versions of PortAudio. If you have trouble getting your device to go into exclusive mode (problems setting the number of channels, sampling rate or bit depth), check the alignment of the PaStreamParameters structure.

PortAudio.pb:

Code: Select all

; /*
;  * $Id: portaudio.h 1083 2006-08-23 07:30:49Z rossb $
;  * PortAudio Portable Real-Time Audio Library
;  * PortAudio API Header File
;  * Latest version available at: http://www.portaudio.com/
;  *
;  * Copyright (c) 1999-2002 Ross Bencina And Phil Burk
;  *
;  * Permission is hereby granted, free of charge, To any person obtaining
;  * a copy of this software And associated documentation files
;  * (the "Software"), To deal in the Software without restriction,
;  * including without limitation the rights To use, copy, modify, merge,
;  * publish, distribute, sublicense, And/Or sell copies of the Software,
;  * And To permit persons To whom the Software is furnished To do so,
;  * subject To the following conditions:
;  *
;  * The above copyright notice And this permission notice shall be
;  * included in all copies Or substantial portions of the Software.
;  *
;  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;  * EXPRESS Or IMPLIED, INCLUDING BUT Not LIMITED To THE WARRANTIES OF
;  * MERCHANTABILITY, FITNESS For A PARTICULAR PURPOSE And NONINFRINGEMENT.
;  * IN NO EVENT SHALL THE AUTHORS Or COPYRIGHT HOLDERS BE LIABLE For
;  * ANY CLAIM, DAMAGES Or OTHER LIABILITY, WHETHER IN AN ACTION OF
;  * CONTRACT, TORT Or OTHERWISE, ARISING FROM, OUT OF Or IN CONNECTION
;  * With THE SOFTWARE Or THE USE Or OTHER DEALINGS IN THE SOFTWARE.
;  */
; 
; /*
;  * The text above constitutes the entire PortAudio license; however, 
;  * the PortAudio community also makes the following non-binding requests:
;  *
;  * Any person wishing To distribute modifications To the Software is
;  * requested To send the modifications To the original developer so that
;  * they can be incorporated into the canonical version. It is also 
;  * requested that these non-binding requests be included along With the 
;  * license above.
;  */

Enumeration ; PaErrorCode
  #paNoError = 0
  #paNotInitialized = -10000
  #paUnanticipatedHostError
  #paInvalidChannelCount
  #paInvalidSampleRate
  #paInvalidDevice
  #paInvalidFlag
  #paSampleFormatNotSupported
  #paBadIODeviceCombination
  #paInsufficientMemory
  #paBufferTooBig
  #paBufferTooSmall
  #paNullCallback
  #paBadStreamPtr
  #paTimedOut
  #paInternalError
  #paDeviceUnavailable
  #paIncompatibleHostApiSpecificStreamInfo
  #paStreamIsStopped
  #paStreamIsNotStopped
  #paInputOverflowed
  #paOutputUnderflowed
  #paHostApiNotFound
  #paInvalidHostApi
  #paCanNotReadFromACallbackStream      ;/**< @todo review error code name */
  #paCanNotWriteToACallbackStream       ;/**< @todo review error code name */
  #paCanNotReadFromAnOutputOnlyStream   ;/**< @todo review error code name */
  #paCanNotWriteToAnInputOnlyStream     ;/**< @todo review error code name */
  #paIncompatibleStreamHostApi
  #paBadBufferPtr
EndEnumeration

#paNoDevice = (-1)
#paUseHostApiSpecificDeviceSpecification = (-2)

Enumeration ; PaHostApiTypeId
  #paInDevelopment = 0 ; /* use while developing support for a new host API */
  #paDirectSound = 1
  #paMME = 2
  #paASIO = 3
  #paSoundManager = 4
  #paCoreAudio = 5
  #paOSS = 7
  #paALSA = 8
  #paAL = 9
  #paBeOS = 10
  #paWDMKS = 11
  #paJACK = 12
  #paWASAPI = 13
  #paAudioScienceHPI = 14
EndEnumeration

#paFloat32        = ($00000001)
#paInt32          = ($00000002)
#paInt24          = ($00000004)
#paInt16          = ($00000008)
#paInt8           = ($00000010)
#paUInt8          = ($00000020)
#paCustomFormat   = ($00010000)
#paNonInterleaved = ($80000000)

#paFormatIsSupported = (0)
#paFramesPerBufferUnspecified = (0)
#paNoFlag          = (0)
#paClipOff         = ($00000001)
#paDitherOff       = ($00000002)
#paNeverDropInput  = ($00000004)
#paPrimeOutputBuffersUsingStreamCallback = ($00000008)
#paPlatformSpecificFlags = ($FFFF0000)

#paInputUnderflow   = ($00000001)
#paInputOverflow    = ($00000002)
#paOutputUnderflow  = ($00000004)
#paOutputOverflow   = ($00000008)
#paPrimingOutput    = ($00000010)

Enumeration ; PaStreamCallbackResult
  #paContinue=0
  #paComplete=1
  #paAbort=2
EndEnumeration

Structure PaHostApiInfo
  structVersion.l
  type.l
  name.s
  deviceCount.l
  defaultInputDevice.l
  defaultOutputDevice.l
EndStructure

Structure PaHostErrorInfo
  hostApiType.l   
  errorCode.l            
  errorText.s
EndStructure

Structure PaDeviceInfo
  structVersion.l
  name.s
  hostApi.l
  maxInputChannels.i
  maxOutputChannels.i
  defaultLowInputLatency.d
  defaultLowOutputLatency.d
  defaultHighInputLatency.d
  defaultHighOutputLatency.d
  defaultSampleRate.d
EndStructure

;PaDeviceIndex 	device
;int 	channelCount
;PaSampleFormat 	sampleFormat
;PaTime 	suggestedLatency
;void * 	hostApiSpecificStreamInfo
  
Structure PaStreamParameters
  device.l
  channelCount.l
  sampleFormat.l
  ;pad1.l ; FOR STRUCTURE ALIGNMENT -- MAY NO LONGER BE NECESSARY IN PUREBASIC?
  suggestedLatency.d
 *hostApiSpecificStreamInfo
  ;pad2.l ; FOR STRUCTURE ALIGNMENT -- MAY NO LONGER BE NECESSARY IN PUREBASIC?
EndStructure

Structure PaStreamCallbackTimeInfo
  inputBufferAdcTime.d
  currentTime.d
  outputBufferDacTime.d
EndStructure

Structure PaStreamInfo
  structVersion.l
  inputLatency.d
  outputLatency.d
  sampleRate.d
EndStructure

PrototypeC PaStreamCallback(*input, *output, frameCount, *timeInfo, statusFlags, *userdata)
PrototypeC PaStreamFinishedCallback(*user_data)

#paWinWasapiExclusive                = 1 << 0 ;puts WASAPI into exclusive mode
#paWinWasapiRedirectHostProcessor    = 1 << 1 ;allows To skip internal PA processing completely
#paWinWasapiUseChannelMask           = 1 << 2 ;assigns custom channel mask
#paWinWasapiPolling                  = 1 << 3 ;selects non-Event driven method of
;Data Read/write Note: WASAPI Event driven core is capable of 2ms latency, but Polling method
;can only provide 15-20ms latency.
#paWinWasapiThreadPriority           = 1 << 4 ;forces custom thread priority setting.
; must be used If PaWasapiStreamInfo::threadPriority is set To custom value.

;/* Host processor. Allows To skip internal PA processing completely. 
;   You must set paWinWasapiRedirectHostProcessor flag To PaWasapiStreamInfo::flags member
;   in order To have host processor redirected To your callback.
;   Use With caution! inputFrames And outputFrames depend solely on final device setup (buffer
;   size is just recommendation) but are Not changing during run-time once stream is started.
;*/
;typedef void (*PaWasapiHostProcessorCallback) (void *inputBuffer,  long inputFrames,
;                                               void *outputBuffer, long outputFrames,
;                                               void *userData);

Enumeration ;Device role
    #eRoleRemoteNetworkDevice = 0
    #eRoleSpeakers
    #eRoleLineLevel
    #eRoleHeadphones
    #eRoleMicrophone
    #eRoleHeadset
    #eRoleHandset
    #eRoleUnknownDigitalPassthrough
    #eRoleSPDIF
    #eRoleHDMI
    #eRoleUnknownFormFactor
EndEnumeration

Enumeration ;Thread priority
    #eThreadPriorityNone = 0
    #eThreadPriorityAudio ;Default For Shared mode.
    #eThreadPriorityCapture
    #eThreadPriorityDistribution
    #eThreadPriorityGames
    #eThreadPriorityPlayback
    #eThreadPriorityProAudio ;Default For Exclusive mode.
    #eThreadPriorityWindowManager
EndEnumeration

;Stream descriptor
Structure PaWasapiStreamInfo
  size.l ;= 32 ;SizeOf(PaWasapiStreamInfo)
  hostApiType.l ;#paWASAPI   ;PaHostApiTypeId hostApiType;    ;paWASAPI
  version.l          ;1
  flags.l            ;collection of PaWasapiFlags

;    /* Support For WAVEFORMATEXTENSIBLE channel masks. If flags contains
;       paWinWasapiUseChannelMask this allows you To specify which speakers 
;       To address in a multichannel stream. Constants For channelMask
;       are specified in pa_win_waveformat.h. Will be used only If 
;       paWinWasapiUseChannelMask flag is specified.
;    */
;    PaWinWaveFormatChannelMask channelMask
  channelMask.l

;    /* Delivers raw Data To callback obtained from GetBuffer() methods skipping 
;       internal PortAudio processing inventory completely. userData parameter will 
;       be the same that was passed To Pa_OpenStream method. Will be used only If 
;       paWinWasapiRedirectHostProcessor flag is specified.
;    */
;    PaWasapiHostProcessorCallback hostProcessorOutput
;    PaWasapiHostProcessorCallback hostProcessorInput

    hostProcessorOutput.l
    hostProcessorInput.l

;    /* Specifies thread priority explicitly. Will be used only If paWinWasapiThreadPriority flag
;       is specified.

;       Please note, If Input/Output streams are opened simultaniously (Full-Duplex mode)
;       you shall specify same value For threadPriority Or othervise one of the values will be used
;       To setup thread priority.
;    */
;    PaWasapiThreadPriority threadPriority
    threadPriority.l

EndStructure


;/* Returns Default sound format For device. Format is represented by PaWinWaveFormat Or 
;   WAVEFORMATEXTENSIBLE Structure.

; @param pFormat pointer To PaWinWaveFormat Or WAVEFORMATEXTENSIBLE Structure.
; @param nFormatSize pize of PaWinWaveFormat Or WAVEFORMATEXTENSIBLE Structure in bytes.
; @param nDevice device index.

 ;@return A non-negative value indicating the number of bytes copied into format decriptor
 ;        Or, a PaErrorCode (which are always negative) If PortAudio is Not initialized
 ;        Or an error is encountered.
;*/
;int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice );


;/* Returns device role (PaWasapiDeviceRole enum).

 ;@param nDevice device index.

; @return A non-negative value indicating device role Or, a PaErrorCode (which are always negative)
;         If PortAudio is Not initialized Or an error is encountered.
;*/

;int/*PaWasapiDeviceRole*/ PaWasapi_GetDeviceRole( PaDeviceIndex nDevice );
;int PaWasapi_GetDeviceRole( PaDeviceIndex nDevice )

;/* Boost thread priority of calling thread (MMCSS). Use it For Blocking Interface only For thread
;    which makes calls To Pa_WriteStream/Pa_ReadStream.

; @param hTask a handle To pointer To priority task. Must be used With PaWasapi_RevertThreadPriority
;              method To revert thread priority To initial state.

 ;@param nPriorityClass an Id of thread priority of PaWasapiThreadPriority type. Specifying 
 ;                      eThreadPriorityNone does nothing.

; @return Error code indicating success Or failure.
; @see PaWasapi_RevertThreadPriority
;*/

;PaError PaWasapi_ThreadPriorityBoost( void **hTask, PaWasapiThreadPriority nPriorityClass );

;/** Boost thread priority of calling thread (MMCSS). Use it For Blocking Interface only For
;    thread which makes calls To Pa_WriteStream/Pa_ReadStream.

; @param hTask Task handle obtained by PaWasapi_BoostThreadPriority method.
; @return Error code indicating success Or failure.
; @see PaWasapi_BoostThreadPriority
;*/
;PaError PaWasapi_ThreadPriorityRevert( void *hTask );


;IMPORTANT:

;WASAPI is implemented For Callback And Blocking interfaces. It supports Shared And
;Exclusive share modes. 
    
;    Exclusive Mode:

;        Exclusive mode allows To deliver audio Data directly To hardware bypassing
;        software mixing.
;        Exclusive mode is specified by 'paWinWasapiExclusive' flag.

;    Callback Interface:

;        Provides best audio quality With low latency. Callback Interface is implemented in 
;        two versions:

;        1) Event-Driven:
;        This is the most powerful WASAPI implementation which is capable to provide
;        glitch-free audio at 2ms latency in Exclusive mode. Lowest possible latency For
;        this mode is usually - 2ms For HD Audio class audio chips (including on-board audio,
;        2ms was achieved on Realtek ALC888/S/T). For Shared mode latency can Not go lower
;        than 20ms.

;        2) Poll-Driven:
;        Polling is another method To operate With WASAPI. It is less efficient than
;        Event-Driven and provides latency at around 12-13ms. Polling must be used To
;        overcome a system bug under Windows Vista x64 when application is WOW64(32-bit)
;        And Event-Driven method simply times out (event handle is never signalled on buffer
;        completion). Please note, such Vista bug does Not exist in Windows 7 x64.
;        Polling is setup by speciying 'paWinWasapiPolling' flag.
;        Thread priority can be boosted by specifying 'paWinWasapiBlockingThreadPriorityPro'
;        flag.

;    Blocking Interface:

;        Blocking Interface is implemented but due To above described Poll-Driven method can
;        Not deliver low latency audio. Specifying too low latency in Shared mode will
;        result in distorted audio although Exclusive mode adds stability.

;    Pa_IsFormatSupported:

;        To check format With correct Share Mode (Exclusive/Shared) you must supply
;        PaWasapiStreamInfo With flags paWinWasapiExclusive set through member of 
;        PaStreamParameters::hostApiSpecificStreamInfo Structure.


ImportC "/usr/local/lib/libportaudio.so" ;LINUX VERSION
;ImportC "portaudio_x86.lib" ;WINDOWS VERSION
    
  Pa_GetVersion()
  Pa_GetVersionText()
  Pa_GetErrorText(errorCode)
  Pa_Initialize()
  Pa_Terminate()
  Pa_GetHostApiCount()
  Pa_GetDefaultHostApi()
  Pa_GetHostApiInfo(hostApi)
  Pa_HostApiTypeIdToHostApiIndex(type)
  Pa_HostApiDeviceIndexToDeviceIndex(hostApi, hostApiDeviceIndex)
  Pa_GetLastHostErrorInfo()
  Pa_GetDeviceCount()
  Pa_GetDefaultInputDevice()
  Pa_GetDefaultOutputDevice()
  Pa_GetDeviceInfo(device)
  Pa_IsFormatSupported(*inputParameters, *outputParameters, sampleRate.d)
;  Pa_OpenStream(*stream.l, *inputParameters, *outputParameters, sampleRate.d, framesPerBuffer, streamFlags, *streamCallback, *user_data)
  Pa_OpenStream(*stream, *inputParameters, *outputParameters, sampleRate.d, framesPerBuffer, streamFlags, *streamCallback, *user_data)
;  Pa_OpenDefaultStream(*stream.l, numInputChannels, numOutputChannels, sampleFormat, sampleRate.d, framesPerBuffer, *streamCallback, *user_data)
  Pa_OpenDefaultStream(*stream, numInputChannels, numOutputChannels, sampleFormat, sampleRate.d, framesPerBuffer, *streamCallback, *user_data)
  Pa_CloseStream(*stream)
  Pa_SetStreamFinishedCallback(*stream, *streamFinishedCallback)
  Pa_StartStream(*stream)
  Pa_StopStream(*stream)
  Pa_AbortStream(*stream)
  Pa_IsStreamStopped(*stream)
  Pa_IsStreamActive(*stream)
  Pa_GetStreamInfo(*stream)
  Pa_GetStreamTime.d(*stream)
  Pa_GetStreamCpuLoad.d(*stream)
  Pa_ReadStream(*stream, *buffer, frames)
  Pa_WriteStream(*stream, *buffer, frames)
  Pa_GetStreamReadAvailable(*stream)
  Pa_GetStreamWriteAvailable(*stream)
  Pa_GetSampleSize(format)
  Pa_Sleep(msec)
EndImport
Last edited by chris319 on Mon Aug 12, 2013 11:21 am, edited 5 times in total.
User avatar
oryaaaaa
Addict
Addict
Posts: 825
Joined: Mon Jan 12, 2004 11:40 pm
Location: Okazaki, JAPAN

Re: PortAudio for PB

Post by oryaaaaa »

Thanks, I didn't understand Pa_GetDeviceInfo().
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

See below for a better way to handle PortAudio error messages using Pa_GetErrorText().
Last edited by chris319 on Sat Mar 17, 2012 8:54 am, edited 1 time in total.
User avatar
oryaaaaa
Addict
Addict
Posts: 825
Joined: Mon Jan 12, 2004 11:40 pm
Location: Okazaki, JAPAN

Re: PortAudio for PB

Post by oryaaaaa »

@chris319, Thank you.

Do you know how to use ASIO2?

I build include ASIO2SDK by Visual Studio 2008 Team Edition.
but ASIO2 initialyze error.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

oryaaaaa wrote:@chris319, Thank you.

Do you know how to use ASIO2?

I build include ASIO2SDK by Visual Studio 2008 Team Edition.
but ASIO2 initialyze error.
Are you trying to compile a dll for use with PortAudio and PureBasic?
Last edited by chris319 on Sat Mar 17, 2012 8:47 am, edited 1 time in total.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

This shows how to handle PortAudio error messages simply by using the function Pa_GetErrorText() and passing the result of the function via a structure:

Code: Select all

result = Pa_StartStream(0)
If result <> #paNoError
  *err = Pa_GetErrorText(result): error.string = @*err
  MessageRequester("Unable to start input stream", error\s)
  shutdown()
EndIf
Last edited by chris319 on Sat Mar 17, 2012 9:05 am, edited 2 times in total.
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

Here is an updated version of portaudio.pb. It adds the functions PaWasapi_GetJackCount() and PaWasapi_GetJackDescription()

Code: Select all

; /*
;  * $Id: portaudio.h 1083 2006-08-23 07:30:49Z rossb $
;  * PortAudio Portable Real-Time Audio Library
;  * PortAudio API Header File
;  * Latest version available at: http://www.portaudio.com/
;  *
;  * Copyright (c) 1999-2002 Ross Bencina And Phil Burk
;  *
;  * Permission is hereby granted, free of charge, To any person obtaining
;  * a copy of this software And associated documentation files
;  * (the "Software"), To deal in the Software without restriction,
;  * including without limitation the rights To use, copy, modify, merge,
;  * publish, distribute, sublicense, And/Or sell copies of the Software,
;  * And To permit persons To whom the Software is furnished To do so,
;  * subject To the following conditions:
;  *
;  * The above copyright notice And this permission notice shall be
;  * included in all copies Or substantial portions of the Software.
;  *
;  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;  * EXPRESS Or IMPLIED, INCLUDING BUT Not LIMITED To THE WARRANTIES OF
;  * MERCHANTABILITY, FITNESS For A PARTICULAR PURPOSE And NONINFRINGEMENT.
;  * IN NO EVENT SHALL THE AUTHORS Or COPYRIGHT HOLDERS BE LIABLE For
;  * ANY CLAIM, DAMAGES Or OTHER LIABILITY, WHETHER IN AN ACTION OF
;  * CONTRACT, TORT Or OTHERWISE, ARISING FROM, OUT OF Or IN CONNECTION
;  * With THE SOFTWARE Or THE USE Or OTHER DEALINGS IN THE SOFTWARE.
;  */
; 
; /*
;  * The text above constitutes the entire PortAudio license; however, 
;  * the PortAudio community also makes the following non-binding requests:
;  *
;  * Any person wishing To distribute modifications To the Software is
;  * requested To send the modifications To the original developer so that
;  * they can be incorporated into the canonical version. It is also 
;  * requested that these non-binding requests be included along With the 
;  * license above.
;  */

Enumeration ; PaErrorCode
  #paNoError = 0

  #paNotInitialized = -10000
  #paUnanticipatedHostError
  #paInvalidChannelCount
  #paInvalidSampleRate
  #paInvalidDevice
  #paInvalidFlag
  #paSampleFormatNotSupported
  #paBadIODeviceCombination
  #paInsufficientMemory
  #paBufferTooBig
  #paBufferTooSmall
  #paNullCallback
  #paBadStreamPtr
  #paTimedOut
  #paInternalError
  #paDeviceUnavailable
  #paIncompatibleHostApiSpecificStreamInfo
  #paStreamIsStopped
  #paStreamIsNotStopped
  #paInputOverflowed
  #paOutputUnderflowed
  #paHostApiNotFound
  #paInvalidHostApi
  #paCanNotReadFromACallbackStream      ;/**< @todo review error code name */
  #paCanNotWriteToACallbackStream       ;/**< @todo review error code name */
  #paCanNotReadFromAnOutputOnlyStream   ;/**< @todo review error code name */
  #paCanNotWriteToAnInputOnlyStream     ;/**< @todo review error code name */
  #paIncompatibleStreamHostApi
  #paBadBufferPtr
EndEnumeration

#paNoDevice = (-1)
#paUseHostApiSpecificDeviceSpecification = (-2)

Enumeration ; PaHostApiTypeId
  #paInDevelopment = 0 ; /* use while developing support for a new host API */
  #paDirectSound = 1
  #paMME = 2
  #paASIO = 3
  #paSoundManager = 4
  #paCoreAudio = 5
  #paOSS = 7
  #paALSA = 8
  #paAL = 9
  #paBeOS = 10
  #paWDMKS = 11
  #paJACK = 12
  #paWASAPI = 13
  #paAudioScienceHPI = 14
EndEnumeration

#paFloat32        = ($00000001)
#paInt32          = ($00000002)
#paInt24          = ($00000004)
#paInt16          = ($00000008)
#paInt8           = ($00000010)
#paUInt8          = ($00000020)
#paCustomFormat   = ($00010000)
#paNonInterleaved = ($80000000)

#paFormatIsSupported = (0)
#paFramesPerBufferUnspecified = (0)
#paNoFlag          = (0)
#paClipOff         = ($00000001)
#paDitherOff       = ($00000002)
#paNeverDropInput  = ($00000004)
#paPrimeOutputBuffersUsingStreamCallback = ($00000008)
#paPlatformSpecificFlags = ($FFFF0000)

#paInputUnderflow   = ($00000001)
#paInputOverflow    = ($00000002)
#paOutputUnderflow  = ($00000004)
#paOutputOverflow   = ($00000008)
#paPrimingOutput    = ($00000010)

Enumeration ; PaStreamCallbackResult
  #paContinue=0
  #paComplete=1
  #paAbort=2
EndEnumeration

Structure PaHostApiInfo
  structVersion.l
  type.l
 *name.b
  deviceCount.l
  defaultInputDevice.l
  defaultOutputDevice.l
EndStructure

Structure PaHostErrorInfo
  hostApiType.l   
  errorCode.l            
 *errorText.b          
EndStructure

Structure PaDeviceInfo
  structVersion.l
 *name.b
  hostApi.l
  maxInputChannels.l
  maxOutputChannels.l
  defaultLowInputLatency.d
  defaultLowOutputLatency.d
  defaultHighInputLatency.d
  defaultHighOutputLatency.d
  defaultSampleRate.d
EndStructure

;PaDeviceIndex 	device
;int 	channelCount
;PaSampleFormat 	sampleFormat
;PaTime 	suggestedLatency
;void * 	hostApiSpecificStreamInfo
  
Structure PaStreamParameters
  device.l
  channelCount.l
  sampleFormat.l
  ;pad1.l ; FOR STRUCTURE ALIGNMENT -- MAY NO LONGER BE NECESSARY IN PUREBASIC?
  suggestedLatency.d
 *hostApiSpecificStreamInfo
  ;pad2.l ; FOR STRUCTURE ALIGNMENT -- MAY NO LONGER BE NECESSARY IN PUREBASIC?
EndStructure

Structure PaStreamCallbackTimeInfo
  inputBufferAdcTime.d
  currentTime.d
  outputBufferDacTime.d
EndStructure

Structure PaStreamInfo
  structVersion.l
  inputLatency.d
  outputLatency.d
  sampleRate.d
EndStructure

PrototypeC PaStreamCallback(*input, *output, frameCount, *timeInfo, statusFlags, *userdata)
PrototypeC PaStreamFinishedCallback(*user_data)

#paWinWasapiExclusive                = 1 << 0 ;puts WASAPI into exclusive mode
#paWinWasapiRedirectHostProcessor    = 1 << 1 ;allows To skip internal PA processing completely
#paWinWasapiUseChannelMask           = 1 << 2 ;assigns custom channel mask
#paWinWasapiPolling                  = 1 << 3 ;selects non-Event driven method of
;Data Read/write Note: WASAPI Event driven core is capable of 2ms latency, but Polling method
;can only provide 15-20ms latency.
#paWinWasapiThreadPriority           = 1 << 4 ;forces custom thread priority setting.
; must be used If PaWasapiStreamInfo::threadPriority is set To custom value.

;/* Host processor. Allows To skip internal PA processing completely. 
;   You must set paWinWasapiRedirectHostProcessor flag To PaWasapiStreamInfo::flags member
;   in order To have host processor redirected To your callback.
;   Use With caution! inputFrames And outputFrames depend solely on final device setup (buffer
;   size is just recommendation) but are Not changing during run-time once stream is started.
;*/
;typedef void (*PaWasapiHostProcessorCallback) (void *inputBuffer,  long inputFrames,
;                                               void *outputBuffer, long outputFrames,
;                                               void *userData);

Enumeration ;Device role
    #eRoleRemoteNetworkDevice = 0
    #eRoleSpeakers
    #eRoleLineLevel
    #eRoleHeadphones
    #eRoleMicrophone
    #eRoleHeadset
    #eRoleHandset
    #eRoleUnknownDigitalPassthrough
    #eRoleSPDIF
    #eRoleHDMI
    #eRoleUnknownFormFactor
EndEnumeration

Enumeration ;Thread priority
    #eThreadPriorityNone = 0
    #eThreadPriorityAudio ;Default For Shared mode.
    #eThreadPriorityCapture
    #eThreadPriorityDistribution
    #eThreadPriorityGames
    #eThreadPriorityPlayback
    #eThreadPriorityProAudio ;Default For Exclusive mode.
    #eThreadPriorityWindowManager
EndEnumeration

;Stream descriptor
Structure PaWasapiStreamInfo
  size.l ;= 32 ;SizeOf(PaWasapiStreamInfo)
  hostApiType.l ;#paWASAPI   ;PaHostApiTypeId hostApiType;    ;paWASAPI
  version.l          ;1
  flags.l            ;collection of PaWasapiFlags

;    /* Support For WAVEFORMATEXTENSIBLE channel masks. If flags contains
;       paWinWasapiUseChannelMask this allows you To specify which speakers 
;       To address in a multichannel stream. Constants For channelMask
;       are specified in pa_win_waveformat.h. Will be used only If 
;       paWinWasapiUseChannelMask flag is specified.
;    */
;    PaWinWaveFormatChannelMask channelMask
  channelMask.l

;    /* Delivers raw Data To callback obtained from GetBuffer() methods skipping 
;       internal PortAudio processing inventory completely. userData parameter will 
;       be the same that was passed To Pa_OpenStream method. Will be used only If 
;       paWinWasapiRedirectHostProcessor flag is specified.
;    */
;    PaWasapiHostProcessorCallback hostProcessorOutput
;    PaWasapiHostProcessorCallback hostProcessorInput

    hostProcessorOutput.l
    hostProcessorInput.l

;    /* Specifies thread priority explicitly. Will be used only If paWinWasapiThreadPriority flag
;       is specified.

;       Please note, If Input/Output streams are opened simultaniously (Full-Duplex mode)
;       you shall specify same value For threadPriority Or othervise one of the values will be used
;       To setup thread priority.
;    */
;    PaWasapiThreadPriority threadPriority
    threadPriority.l

EndStructure


;/* Returns Default sound format For device. Format is represented by PaWinWaveFormat Or 
;   WAVEFORMATEXTENSIBLE Structure.

; @param pFormat pointer To PaWinWaveFormat Or WAVEFORMATEXTENSIBLE Structure.
; @param nFormatSize pize of PaWinWaveFormat Or WAVEFORMATEXTENSIBLE Structure in bytes.
; @param nDevice device index.

 ;@return A non-negative value indicating the number of bytes copied into format decriptor
 ;        Or, a PaErrorCode (which are always negative) If PortAudio is Not initialized
 ;        Or an error is encountered.
;*/
;int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice );


;/* Returns device role (PaWasapiDeviceRole enum).

 ;@param nDevice device index.

; @return A non-negative value indicating device role Or, a PaErrorCode (which are always negative)
;         If PortAudio is Not initialized Or an error is encountered.
;*/

;int/*PaWasapiDeviceRole*/ PaWasapi_GetDeviceRole( PaDeviceIndex nDevice );
;int PaWasapi_GetDeviceRole( PaDeviceIndex nDevice )

;/* Boost thread priority of calling thread (MMCSS). Use it For Blocking Interface only For thread
;    which makes calls To Pa_WriteStream/Pa_ReadStream.

; @param hTask a handle To pointer To priority task. Must be used With PaWasapi_RevertThreadPriority
;              method To revert thread priority To initial state.

 ;@param nPriorityClass an Id of thread priority of PaWasapiThreadPriority type. Specifying 
 ;                      eThreadPriorityNone does nothing.

; @return Error code indicating success Or failure.
; @see PaWasapi_RevertThreadPriority
;*/

;PaError PaWasapi_ThreadPriorityBoost( void **hTask, PaWasapiThreadPriority nPriorityClass );

;/** Boost thread priority of calling thread (MMCSS). Use it For Blocking Interface only For
;    thread which makes calls To Pa_WriteStream/Pa_ReadStream.

; @param hTask Task handle obtained by PaWasapi_BoostThreadPriority method.
; @return Error code indicating success Or failure.
; @see PaWasapi_BoostThreadPriority
;*/
;PaError PaWasapi_ThreadPriorityRevert( void *hTask );


;IMPORTANT:

;WASAPI is implemented For Callback And Blocking interfaces. It supports Shared And
;Exclusive share modes. 
    
;    Exclusive Mode:

;        Exclusive mode allows To deliver audio Data directly To hardware bypassing
;        software mixing.
;        Exclusive mode is specified by 'paWinWasapiExclusive' flag.

;    Callback Interface:

;        Provides best audio quality With low latency. Callback Interface is implemented in 
;        two versions:

;        1) Event-Driven:
;        This is the most powerful WASAPI implementation which is capable to provide
;        glitch-free audio at 2ms latency in Exclusive mode. Lowest possible latency For
;        this mode is usually - 2ms For HD Audio class audio chips (including on-board audio,
;        2ms was achieved on Realtek ALC888/S/T). For Shared mode latency can Not go lower
;        than 20ms.

;        2) Poll-Driven:
;        Polling is another method To operate With WASAPI. It is less efficient than
;        Event-Driven and provides latency at around 12-13ms. Polling must be used To
;        overcome a system bug under Windows Vista x64 when application is WOW64(32-bit)
;        And Event-Driven method simply times out (event handle is never signalled on buffer
;        completion). Please note, such Vista bug does Not exist in Windows 7 x64.
;        Polling is setup by speciying 'paWinWasapiPolling' flag.
;        Thread priority can be boosted by specifying 'paWinWasapiBlockingThreadPriorityPro'
;        flag.

;    Blocking Interface:

;        Blocking Interface is implemented but due To above described Poll-Driven method can
;        Not deliver low latency audio. Specifying too low latency in Shared mode will
;        result in distorted audio although Exclusive mode adds stability.

;    Pa_IsFormatSupported:

;        To check format With correct Share Mode (Exclusive/Shared) you must supply
;        PaWasapiStreamInfo With flags paWinWasapiExclusive set through member of 
;        PaStreamParameters::hostApiSpecificStreamInfo Structure.

;    Pa_OpenStream:

;        To set desired Share Mode (Exclusive/Shared) you must supply
;        PaWasapiStreamInfo With flags paWinWasapiExclusive set through member of 
;        PaStreamParameters::hostApiSpecificStreamInfo Structure.

ImportC "portaudio_x86.lib"
  Pa_GetVersion()
  Pa_GetVersionText()
  Pa_GetErrorText(errorCode)
  Pa_Initialize()
  Pa_Terminate()
  Pa_GetHostApiCount()
  Pa_GetDefaultHostApi()
  Pa_GetHostApiInfo(hostApi)
  Pa_HostApiTypeIdToHostApiIndex(type)
  Pa_HostApiDeviceIndexToDeviceIndex(hostApi, hostApiDeviceIndex)
  Pa_GetLastHostErrorInfo()
  Pa_GetDeviceCount()
  Pa_GetDefaultInputDevice()
  Pa_GetDefaultOutputDevice()
  Pa_GetDeviceInfo(device)
  Pa_IsFormatSupported(*inputParameters, *outputParameters, sampleRate.d)
  Pa_OpenStream(*stream.l, *inputParameters, *outputParameters, sampleRate.d, framesPerBuffer, streamFlags, *streamCallback, *user_data)
  Pa_OpenDefaultStream(*stream.l, numInputChannels, numOutputChannels, sampleFormat, sampleRate.d, framesPerBuffer, *streamCallback, *user_data)
  Pa_CloseStream(*stream)
  Pa_SetStreamFinishedCallback(*stream, *streamFinishedCallback)
  Pa_StartStream(*stream)
  Pa_StopStream(*stream)
  Pa_AbortStream(*stream)
  Pa_IsStreamStopped(*stream)
  Pa_IsStreamActive(*stream)
  Pa_GetStreamInfo(*stream)
  Pa_GetStreamTime.d(*stream)
  Pa_GetStreamCpuLoad.d(*stream)
  Pa_ReadStream(*stream, *buffer, frames)
  Pa_WriteStream(*stream, *buffer, frames)
  Pa_GetStreamReadAvailable(*stream)
  Pa_GetStreamWriteAvailable(*stream)
  Pa_GetSampleSize(format)
  Pa_Sleep(msec)
  
  PaWasapi_GetDeviceDefaultFormat(*pFormat, nFormatSize.l, nDevice.l)
  PaWasapi_GetDeviceRole(nDevice.l)
  PaWasapi_ThreadPriorityBoost(*hTask.l, nPriorityClass.l)
  PaWasapi_ThreadPriorityRevert(*hTask.l)
  PaWasapi_GetFramesPerHostBuffer(*pStream, *nInput, *nOutput)
  PaWasapi_GetJackCount(nDevice.l, *jcount);
  PaWasapi_GetJackDescription(nDevice.l, jindex.l, *pJackDescription);

EndImport
chris319
Enthusiast
Enthusiast
Posts: 782
Joined: Mon Oct 24, 2005 1:05 pm

Re: PortAudio for PB

Post by chris319 »

Here is a demo program for recording audio using PortAudio. You will need a dll and a lib file to compile it (or the OS X/Linux equivalents) as well as PortAudio.pb found elsewhere in this thread.

This is a cut down version of a more functional recording program. It still has some rough edges. Code for metering and timing the length of the recording have been eliminated so that you can have the fun of writing them yourself. It does demonstrate some techniques any PortAudio program should use, such as:

- Discovering and enumerating hardware devices

- Working with audio streams in PortAudio

- Multi-threading -- There is a separate thread for writing data to disk which runs separately from the PortAudio callback. This is very important because you don't want system tasks such as disk writing to block the callback (important in real-time programming). If you implement an audio level meter you will want to encapsulate that in a separate thread for the same reason.

- File buffering -- uses FileBuffersSize() to force disk buffering of the audio buffer so that WriteData() does not return until disk write is finished, making writeFlag a valid indicator of disk activity.

- Ring buffering -- This is not a necessity but is a feature I added to test the PB ring buffer written by infratec. At present it starts recording when you launch the program rather than when you press the record button, but you can fix that. Ring buffers are commonly used in this type of program, but my preferred method of buffering is dynamic and adjusts the buffer size according to the amount of data present. In recording you never want an overflow, i.e. the data arrives faster than it can be written to a storage medium (one alternative would be to allocate a huge RAM buffer -- around 2 GB -- and write the data to hard disk when the recording is stopped).

http://www.portaudio.com

Code: Select all

;PA RINGBUFFER DEMO.PB
;USES RING BUFFER
;WORKS WITH PUREBASIC 5.00
;UPDATED BY CHRIS319 ON 2/3/2013
;RingBuffer implementation by infratec
;CUT-DOWN VERSION FOR POSTING ON PB FORUM

;STRUCTURE MEMBER ALIGNMENT MUST BE 4 BYTES WHEN COMPILING DLL

XIncludeFile "PortAudio.pb"

Enumeration
  #RingBuffer_Getmode
  #RingBuffer_Peekmode
EndEnumeration

Structure RingBufferStructure
  *RingBuffer
  Size.i
  ReadPtr.i
  WritePtr.i
EndStructure

;With the optional parameter Mode = #RingBuffer_Peekmode of RingBuffer_Get(),
;you can get Data without removing them from the buffer.

Global RB1.RingBufferStructure

Procedure.i RingBufferInit(*RB.RingBufferStructure, Size)

      *RB\RingBuffer = AllocateMemory(Size)
      If *RB\RingBuffer <> 0
        *RB\Size = Size
        *RB\ReadPtr = 0
        *RB\WritePtr = 0
      Else
        *RB\Size = 0
      EndIf
     
      ProcedureReturn *RB\Size
     
    EndProcedure

    Procedure.i RingBufferBytesToRead(*RB.RingBufferStructure)
     
      Protected Result.i
     
      If *RB\WritePtr = *RB\ReadPtr
        Result = 0
      ElseIf *RB\WritePtr > *RB\ReadPtr
        Result = *RB\WritePtr - *RB\ReadPtr
      Else
        Result = *RB\Size - (*RB\ReadPtr - *RB\WritePtr)
      EndIf
     
      ProcedureReturn Result
     
    EndProcedure

Procedure.i RingBufferPut(*RB.RingBufferStructure, *Src, Bytes)
     
      Protected Result.i, Extra.i
     
      ;Result = #False
     
      If *RB\Size - RingBufferBytesToRead(*RB.RingBufferStructure) >= Bytes
        If *RB\WritePtr + Bytes <= *RB\Size
          CopyMemory(*Src, *RB\RingBuffer + *RB\WritePtr, Bytes)
          *RB\WritePtr + Bytes
        Else
          Extra = *RB\Size - *RB\WritePtr
          CopyMemory(*Src, *RB\RingBuffer + *RB\WritePtr, Extra)
          CopyMemory(*Src + Extra, *RB\RingBuffer, Bytes - Extra)
          *RB\WritePtr = Bytes - Extra
        EndIf
       
        Result = #True

      Else
        Result = #False
      EndIf
     
      ProcedureReturn Result
     
EndProcedure

;    Procedure.i RingBufferGet(*RB.RingBufferStructure, *Dst, Bytes.i, Mode.i = #RingBuffer_Getmode)
Procedure.i RingBufferGet(*RB.RingBufferStructure, *Dst, Bytes.i)
     
      Protected AvailableBytes.i, Part.i
     
      AvailableBytes = RingBufferBytesToRead(*RB.RingBufferStructure)
      If AvailableBytes > 0
        If AvailableBytes < Bytes : Bytes = AvailableBytes : EndIf
        If *RB\Size - *RB\ReadPtr >= Bytes
          CopyMemory(*RB\RingBuffer + *RB\ReadPtr, *Dst, Bytes)
;          If Mode = #RingBuffer_Getmode : 
*RB\ReadPtr + Bytes
; : EndIf
        Else
          Part = *RB\Size - *RB\ReadPtr
          CopyMemory(*RB\RingBuffer + *RB\ReadPtr, *Dst, Part)
          CopyMemory(*RB\RingBuffer, *Dst + Part, Bytes - Part)
;          If Mode = #RingBuffer_Getmode : 
*RB\ReadPtr = Bytes - Part
; : EndIf
        EndIf
      Else
        Bytes = 0
      EndIf
     
      ProcedureReturn Bytes
     
EndProcedure

    Procedure RingBufferClear(*RB.RingBufferStructure)
      *RB\ReadPtr = 0
      *RB\WritePtr = 0
    EndProcedure

    Procedure RingBufferFree(*RB.RingBufferStructure)
      FreeMemory(*RB\RingBuffer)
      *RB\Size = 0
      *RB\ReadPtr = 0
      *RB\WritePtr = 0
    EndProcedure

CompilerIf(#PB_Compiler_OS = #PB_OS_Windows)
Global WasapiInfo.PaWasapiStreamInfo
WasapiInfo\size = SizeOf(PaWasapiStreamInfo)
WasapiInfo\hostApiType = #paWASAPI
WasapiInfo\version = 1
WasapiInfo\flags = #paWinWasapiExclusive | #paWinWasapiPolling
WasapiInfo\channelMask = #Null
WasapiInfo\hostProcessorOutput = #Null
WasapiInfo\hostProcessorInput = #Null
WasapiInfo\threadPriority = #Null

;Global WasapiPlaybackInfo.PaWasapiStreamInfo ;FOR OUTPUT DEVICE IN NON-EXCLUSIVE MODE
;WasapiPlaybackInfo\size = SizeOf(PaWasapiStreamInfo)
;WasapiPlaybackInfo\hostApiType = #paWASAPI
;WasapiPlaybackInfo\version = 1
;WasapiPlaybackInfo\flags = #Null ;NO EXCLUSIVE MODE FOR OUTPUT DEVICE!
;WasapiPlaybackInfo\channelMask = #Null
;WasapiPlaybackInfo\hostProcessorOutput = #Null
;WasapiPlaybackInfo\hostProcessorInput = #Null
;WasapiPlaybackInfo\threadPriority = #Null

CompilerEndIf

Global bct ;FOR TESTING RING BUFFER
Global *bigBufferSize, *bufLimit, slash$, recBoxFlag = #False
Global maxBytes.q ;= (2 * 1024 * 1024 * 1024) - (*bigBufferSize * 2) ;2147483648 -- 2 GB MAXIMUM FILE SIZE
;Global maxBytes.q = 1 * 1024 * 1024 * 1024 ; 1 GB MAXIMUM FILE SIZE
;Global maxBytes.q = 100 * 8192 ;FOR TESTING

#MENU_MARGIN = 22: #TEXT_Y = 380
#STOP_RED = $333377: #BRIGHT_RED = $3333ff
#LEFT_EDGE = 50
#RECBOX_X = 20: #RECBOX_Y = 5: #RECBOX_WIDTH = 97: #RECBOX_HEIGHT = 75

#BUFFER_DURATION = 30 * 1000;ms 30 SECONDS
;#BUFFER_DURATION = 125 ;ms

Global NO_DRAG_Y
#TIMER_CONFIRM_HEIGHT = 90 ;HEIGHT OF TIMER AND CONFIRM TEXT GADGETS
#BG_COLOR = $eeeeee
#BUTTON_WIDTH = 60

Global center, bytesToWrite, quantity, audioSemaphore, fileBufferSize
Global firstPass, stopFlag, quitFlag, file_name$, *startToWrite
Global *buffer1, *dealloc1, *buffer2, *dealloc2 ;, *monitorBuffer
Global writeFlag, recordedBytes, audioLoopContinue
Global *null = 0
Global Dim *Devices.PaDeviceInfo(0)
Global Dim *Devices(0)

Structure SAllocation
  Size.i
  File.s
  Line.i
  Pointer.i
EndStructure

Global NewList Memories.SAllocation() ;FOR DETECTING MEMORY LEAKS

Procedure _AllocateMemory3(Size, File.s, Line.i)
  AddElement(Memories())
  Memories()\Size = Size
  Memories()\File = File
  Memories()\Line = Line
  Memories()\Pointer = AllocateMemory(Size)
  ProcedureReturn Memories()\Pointer
EndProcedure

Macro AllocateMemory3(Size)
  _AllocateMemory3(Size, #PB_Compiler_File, #PB_Compiler_Line)
EndMacro

Procedure FreeMemory3(Memory)
  ForEach Memories()
    If Memories()\Pointer = Memory
      DeleteElement(Memories())
      Break
    EndIf
  Next
If Memory <> #Null: FreeMemory(Memory): EndIf
EndProcedure

;ShowVariableViewer()

Global framesPerBuffer

Global sampleRate.d, secns
Global byteCount, inputbox, outputbox

Global source_device, dest_device, filehandle1, bytesRecorded
Global in_streamparms.PaStreamParameters, out_streamparms.PaStreamParameters
Global *my_Stream, *my_Stream2, *my_Stream3, file_length, offset.q, writeAudioFlag, overflowCount
Global Dim device$(255)

Global scale.f, sample.w, float_sample.f, min.f, bar_height.l, buf_addr.l
Global window_height.l, bar_y.l, rec_end.l, fullscale.w, File.s

Global chunksize.l, subchunk1ID.l, subchunk1Size.l, audioformat.w, cutCounter
Global byterate.l, blockalign.u, bitsPerSample.u, subchunk2size.l, ct.l, tempword.w

Global servct = 0, QuitRec.l, program_running.l

Global scale.f, sample.w, float_sample.f, min.f, bar_height.l, buf_addr.l, frameCount, bytesPerFrame
Global window_width.l, window_height.l, bar_y.l, rec_end.l, fullscale.w

Global subchunk1id.l, audioformat.w, buffersize, record_minutes.f, avail_mem.q
Global ct.l, stream_open, record_seconds.l

Global elapsed_seconds.l, remaining_seconds.l, recording_now, ch2Offset
Global hours.s, minutes.s, seconds.s, DeviceName.s
Global draw_bkgd.l, leftY, rightY, device_offset, deviceCount, totalBytes.q

Global format, sampleformat, threadID1, killThread1 = #False
Global channels.u, bitDepth.u, SampleRate.d

Structure MYWAVEFORMATEX
  wFormatTag.u
  nChannels.u
  nSamplesPerSec.l
  nAvgBytesPerSec.l
  nBlockAlign.u
  wBitsPerSample.u
  cbSize.u
EndStructure

Structure WAVEFORMATEXTENSIBLE
  format.MYWAVEFORMATEX ;WAVEFORMATEX STRUCTURE
  StructureUnion
    wValidBitsPerSample.u
    wSamplesPerBlock.u
    wReserved.u
  EndStructureUnion
  dwChannelMask.l
  CompilerIf(#PB_Compiler_OS = #PB_OS_Windows)
    SubFormat.GUID
  CompilerElse
    SubFormat.s{16}
  CompilerEndIf
EndStructure

Global my_WFE.WAVEFORMATEXTENSIBLE

;Global *my_HOSTERROR.PaHostErrorInfo

#WAVE_FORMAT_PCM        = $0001 ;PCM <= 2 CHANNELS
#WAVE_FORMAT_EXTENSIBLE	=	$FFFE ;PCM >  2 CHANNELS

#MONO = 1
#STEREO = 2

Enumeration 0 ;_AUDCLNT_SHAREMODE
  #AUDCLNT_SHAREMODE_SHARED
  #AUDCLNT_SHAREMODE_EXCLUSIVE
EndEnumeration    

Enumeration 0 ;GADGETS
  #gadStart
  #gadStop
  #gadChannels
  #gadBitDepth
  #gadSampleRate
  #gadDevice
  #gadRecord
;  #gadSave
  #gadOK
  #gadSettings
  #gadCutCounter
  #gadNextCut
  #gadExit
  #gadText1 ;CHANNELS
  #gadText2 ;BIT DEPTH
  #gadText3 ;SAMPLE RATE
  #gadText4 ;INPUT DEVICE
  #gadText6 ;RECORDING TOO LONG
  #gadText7 ;BUFFER OVERFLOW
  #gadText8 ;FILE NAME
  #gadtoRecord
  #gadtoStop
  #gadtoExit
  #gadRecording ;MESSAGE THAT WE ARE RECORDING NOW
  #gadEndOfFile ;NO MORE RECORDING SPACE
  #gadConfirm
  #gadNextCutText
EndEnumeration

;==============================================================================

Procedure.s Pa_PaHostApiIndexToString(Ind)
  *info.PaHostApiInfo = Pa_GetHostApiInfo(Ind)
  ProcedureReturn PeekS(*info\name)
EndProcedure

Procedure.s DeviceString(*dev.PaDeviceInfo)
  ProcedureReturn PeekS(*dev\name) + " " + Pa_PaHostApiIndexToString(*dev\hostApi)
EndProcedure

Procedure RecBox(boxColor)
  Box(#RECBOX_X, #RECBOX_Y, #RECBOX_WIDTH, #RECBOX_HEIGHT - 61, boxColor)
  Box(#RECBOX_X, #RECBOX_Y + 55, #RECBOX_WIDTH, #RECBOX_HEIGHT - 61, boxColor)
  Box(#RECBOX_X, #RECBOX_Y + 14, #RECBOX_WIDTH - 78, #RECBOX_HEIGHT - 34, boxColor)
  Box(#RECBOX_X + 80, #RECBOX_Y + 14, #RECBOX_WIDTH - 80, #RECBOX_HEIGHT - 34, boxColor)
EndProcedure

Procedure Record_Stop()
If recording_now = #True

;While writeFlag = #False: Wend ;IS THE DISK DRIVE BUSY?
;  SignalSemaphore(audioSemaphore) ;DATA-WRITE SEMAPHORE

  recording_now = #False

  Repeat: Until offset = 0; WRITE LAST BIT OF AUDIO

  SetGadgetText(#gadRecording, "")
  cutCounter + 1: SetGadgetState(#gadCutCounter, cutCounter)
  recBoxFlag = #STOP_RED
  StickyWindow(1, #False)

  DisableGadget(#gadRecord, 0)
  DisableGadget(#gadStop, 1)
  
  CloseFile(1)
  OpenFile(1, file_name$)
  subchunk2size.l = totalBytes.q

  FileSeek(1, 4): WriteLong(1, chunksize + subchunk2size) ;WRITE CHUNKSIZE
  If my_wfe\format\wFormatTag = #WAVE_FORMAT_EXTENSIBLE
    FileSeek(1, 64): WriteLong(1, subchunk2size) ;WRITE SUBCHUNK2SIZE
  Else
    FileSeek(1, 42): WriteLong(1, subchunk2size) ;WRITE SUBCHUNK2SIZE
  EndIf

  CloseFile(1)
EndIf
EndProcedure

Procedure ShutDown()
  If recording_now = #True: Record_Stop(): EndIf
  audioLoopContinue = #False: Delay(250)
  Pa_StopStream(*my_stream): Pa_CloseStream(*my_stream)
  Pa_Terminate()
  killThread1 = #True: SignalSemaphore(audioSemaphore)
  FreeMemory3(*dealloc1): FreeMemory3(*dealloc2)
  RingBufferFree(@RB1)
  StopDrawing()
  CloseWindow(1)
  End
EndProcedure

Procedure GetDevices()
;;;IMPORTED CODE
Dim channel$(100)
;Global Dim *Devices.PaDeviceInfo(0)

;deviceCount.i
;DefaultOutputIndex.i
;DefaultInputIndex.i

;Enumerate devices  ;THIS CODE HAS BEEN MOVED TO GetSettings()
;If deviceCount = 0
;  MessageRequester("Error", "No recording devices found.")
;  End ;shutdown()
;EndIf

;deviceCount = Pa_GetDeviceCount()
;Global Dim *Devices(deviceCount)

Global Dim api_source$(deviceCount) ;INPUT
Global Dim api_dest$(deviceCount) ;OUTPUT

For I = 0 To deviceCount - 1
  *Devices(I) = Pa_GetDeviceInfo(I)
Next

K = 0 ;GET INPUT DEVICE
For I = 0 To deviceCount - 1
  If *Devices(I)\maxInputChannels > 0
    ;device$(I) = PeekS(*Devices(I)\name)
    device$(I) = DeviceString(*Devices(I))
    channel$(I) = Str(*Devices(I)\maxInputChannels)

    ct2 = Len(device$(I))
    While Mid(device$(I), ct2, 1) <> " "
      ct2 - 1
    Wend
    api_source$(I) = Right(device$(I), Len(Device$(I)) - ct2)

      If source_device = 0: source_device = I: EndIf ;SET TO FIRST-DISCOVERED DEVICE
      AddGadgetItem(#gadDevice, -1, device$(I) + "  |  " + channel$(I) + " channels")
      SetGadgetItemData(#gadDevice, K, I)
      K + 1

  EndIf

Next

; If device$(0) = ""
;   MessageRequester("Error", "No recording devices found.")
;   shutdown()
; EndIf

EndProcedure  

ProcedureC PaStreamCallback(*recordBuffer, *null, frameCount.l, *timeInfo.PaStreamCallbackTimeInfo, statusFlags, *userdata)
recordedBytes = frameCount * bytesPerFrame

RingBufferPut(@RB1, *recordBuffer, recordedBytes)

; ;For TESTING RING BUFFER -- SIMULATES DISK DRIVE BEING BUSY
; Debug bct
; If bct < 4
; bct + 1: blaFlag = #True
; Else
; bct = 0: blaFlag = #False
; EndIf
; If blaFlag = #False ;FOR TESTING RING BUFFER

;bytesToWrite = recordedBytes ;quantity: *startToWrite = *buffer1 + offset
If writeFlag = #False ;IS THE DISK DRIVE BUSY?
  SignalSemaphore(audioSemaphore) ;DATA-WRITE SEMAPHORE
;  quantity = 0
EndIf

ProcedureReturn #paContinue
EndProcedure

Procedure StreamStart()
result = Pa_IsFormatSupported(@in_streamparms, 0, sampleRate)
If result <> #paFormatIsSupported
  *err = Pa_GetErrorText(result): error.string = @*err
  CompilerIf(#PB_Compiler_OS = #PB_OS_Windows)
    MessageRequester("Source format not supported", error\s+"."+Chr(10)+"Make sure device is in exclusive mode.")
  CompilerElse  
    MessageRequester("Source format not supported", error\s)
  CompilerEndIf
    shutdown()
EndIf

result = Pa_OpenStream(@*my_stream, @in_streamparms, 0, sampleRate, framesPerBuffer, #paDitherOff|#paClipOff, @PaStreamCallback(), @in_streamparms\hostApiSpecificStreamInfo)
If result <> #paNoError
  *err = Pa_GetErrorText(result): error.string = @*err
  MessageRequester("Unable to open input stream", error\s)
  shutdown()
EndIf

result = Pa_StartStream(*my_stream)
If result <> #paNoError
  *err = Pa_GetErrorText(result): error.string = @*err
  MessageRequester("Unable to start input stream", error\s)
  shutdown()
EndIf

EndProcedure

Procedure GetSettings()
active = Pa_IsStreamActive(*my_stream)
If active = #True
  Pa_StopStream(*my_stream)
  Pa_CloseStream(*my_stream)
EndIf

;FIND OUT IF THERE ARE ANY RECORDING DEVICES
deviceCount = Pa_GetDeviceCount()
If deviceCount = 0
  MessageRequester("Error", "No audio devices found."): Pa_Terminate(): End
EndIf

ReDim *Devices(deviceCount)
For I = 0 To deviceCount - 1
  *Devices(I) = Pa_GetDeviceInfo(I)
Next

availableRecordDevices = #Null
For I = 0 To deviceCount - 1
  If *Devices(I)\maxInputChannels > 0
    availableRecordDevices = #True
  EndIf
Next

If availableRecordDevices = #Null
  MessageRequester("Error", "No recording devices found."): Pa_Terminate(): End
EndIf

OpenWindow(2, 190, 350, 620, 260, "Control Panel", #PB_Window_SystemMenu)
SetWindowColor(2, #BG_COLOR)
ComboBoxGadget(#gadChannels, 20, 50, 85, 20) 
ComboBoxGadget(#gadBitDepth, 115, 50, 90, 20) 
ComboBoxGadget(#gadSampleRate, 215, 50, 110, 20)
ComboBoxGadget(#gadDevice, 20, 110, 500, 20)

getDevices()

ButtonGadget(#gadOK, (WindowWidth(2) / 2) - 30, 215, 60, 30, "OK", #PB_Button_Default)
AddKeyboardShortcut(2, #PB_Shortcut_Return, 1) ;ENTER TO ACCEPT SETTINGS
TextGadget(#gadText1, 34, 25,85,20, "Channels")
TextGadget(#gadText2, 131, 25,85,20, "Bit Depth")
TextGadget(#gadText3, 230, 25,85,20, "Sample Rate")
TextGadget(#gadText4, 250, 85, 85,20, "Input Device")
SetGadgetColor(#gadText1, #PB_Gadget_BackColor, #BG_COLOR)
SetGadgetColor(#gadText2, #PB_Gadget_BackColor, #BG_COLOR)
SetGadgetColor(#gadText3, #PB_Gadget_BackColor, #BG_COLOR)
SetGadgetColor(#gadText4, #PB_Gadget_BackColor, #BG_COLOR)

result = OpenPreferences("." + slash$ + "PA Recorder.ini")
If result = #False
  MessageRequester("Warning", "Unable to read preferences file.")
EndIf

temp.l = ReadPreferenceLong("channels", 2)
channels = temp
temp.l = ReadPreferenceLong("bitdepth", 24)
bitdepth = temp
tempd.d = ReadPreferenceDouble("samplerate", 44100)
sampleRate = tempd

ClosePreferences()

;AddKeyboardShortcut(2, #PB_Shortcut_Return, 1)
;AddKeyboardShortcut(2, #PB_Shortcut_O, 1)

  AddGadgetItem(#gadChannels, -1, "Mono")
  AddGadgetItem(#gadChannels, -1, "Stereo")
  SetGadgetState(#gadChannels, channels - 1)
  
  AddGadgetItem(#gadBitDepth,-1,"16 bits") 
  AddGadgetItem(#gadBitDepth,-1,"24 bits") 

  If bitDepth = 16
    SetGadgetState(#gadBitDepth, 0)
  ElseIf bitDepth = 24
    SetGadgetState(#gadBitDepth, 1)
  EndIf
  
  AddGadgetItem(#gadSampleRate,-1,"6000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"7200 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"8000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"11025 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"12000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"16000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"22050 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"24000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"32000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"44100 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"48000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"64000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"88200 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"96000 Hz") 
  AddGadgetItem(#gadSampleRate,-1,"192000 Hz") 
  Select sampleRate
    Case 6000
      SetGadgetState(#gadSampleRate, 0)
    Case 7200
      SetGadgetState(#gadSampleRate, 1)
    Case 8000
      SetGadgetState(#gadSampleRate, 2)
    Case 11025
      SetGadgetState(#gadSampleRate, 3)
    Case 12000
      SetGadgetState(#gadSampleRate, 4)
    Case 16000
      SetGadgetState(#gadSampleRate, 5)
    Case 22050
      SetGadgetState(#gadSampleRate, 6)
    Case 24000
      SetGadgetState(#gadSampleRate, 7)
    Case 32000
      SetGadgetState(#gadSampleRate, 8)
    Case 44100
      SetGadgetState(#gadSampleRate, 9)
    Case 48000
      SetGadgetState(#gadSampleRate, 10)
    Case 64000
      SetGadgetState(#gadSampleRate, 11)
    Case 88200
      SetGadgetState(#gadSampleRate, 12)
    Case 96000
      SetGadgetState(#gadSampleRate, 13)
    Case 192000
      SetGadgetState(#gadSampleRate, 14)
  EndSelect
  
SetGadgetState(#gadDevice, 0)

Repeat ;GET USER SELECTIONS

event = WaitWindowEvent()

If event = #PB_Event_CloseWindow
    Pa_Terminate()
    CloseWindow(2)
    End
EndIf

If event = #PB_Event_Menu ;KEYBOARD SHORTCUTS
    menuItem = EventMenu()
    Select menuItem
      Case 1: Break ;ENTER KEY ACCEPTS SETTINGS AND CLOSES WINDOW
    EndSelect
    
ElseIf event = #PB_Event_Gadget

Select EventGadget()

Case #gadDevice    
  source_device = GetGadgetItemData(#gadDevice, GetGadgetState(#gadDevice))
  
Case #gadSampleRate
  temp = GetGadgetState(#gadSampleRate)
  Select temp 
    Case 0 : samplerate =  6000 
    Case 1 : samplerate =  7200 
    Case 2 : samplerate =  8000 
    Case 3 : samplerate = 11025 
    Case 4 : samplerate = 12000 
    Case 5 : samplerate = 16000 
    Case 6 : samplerate = 22050 
    Case 7 : samplerate = 24000 
    Case 8 : samplerate = 32000 
    Case 9 : samplerate = 44100 
    Case 10 : samplerate = 48000 
    Case 11 : samplerate = 64000 
    Case 12 : samplerate = 88200 
    Case 13 : samplerate = 96000 
    Case 14 : samplerate = 192000 
  EndSelect 

Case #gadChannels
  temp = GetGadgetState(#gadChannels)
  Select temp 
    Case 0 : channels = #MONO 
    Case 1 : channels = #STEREO
  EndSelect

Case #gadBitDepth
  Select GetGadgetState(#gadBitDepth)
    Case 0 : bitDepth = 16
    Case 1 : bitDepth = 24
  EndSelect
  
EndSelect

EndIf

Until EventGadget() = #gadOK

CloseWindow(2)

Global Dim *Devices.PaDeviceInfo(0)
DeviceCount.i
DefaultOutputIndex.i
DefaultInputIndex.i

Select bitDepth
Case 16
  sampleFormat = #paInt16
Case 24
  sampleFormat = #paInt24
EndSelect

bytesPerFrame = (bitdepth / 8) * channels
framesPerBuffer = sampleRate / 20 ;REFRESH 20 TIMES PER SECOND
period.f = (1 / sampleRate) * 1000

*bigBufferSize = Int((#BUFFER_DURATION / period) + 0.5) + 8
*bigBufferSize * bytesPerFrame

ch2Offset = (bitDepth / 8)
ch3Offset = (bitDepth / 8) * 2
ch4Offset = (bitDepth / 8) * 3

in_streamparms\device = source_device ;: Debug "device: " + Str(in_streamparms\device)
in_streamparms\channelCount = channels ;: Debug "Channels: " + Str(in_streamparms\channelCount)
in_streamparms\sampleFormat = sampleFormat ;: Debug "sample format: " + Str(in_streamparms\sampleFormat)
in_streamparms\suggestedLatency = 0 ;#SUGGESTED_LATENCY
If api_source$(source_device) = "WASAPI"
  in_streamparms\hostApiSpecificStreamInfo = @WasapiInfo
Else
  in_streamparms\hostApiSpecificStreamInfo = #Null
EndIf

OpenPreferences("." + slash$ + "PA Recorder.ini")
If result = #False
  MessageRequester("Warning", "Unable to write to preferences file.")
EndIf
WritePreferenceLong("channels", channels)
WritePreferenceLong("bitdepth", bitDepth)
WritePreferenceDouble("samplerate", sampleRate)
ClosePreferences()

EndProcedure

Procedure WaveHeader(filenum)
If channels > 2 ;Or bitDepth > 16 ;PER MICROSOFT DOCUMENTATION
  my_wfe\format\wFormatTag.u = #WAVE_FORMAT_EXTENSIBLE
Else
  my_wfe\format\wFormatTag.u = #WAVE_FORMAT_PCM
EndIf

samprate.l = samplerate
byterate.l = samprate * bytesPerFrame
blockalign.u = channels * (bitdepth / 8)
bitsPerSample.u = bitdepth

my_WFE\format\nChannels = channels 
my_WFE\format\nSamplesPerSec = samprate
my_WFE\format\nAvgBytesPerSec = samplerate * bytesPerFrame
my_WFE\format\nBlockAlign = channels * (bitsPerSample / 8)
my_WFE\format\wBitsPerSample = bitsPerSample

;Extensible part:
If my_wfe\format\wFormatTag = #WAVE_FORMAT_EXTENSIBLE
  my_wfe\format\cbSize = 22 ;Size of Extensible part in bytes (SubFormat + wValidBitsPerSample + dwChannelMask)
  subchunk1size.l = SizeOf(WAVEFORMATEXTENSIBLE)
  my_wfe\wValidBitsPerSample = bitsPerSample
  my_wfe\dwChannelMask = 7 ;#SPEAKER_STEREO | #SPEAKER_FRONT_CENTER
  ;GUID
  ;my_wfe\SubFormat\Data1    = #WAVE_FORMAT_PCM
  ;my_wfe\SubFormat\Data2    = $00
  ;my_wfe\SubFormat\Data3    = $10
  ;my_wfe\SubFormat\Data4[0] = $80
  ;my_wfe\SubFormat\Data4[1] = $00
  ;my_wfe\SubFormat\Data4[2] = $00
  ;my_wfe\SubFormat\Data4[3] = $aa
  ;my_wfe\SubFormat\Data4[4] = $00
  ;my_wfe\SubFormat\Data4[5] = $38
  ;my_wfe\SubFormat\Data4[6] = $9b
  ;my_wfe\SubFormat\Data4[7] = $71
Else
;NOT EXTENSIBLE
  my_wfe\format\cbSize = 0 ;Size of Extensible part in bytes( SubFormat + wValidBitsPerSample + dwChannelMask)
  subchunk1size.l = SizeOf(MYWAVEFORMATEX)
EndIf

chunksize = 20 + subchunk1Size

; Choose channel mask:
;Select #CHANNELS ;my_wfe\Format\nChannels
;  Case 0
;  	my_wfe\dwChannelMask = #SPEAKER_DIRECTOUT
;  Case 1
;  	my_wfe\dwChannelMask = #SPEAKER_MONO
;  Case 2
;	  my_wfe\dwChannelMask = #SPEAKER_STEREO
;  Case 3
;my_wfe\dwChannelMask = 7 ;#SPEAKER_STEREO | #SPEAKER_FRONT_CENTER
;  Case 4
;	  my_wfe\dwChannelMask = #SPEAKER_QUAD
;  Case 5
;	  my_wfe\dwChannelMask = #SPEAKER_QUAD | #SPEAKER_FRONT_CENTER
;  Case 6
;	  my_wfe\dwChannelMask = #SPEAKER_5POINT1_SURROUND
;  Case 7
;	  my_wfe\dwChannelMask = #SPEAKER_5POINT1_SURROUND | #SPEAKER_BACK_CENTER
;  Case 8
;	  my_wfe\dwChannelMask = #SPEAKER_7POINT1_SURROUND
;EndSelect

;#define STATIC_KSDATAFORMAT_SUBTYPE_PCM\
;    DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_PCM)
;DEFINE_GUIDSTRUCT("00000001-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_PCM);
;#define KSDATAFORMAT_SUBTYPE_PCM DEFINE_GUIDNAMED(KSDATAFORMAT_SUBTYPE_PCM)

;WRITE WAV HEADER
WriteString(filenum, "RIFF") ; 4 bytes
WriteLong(filenum, chunksize) ; 4 bytes
WriteString(filenum, "WAVE") ; 4 bytes
WriteString(filenum, "fmt ") ; 4 bytes
WriteLong(filenum, subchunk1size) ; 4 bytes

;WRITE STRUCTURE
If my_wfe\format\wFormatTag = #WAVE_FORMAT_EXTENSIBLE
  WriteData(filenum, my_WFE, SizeOf(WAVEFORMATEXTENSIBLE))
Else
  WriteData(filenum, my_WFE, SizeOf(MYWAVEFORMATEX))
EndIf

WriteString(filenum, "data", #PB_Ascii) ; 4 bytes
WriteLong(filenum, 0) ; 4 BYTES -- subchunk2Size WILL GO HERE
;END OF FILE HEADER

EndProcedure

;==============================================================================

Procedure RECORD_Start()
;StickyWindow(1, #True)
totalBytes.q = 0
;CREATE WAV FILE
cutCounter = GetGadgetState(#gadCutCounter)
file_name$ = "." + slash$ + Right("0000000" + Str(cutCounter), 8) + ".wav"

fsize.q = FileSize(file_name$)
If fsize <> -1
  result = MessageRequester("Overwrite file?", "File " + GetFilePart(file_name$) + " already exists. Overwrite existing file?" ,#PB_MessageRequester_YesNo)
  If result = #PB_MessageRequester_No
    Goto doNotRecord ;DO NOT OVERWRITE FILE
  EndIf
EndIf

filehandle1 = CreateFile(1, file_name$)
If filehandle1 = 0
  MessageRequester("Error", "Unable to create record file.")
  shutdown()
EndIf
SetGadgetText(#gadRecording, "Recording to file " + GetFilePart(file_name$))
waveHeader(1)
  
DisableGadget(#gadRecord, 1)       
DisableGadget(#gadStop, 0)

elapsed_seconds = 0
remaining_seconds = buffersize / samplerate
record_seconds = remaining_seconds

secns = 0
recording_now = #True
firstPass = #True

doNotRecord:
;Pa_StartStream(*my_stream)
EndProcedure

Procedure AudioLoop(null)
Repeat
If killThread1 = #False
WaitSemaphore(audioSemaphore)
If recording_now = #True And firstPass = #False ;THROW AWAY FIRST BUFER TO SYNCHRONIZE AND AVOID GLITCH AT START

  If totalBytes.q + bytesToWrite >= maxBytes.q ;MAXIMUM FILE SIZE REACHED
    SetGadgetText(#gadEndOfFile, "Recording stopped. Maximum file size reached.")
    Record_Stop()
  Else
    writeFlag = #True
    bytesToWrite = RingBufferBytesToRead(@RB1): RingBufferGet(@RB1, *buffer2, bytesToWrite)
    WriteData(1, *buffer2, bytesToWrite)
    writeFlag = #False
    totalBytes + bytesToWrite
  EndIf

EndIf

If firstPass = #True
  firstPass = #False
  recBoxFlag = #BRIGHT_RED
  secns = 0
EndIf  

Else ;killThread1 = #True
  KillThread(threadID1)
EndIf
  
Until audioLoopContinue = #False
EndProcedure

;***********************************************************************************
;- START OF PROGRAM
;***********************************************************************************
DisableDebugger

If OSVersion() < 1000 ;WINDOWS
  slash$ = "\"
  NO_DRAG_Y = -22
Else
  slash$ = "/" ;MAC AND LINUX
  NO_DRAG_Y = 68
EndIf

result = Pa_Initialize()
If result <> #paNoError
  *err = Pa_GetErrorText(result): error.string = @*err
  MessageRequester("Unable to initialize PortAudio", error\s)
  End ;shutdown()
EndIf

source_device = 0: getSettings()

;*buffer1 = AllocateMemory3((*bigBufferSize * #BIG_BUFFER_MULTIPLIER) + 6) ;APPROXIMATELY 30 SECONDS OF AUDIO
*buffer1 = AllocateMemory3(*bigBufferSize) ;APPROXIMATELY 30 SECONDS OF AUDIO
If *buffer1 = #Null
  MessageRequester("Error", "Unable to allocate memory 1.")
  End
EndIf

FileBuffersSize(1, bigBufferSize) ;LET SYSTEM BUFFER FILES

*dealloc1 = *buffer1
remainder.f = Mod(*buffer1, 4)
If remainder <> 0: *buffer1 + 2: EndIf; TRY TO FORCE BUFFER TO A 4-BYTE BOUNDARY

*buffer2 = AllocateMemory3(*bigBufferSize) ;APPROXIMATELY 30 SECONDS OF AUDIO
If *buffer2 = #Null
  MessageRequester("Error", "Unable to allocate memory 2.")
  End
EndIf
*dealloc2 = *buffer2
remainder.f = Mod(*buffer2, 4)
If remainder <> 0: *buffer2 + 2: EndIf; TRY TO FORCE BUFFER TO A 4-BYTE BOUNDARY

bbs = *bigBufferSize: RingBufferInit(@RB1, bbs)

offset = 0: *bufLimit = (*buffer1 + *bigBufferSize) - 16 ;GIVE IT A SAFETY FACTOR
quantity = 0

;maxBytes.q = (2048 * 1024 * 1024) - (*bigBufferSize * #BIG_BUFFER_MULTIPLIER) ;2147483648 -- 2 GB MAXIMUM FILE SIZE
maxBytes.q = (2048 * 1024 * 1024) - *bigBufferSize ;2147483648 -- 2 GB MAXIMUM FILE SIZE

OpenWindow(1, 0, NO_DRAG_Y, 1000, 600, "PA Recorder")
;StickyWindow(1, #True)
SetWindowColor(1, #BG_COLOR)
winWidth = WindowWidth(1): center = winWidth / 2

LoadFont(1, "Geneva", 60)
LoadFont(2, "Geneva", 18)
StartDrawing(WindowOutput(1))
FrontColor(0)
BackColor(#BG_COLOR)
DrawingFont(FontID(1))
StopDrawing()

AddKeyboardShortcut(1, #PB_Shortcut_Control | #PB_Shortcut_R, 1) ;CTRL "R" TO START RECORDING
AddKeyboardShortcut(1, #PB_Shortcut_Control | #PB_Shortcut_S, 2) ;CTRL "S" TO STOP RECORDING
AddKeyboardShortcut(1, #PB_Shortcut_Control | #PB_Shortcut_X, 3) ;CTRL "X" TO EXIT/QUIT PROGRAM
AddKeyboardShortcut(1, #PB_Shortcut_Control | #PB_Shortcut_Q, 4) ;CTRL "Q" TO EXIT/QUIT PROGRAM
AddKeyboardShortcut(1, #PB_Shortcut_Return, 6) ;"RETURN" KEY TO CONFIRM STOP
AddKeyboardShortcut(1, #PB_Shortcut_Escape, 7) ;"ESCAPE" KEY TO ABORT STOP

;LineXY(center, 0, center, 800, 0)
SpinGadget(#gadCutCounter, 300, 35, 80, 30, 1, 999, #PB_Spin_Numeric): SetGadgetFont(#gadCutCounter, FontID(2))
cutCounter = 1: SetGadgetState(#gadCutCounter, cutCounter)
ButtonGadget(#gadRecord, 40, 20, #BUTTON_WIDTH, 40, "Record")
ButtonGadget(#gadStop, 220, 20, #BUTTON_WIDTH, 40, "Stop")
DisableGadget(#gadRecord, 0)

ButtonGadget(#gadExit, winWidth - 40, 0, 40, 30, "Exit")
DisableGadget(#gadStop, 1)
TextGadget(#gadtoRecord, 35, 80, 80, 15, "Ctrl-R"): SetGadgetColor(#gadToRecord, #PB_Gadget_BackColor, #BG_COLOR)
TextGadget(#gadtoStop, 225, 80, 70, 15, "Ctrl-S"): SetGadgetColor(#gadToStop, #PB_Gadget_BackColor, #BG_COLOR)
TextGadget(#gadRecording, 20, #TEXT_Y + 40, 960, 26, "")
SetGadgetFont(#gadRecording, FontID(2))
SetGadgetColor(#gadRecording, #PB_Gadget_BackColor, #BG_COLOR)
TextGadget(#gadEndOfFile, 20, 460, 510, 26, "")
SetGadgetFont(#gadEndOfFile, FontID(2)): SetGadgetColor(#gadEndOfFile, #PB_Gadget_BackColor, #BG_COLOR)
TextGadget(#gadNextCutText, 310, 17, 50, 18, "Next cut #"): SetGadgetColor(#gadNextCutText, #PB_Gadget_BackColor, #BG_COLOR)

;TRY ALSO STRING GADGET
TextGadget(#gadConfirm, 470, 20, 190, 55, "", #PB_Button_MultiLine)
SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, #BG_COLOR): SetGadgetColor(#gadConfirm, #PB_Gadget_FrontColor, $00ffff)
SetGadgetFont(#gadConfirm, FontID(2))

audioSemaphore = CreateSemaphore(0)

CompilerIf(#PB_Compiler_OS = #PB_OS_Windows)
;SET THIS PROGRAM TO RUN AT REAL-TIME PRIORITY
result = SetPriorityClass_(GetCurrentProcess_(), #REALTIME_PRIORITY_CLASS) ;COMMENT THIS LINE OUT FOR MAC
;MAKES AVAILABLE PRIORITIES 16 - 31 WHEN LOGGED ON As ADMINISTRATOR
;SEE http://music.columbia.edu/pipermail/portaudio/2004-February/003120.html
CompilerEndIf

StreamStart()

audioLoopContinue = #True
threadID1 = CreateThread(@audioLoop(), #Null) ;THREAD TO MONITOR CALLBACK FLAG
CompilerIf(#PB_Compiler_OS = #PB_OS_Windows)
  SetThreadPriority_(ThreadID(threadID1), #THREAD_PRIORITY_ABOVE_NORMAL) ;COMMENT THIS LINE OUT FOR MAC
; PRIORITY = 25 AS ADMINISTRATOR -- WASAPI CODE RUNS AT PRIORITY 26
CompilerEndIf

alertBoxFlag = #BRIGHT_RED
Repeat ;MAIN EVENT LOOP
event = WaitWindowEvent(10)

Select event
    
  Case #PB_Event_Menu ;KEYBOARD SHORTCUTS
    menuItem = EventMenu()
    If menuItem = 1 And recording_now = #False;CTRL-R TO RECORD
      Record_Start()
    ElseIf menuItem = 2 And recording_now = #True ;CTRL-S TO STOP
      stopFlag = #True
      SetGadgetText(#gadConfirm, "Press Enter to stop" + Chr(10) + Chr(13) + "Press ESC to continue")
      SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, $0000dd)
    ElseIf menuItem = 3 Or menuItem = 4 ;CTRL-X OR CTRL-Q TO QUIT PROGRAM
      If recording_now = #True
        quitFlag = #True
        SetGadgetText(#gadConfirm, "Press Enter to quit Press Esc to continue")
        SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, $0000dd)
      Else
        shutdown()
      EndIf
    ElseIf menuItem = 6 ;"RETURN" KEY TO CONFIRM STOP/QUIT
      SetGadgetText(#gadConfirm, "")
      SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, #BG_COLOR)
      If stopFlag = #True: stopFlag = #False: Record_Stop()
      ElseIf quitFlag = #True: shutdown()
      EndIf
    ElseIf menuItem = 7 ;"ESCAPE" KEY TO ABORT STOP/QUIT
      SetGadgetText(#gadConfirm, ""): stopFlag = #False: quitFlag = #False
      SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, #BG_COLOR)
    EndIf
    
  Case #PB_Event_Gadget
  event_gadget = EventGadget()

  Select event_gadget
      
    Case #gadRecord
      RECORD_Start()  
      
    Case #gadStop
      stopFlag = #True
      SetGadgetText(#gadConfirm, "Press Enter to stop" + Chr(10) + "Press ESC to continue")
      SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, $0000dd)
  
    Case #gadSettings
      getsettings()
      Pa_StopStream(*my_stream)
      Pa_StartStream(*my_stream)

    Case #gadExit
      If recording_now = #True
        quitFlag = #True
        SetGadgetText(#gadConfirm, "Press Enter to quit" + Chr(10) + "Press ESC to continue")
        SetGadgetColor(#gadConfirm, #PB_Gadget_BackColor, $0000dd)
      Else
        shutdown()
      EndIf

  EndSelect ;event gadget

Case #PB_Event_Repaint
  If firstPass = #False
    If recording_now = #True
      recBoxFlag = #BRIGHT_RED
    ElseIf recording_now = #False
      recBoxFlag = #STOP_RED
    EndIf
  EndIf

Case #PB_Event_CloseWindow

ShutDown()

EndSelect

ForEver
Post Reply