use CLANG wrappers

This commit is contained in:
Brandon Taylor 2021-06-17 13:49:33 -04:00
parent 619c79c489
commit 4f62de7fda
5 changed files with 584 additions and 332 deletions

16
gen/generator.jl Normal file
View file

@ -0,0 +1,16 @@
using Clang.Generators
using libportaudio_jll
cd(@__DIR__)
include_dir = joinpath(libportaudio_jll.artifact_dir, "include") |> normpath
portaudio_h = joinpath(include_dir, "portaudio.h")
options = load_options(joinpath(@__DIR__, "generator.toml"))
args = get_default_args()
push!(args, "-I$include_dir")
ctx = create_context(portaudio_h, args, options)
build!(ctx)

9
gen/generator.toml Normal file
View file

@ -0,0 +1,9 @@
[general]
library_name = "libportaudio"
output_file_path = "../src/LibPortAudio.jl"
module_name = "LibPortAudio"
jll_pkg_name = "libportaudio_jll"
export_symbol_prefixes = ["Pa", "pa"]
use_julia_native_enum_type = true
auto_mutability = true

View file

@ -14,6 +14,92 @@ using LinearAlgebra: transpose!
export PortAudioStream export PortAudioStream
include("libportaudio.jl") include("libportaudio.jl")
using .LibPortAudio:
PaSampleFormat,
Pa_Initialize,
Pa_Terminate,
Pa_GetVersion,
PaDeviceIndex,
PaDeviceInfo,
Pa_GetVersionText,
PaHostApiTypeId,
Pa_GetHostApiInfo,
PaStream,
Pa_GetDeviceCount,
Pa_GetDeviceInfo,
Pa_GetDefaultInputDevice,
Pa_GetDefaultOutputDevice,
Pa_OpenStream,
Pa_StartStream,
Pa_StopStream,
Pa_CloseStream,
Pa_GetStreamReadAvailable,
Pa_GetStreamWriteAvailable,
Pa_ReadStream,
Pa_WriteStream,
Pa_GetErrorText,
paOutputUnderflowed,
paInputOverflowed,
paNoFlag,
PaStreamParameters,
PaErrorCode
function safe_load(result, an_error)
if result == C_NULL
throw(an_error)
end
unsafe_load(result)
end
"""
Call the given expression in a separate thread, waiting on the result. This is
useful when running code that would otherwise block the Julia process (like a
`ccall` into a function that does IO).
"""
macro tcall(ex)
:(fetch(Base.Threads.@spawn $(esc(ex))))
end
# because we're calling Pa_ReadStream and PA_WriteStream from separate threads,
# we put a mutex around libportaudio calls
const pamutex = ReentrantLock()
macro locked(ex)
quote
lock(pamutex) do
$(esc(ex))
end
end
end
convert_nothing(::Nothing) = C_NULL
convert_nothing(something) = something
function is_xrun(error_code)
error_code == paOutputUnderflowed || error_code == paInputOverflowed
end
function is_xrun(number::Integer)
is_xrun(PaErrorCode(number))
end
function get_error_text(error_code)
unsafe_string(@locked Pa_GetErrorText(error_code))
end
# General utility function to handle the status from the Pa_* functions
function handle_status(err; warn_xruns::Bool = true)
if Int(err) < 0
if is_xrun(err)
if warn_xruns
@warn("libportaudio: " * get_error_text(err))
end
else
throw(ErrorException("libportaudio: " * get_error_text(err)))
end
end
err
end
macro stderr_as_debug(expression) macro stderr_as_debug(expression)
quote quote
@ -31,8 +117,8 @@ end
const CHUNKFRAMES = 128 const CHUNKFRAMES = 128
function versioninfo(io::IO = stdout) function versioninfo(io::IO = stdout)
println(io, Pa_GetVersionText()) println(io, unsafe_string(@locked Pa_GetVersionText()))
println(io, "Version: ", Pa_GetVersion()) println(io, "Version: ", @locked Pa_GetVersion())
end end
struct Bounds struct Bounds
@ -53,26 +139,36 @@ end
function PortAudioDevice(info::PaDeviceInfo, idx) function PortAudioDevice(info::PaDeviceInfo, idx)
PortAudioDevice( PortAudioDevice(
unsafe_string(info.name), unsafe_string(info.name),
unsafe_string(Pa_GetHostApiInfo(info.host_api).name), unsafe_string(safe_load(
info.default_sample_rate, (@locked Pa_GetHostApiInfo(info.hostApi)),
BoundsError(Pa_GetHostApiInfo, idx),
).name),
info.defaultSampleRate,
idx, idx,
Bounds( Bounds(
info.max_input_channels, info.maxInputChannels,
info.default_low_input_latency, info.defaultLowInputLatency,
info.default_high_input_latency, info.defaultHighInputLatency,
), ),
Bounds( Bounds(
info.max_output_channels, info.maxOutputChannels,
info.default_low_output_latency, info.defaultLowOutputLatency,
info.default_high_output_latency, info.defaultHighInputLatency,
), ),
) )
end end
name(device::PortAudioDevice) = device.name name(device::PortAudioDevice) = device.name
function get_device_info(i)
safe_load(
(@locked Pa_GetDeviceInfo(i)),
BoundsError(Pa_GetDeviceInfo, i),
)
end
function devices() function devices()
[PortAudioDevice(Pa_GetDeviceInfo(i), i) for i in 0:(Pa_GetDeviceCount() - 1)] [PortAudioDevice(get_device_info(i), i) for i in 0:(handle_status(@locked Pa_GetDeviceCount()) - 1)]
end end
struct Buffer{T} struct Buffer{T}
@ -88,19 +184,28 @@ end
struct PortAudioStream{T} struct PortAudioStream{T}
samplerate::Float64 samplerate::Float64
latency::Float64 latency::Float64
pointer_ref::Ref{PaStream} pointer_ref::Ref{Ptr{PaStream}}
warn_xruns::Bool warn_xruns::Bool
recover_xruns::Bool recover_xruns::Bool
sink_buffer::Buffer{T} sink_buffer::Buffer{T}
source_buffer::Buffer{T} source_buffer::Buffer{T}
end end
const type_to_fmt = Dict{Type, PaSampleFormat}(
Float32 => 1,
Int32 => 2,
# Int24 => 4,
Int16 => 8,
Int8 => 16,
UInt8 => 3,
)
function make_parameters(device, channels, T, latency, host_api_specific_stream_info) function make_parameters(device, channels, T, latency, host_api_specific_stream_info)
if channels == 0 if channels == 0
Ptr{Pa_StreamParameters}(0) Ptr{PaStreamParameters}(0)
else else
Ref( Ref(
Pa_StreamParameters( PaStreamParameters(
device.idx, device.idx,
channels, channels,
type_to_fmt[T], type_to_fmt[T],
@ -205,16 +310,20 @@ function PortAudioStream(
) )
inchans = fill_max_channels(inchans, indev.input_bounds) inchans = fill_max_channels(inchans, indev.input_bounds)
outchans = fill_max_channels(outchans, outdev.output_bounds) outchans = fill_max_channels(outchans, outdev.output_bounds)
pointer_ref = @stderr_as_debug Pa_OpenStream( pointer_ref = streamPtr = Ref{Ptr{PaStream}}(0)
make_parameters(indev, inchans, eltype, latency, input_info), handle_status(
make_parameters(outdev, outchans, eltype, latency, output_info), @locked @stderr_as_debug Pa_OpenStream(
samplerate, streamPtr,
frames_per_buffer, make_parameters(indev, inchans, eltype, latency, input_info),
flags, make_parameters(outdev, outchans, eltype, latency, output_info),
callback, float(samplerate),
user_data, frames_per_buffer,
flags,
convert_nothing(callback),
convert_nothing(user_data),
)
) )
Pa_StartStream(pointer_ref[]) handle_status(@locked Pa_StartStream(pointer_ref[]))
this = PortAudioStream{eltype}( this = PortAudioStream{eltype}(
samplerate, samplerate,
latency, latency,
@ -273,11 +382,11 @@ end
# use the default input and output devices # use the default input and output devices
function PortAudioStream(inchans = 2, outchans = 2; kwargs...) function PortAudioStream(inchans = 2, outchans = 2; kwargs...)
inidx = Pa_GetDefaultInputDevice() inidx = handle_status(@locked Pa_GetDefaultInputDevice())
outidx = Pa_GetDefaultOutputDevice() outidx = handle_status(@locked Pa_GetDefaultOutputDevice())
PortAudioStream( PortAudioStream(
PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx), PortAudioDevice(get_device_info(inidx), inidx),
PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx), PortAudioDevice(get_device_info(outidx), outidx),
inchans, inchans,
outchans; outchans;
kwargs..., kwargs...,
@ -298,8 +407,8 @@ function close(stream::PortAudioStream)
pointer_ref = stream.pointer_ref pointer_ref = stream.pointer_ref
pointer = pointer_ref[] pointer = pointer_ref[]
if pointer != C_NULL if pointer != C_NULL
Pa_StopStream(pointer) handle_status(@locked Pa_StopStream(pointer))
Pa_CloseStream(pointer) handle_status(@locked Pa_CloseStream(pointer))
pointer_ref[] = C_NULL pointer_ref[] = C_NULL
end end
nothing nothing
@ -402,12 +511,24 @@ function interleave!(long, wide, n, already, offset, wide_to_long)
end end
function handle_xrun(stream, error_code, recover_xruns) function handle_xrun(stream, error_code, recover_xruns)
if recover_xruns && if recover_xruns && is_xrun(error_code)
(error_code == PA_OUTPUT_UNDERFLOWED || error_code == PA_INPUT_OVERFLOWED)
recover_xrun(stream) recover_xrun(stream)
end end
end end
function write_stream(stream::Ptr{PaStream}, buf::Array, frames::Integer; warn_xruns = true)
handle_status(
disable_sigint() do
@tcall @locked Pa_WriteStream(
stream,
buf,
frames,
)
end,
warn_xruns = warn_xruns,
)
end
function SampledSignals.unsafe_write( function SampledSignals.unsafe_write(
sink::PortAudioSink, sink::PortAudioSink,
buf::Array, buf::Array,
@ -428,7 +549,7 @@ function SampledSignals.unsafe_write(
# shorter-than-requested frame count instead of throwing an error # shorter-than-requested frame count instead of throwing an error
handle_xrun( handle_xrun(
stream, stream,
Pa_WriteStream(pointer, chunkbuf, n, warn_xruns = warn_xruns), write_stream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
recover_xruns, recover_xruns,
) )
nwritten += n nwritten += n
@ -437,6 +558,24 @@ function SampledSignals.unsafe_write(
nwritten nwritten
end end
function read_stream(stream::Ptr{PaStream}, buf::Array, frames::Integer; warn_xruns = true)
# without disable_sigint I get a segfault with the error:
# "error thrown and no exception handler available."
# if the user tries to ctrl-C. Note I've still had some crash problems with
# ctrl-C within `pasuspend`, so for now I think either don't use `pasuspend` or
# don't use ctrl-C.
handle_status(
disable_sigint() do
@tcall @locked Pa_ReadStream(
stream,
buf,
frames
)
end,
warn_xruns = warn_xruns,
)
end
function SampledSignals.unsafe_read!( function SampledSignals.unsafe_read!(
source::PortAudioSource, source::PortAudioSource,
buf::Array, buf::Array,
@ -455,7 +594,7 @@ function SampledSignals.unsafe_read!(
# shorter-than-requested frame count instead of throwing an error # shorter-than-requested frame count instead of throwing an error
handle_xrun( handle_xrun(
stream, stream,
Pa_ReadStream(pointer, chunkbuf, n, warn_xruns = warn_xruns), read_stream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
recover_xruns, recover_xruns,
) )
# de-interleave the samples # de-interleave the samples
@ -476,11 +615,11 @@ function prefill_output(sink::PortAudioSink)
stream = sink.stream stream = sink.stream
pointer = stream.pointer_ref[] pointer = stream.pointer_ref[]
chunkbuf = stream.sink_buffer.chunkbuf chunkbuf = stream.sink_buffer.chunkbuf
towrite = Pa_GetStreamWriteAvailable(pointer) towrite = handle_status(@locked Pa_GetStreamWriteAvailable(pointer))
while towrite > 0 while towrite > 0
n = min(towrite, CHUNKFRAMES) n = min(towrite, CHUNKFRAMES)
fill!(chunkbuf, zero(eltype(chunkbuf))) fill!(chunkbuf, zero(eltype(chunkbuf)))
Pa_WriteStream(pointer, chunkbuf, n, warn_xruns = false) write_stream(pointer, chunkbuf, n, warn_xruns = false)
towrite -= n towrite -= n
end end
end end
@ -495,10 +634,10 @@ function discard_input(source::PortAudioSource)
stream = source.stream stream = source.stream
pointer = stream.pointer_ref[] pointer = stream.pointer_ref[]
chunkbuf = stream.source_buffer.chunkbuf chunkbuf = stream.source_buffer.chunkbuf
toread = Pa_GetStreamReadAvailable(pointer) toread = handle_status(@locked Pa_GetStreamReadAvailable(pointer))
while toread > 0 while toread > 0
n = min(toread, CHUNKFRAMES) n = min(toread, CHUNKFRAMES)
Pa_ReadStream(pointer, chunkbuf, n, warn_xruns = false) read_stream(pointer, chunkbuf, n, warn_xruns = false)
toread -= n toread -= n
end end
end end
@ -543,10 +682,10 @@ function __init__()
# junk to STDOUT on initialization, so we swallow it. # junk to STDOUT on initialization, so we swallow it.
# TODO: actually check the junk to make sure there's nothing in there we # TODO: actually check the junk to make sure there's nothing in there we
# don't expect # don't expect
@stderr_as_debug Pa_Initialize() @stderr_as_debug handle_status(@locked Pa_Initialize())
atexit() do atexit() do
Pa_Terminate() handle_status(@locked Pa_Terminate())
end end
end end

View file

@ -1,190 +1,328 @@
# Low-level wrappers for Portaudio calls module LibPortAudio
using libportaudio_jll
export libportaudio_jll
function Pa_GetVersion()
ccall((:Pa_GetVersion, libportaudio), Cint, ())
end
function Pa_GetVersionText()
ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ())
end
mutable struct PaVersionInfo
versionMajor::Cint
versionMinor::Cint
versionSubMinor::Cint
versionControlRevision::Ptr{Cchar}
versionText::Ptr{Cchar}
end
# no prototype is found for this function at portaudio.h:114:22, please use with caution
function Pa_GetVersionInfo()
ccall((:Pa_GetVersionInfo, libportaudio), Ptr{PaVersionInfo}, ())
end
# General type aliases
const PaTime = Cdouble
const PaError = Cint const PaError = Cint
const PaSampleFormat = Culong
const PaDeviceIndex = Cint
const PaHostApiIndex = Cint
const PaHostApiTypeId = Cint
# PaStream is always used as an opaque type, so we're always dealing
# with the pointer
const PaStream = Ptr{Cvoid}
const PaStreamCallback = Cvoid
const PaStreamFlags = Culong
const paNoFlag = PaStreamFlags(0x00) @enum PaErrorCode::Int32 begin
paNoError = 0
const PA_NO_ERROR = 0 paNotInitialized = -10000
const PA_INPUT_OVERFLOWED = -10000 + 19 paUnanticipatedHostError = -9999
const PA_OUTPUT_UNDERFLOWED = -10000 + 20 paInvalidChannelCount = -9998
paInvalidSampleRate = -9997
# sample format types paInvalidDevice = -9996
const paFloat32 = PaSampleFormat(0x01) paInvalidFlag = -9995
const paInt32 = PaSampleFormat(0x02) paSampleFormatNotSupported = -9994
const paInt24 = PaSampleFormat(0x04) paBadIODeviceCombination = -9993
const paInt16 = PaSampleFormat(0x08) paInsufficientMemory = -9992
const paInt8 = PaSampleFormat(0x10) paBufferTooBig = -9991
const paUInt8 = PaSampleFormat(0x20) paBufferTooSmall = -9990
const paNonInterleaved = PaSampleFormat(0x80000000) paNullCallback = -9989
paBadStreamPtr = -9988
const type_to_fmt = Dict{Type, PaSampleFormat}( paTimedOut = -9987
Float32 => 1, paInternalError = -9986
Int32 => 2, paDeviceUnavailable = -9985
# Int24 => 4, paIncompatibleHostApiSpecificStreamInfo = -9984
Int16 => 8, paStreamIsStopped = -9983
Int8 => 16, paStreamIsNotStopped = -9982
UInt8 => 3, paInputOverflowed = -9981
) paOutputUnderflowed = -9980
paHostApiNotFound = -9979
const PaStreamCallbackResult = Cint paInvalidHostApi = -9978
# Callback return values paCanNotReadFromACallbackStream = -9977
const paContinue = PaStreamCallbackResult(0) paCanNotWriteToACallbackStream = -9976
const paComplete = PaStreamCallbackResult(1) paCanNotReadFromAnOutputOnlyStream = -9975
const paAbort = PaStreamCallbackResult(2) paCanNotWriteToAnInputOnlyStream = -9974
paIncompatibleStreamHostApi = -9973
function safe_load(result, an_error) paBadBufferPtr = -9972
if result == C_NULL
throw(an_error)
end
unsafe_load(result)
end end
""" function Pa_GetErrorText(errorCode)
Call the given expression in a separate thread, waiting on the result. This is ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), errorCode)
useful when running code that would otherwise block the Julia process (like a
`ccall` into a function that does IO).
"""
macro tcall(ex)
:(fetch(Base.Threads.@spawn $(esc(ex))))
end
# because we're calling Pa_ReadStream and PA_WriteStream from separate threads,
# we put a mutex around libportaudio calls
const pamutex = ReentrantLock()
macro locked(ex)
quote
lock(pamutex) do
$(esc(ex))
end
end
end end
function Pa_Initialize() function Pa_Initialize()
handle_status(@locked ccall((:Pa_Initialize, libportaudio), PaError, ())) ccall((:Pa_Initialize, libportaudio), PaError, ())
nothing
end end
function Pa_Terminate() function Pa_Terminate()
handle_status(@locked ccall((:Pa_Terminate, libportaudio), PaError, ())) ccall((:Pa_Terminate, libportaudio), PaError, ())
nothing
end end
Pa_GetVersion() = @locked ccall((:Pa_GetVersion, libportaudio), Cint, ()) const PaDeviceIndex = Cint
function Pa_GetVersionText() const PaHostApiIndex = Cint
unsafe_string(@locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()))
function Pa_GetHostApiCount()
ccall((:Pa_GetHostApiCount, libportaudio), PaHostApiIndex, ())
end end
# Host API Functions function Pa_GetDefaultHostApi()
ccall((:Pa_GetDefaultHostApi, libportaudio), PaHostApiIndex, ())
end
# A Host API is the top-level of the PortAudio hierarchy. Each host API has a @enum PaHostApiTypeId::UInt32 begin
# unique type ID that tells you which native backend it is (JACK, ALSA, ASIO, paInDevelopment = 0
# etc.). On a given system you can identify each backend by its index, which paDirectSound = 1
# will range between 0 and Pa_GetHostApiCount() - 1. You can enumerate through paMME = 2
# all the host APIs on the system by iterating through those values. paASIO = 3
paSoundManager = 4
# PaHostApiTypeId values paCoreAudio = 5
const pa_host_api_names = Dict{PaHostApiTypeId, String}( paOSS = 7
0 => "In Development", # use while developing support for a new host API paALSA = 8
1 => "Direct Sound", paAL = 9
2 => "MME", paBeOS = 10
3 => "ASIO", paWDMKS = 11
4 => "Sound Manager", paJACK = 12
5 => "Core Audio", paWASAPI = 13
7 => "OSS", paAudioScienceHPI = 14
8 => "ALSA", end
9 => "AL",
10 => "BeOS",
11 => "WDMKS",
12 => "Jack",
13 => "WASAPI",
14 => "AudioScience HPI",
)
mutable struct PaHostApiInfo mutable struct PaHostApiInfo
struct_version::Cint structVersion::Cint
api_type::PaHostApiTypeId type::PaHostApiTypeId
name::Ptr{Cchar} name::Ptr{Cchar}
deviceCount::Cint deviceCount::Cint
defaultInputDevice::PaDeviceIndex defaultInputDevice::PaDeviceIndex
defaultOutputDevice::PaDeviceIndex defaultOutputDevice::PaDeviceIndex
end end
function Pa_GetHostApiInfo(i) function Pa_GetHostApiInfo(hostApi)
safe_load( ccall(
(@locked ccall( (:Pa_GetHostApiInfo, libportaudio),
(:Pa_GetHostApiInfo, libportaudio), Ptr{PaHostApiInfo},
Ptr{PaHostApiInfo}, (PaHostApiIndex,),
(PaHostApiIndex,), hostApi,
i,
)),
BoundsError(Pa_GetHostApiInfo, i),
) )
end end
# Device Functions function Pa_HostApiTypeIdToHostApiIndex(type)
ccall(
(:Pa_HostApiTypeIdToHostApiIndex, libportaudio),
PaHostApiIndex,
(PaHostApiTypeId,),
type,
)
end
mutable struct PaDeviceInfo function Pa_HostApiDeviceIndexToDeviceIndex(hostApi, hostApiDeviceIndex)
struct_version::Cint ccall(
name::Ptr{Cchar} (:Pa_HostApiDeviceIndexToDeviceIndex, libportaudio),
host_api::PaHostApiIndex PaDeviceIndex,
max_input_channels::Cint (PaHostApiIndex, Cint),
max_output_channels::Cint hostApi,
default_low_input_latency::PaTime hostApiDeviceIndex,
default_low_output_latency::PaTime )
default_high_input_latency::PaTime end
default_high_output_latency::PaTime
default_sample_rate::Cdouble mutable struct PaHostErrorInfo
hostApiType::PaHostApiTypeId
errorCode::Clong
errorText::Ptr{Cchar}
end
function Pa_GetLastHostErrorInfo()
ccall((:Pa_GetLastHostErrorInfo, libportaudio), Ptr{PaHostErrorInfo}, ())
end end
function Pa_GetDeviceCount() function Pa_GetDeviceCount()
handle_status(@locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ())) ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ())
end
function Pa_GetDeviceInfo(i)
safe_load(
(@locked ccall(
(:Pa_GetDeviceInfo, libportaudio),
Ptr{PaDeviceInfo},
(PaDeviceIndex,),
i,
)),
BoundsError(Pa_GetDeviceInfo, i),
)
end end
function Pa_GetDefaultInputDevice() function Pa_GetDefaultInputDevice()
handle_status( ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
@locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
)
end end
function Pa_GetDefaultOutputDevice() function Pa_GetDefaultOutputDevice()
handle_status( ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
@locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
)
end end
# Stream Functions const PaTime = Cdouble
mutable struct Pa_StreamParameters const PaSampleFormat = Culong
mutable struct PaDeviceInfo
structVersion::Cint
name::Ptr{Cchar}
hostApi::PaHostApiIndex
maxInputChannels::Cint
maxOutputChannels::Cint
defaultLowInputLatency::PaTime
defaultLowOutputLatency::PaTime
defaultHighInputLatency::PaTime
defaultHighOutputLatency::PaTime
defaultSampleRate::Cdouble
end
function Pa_GetDeviceInfo(device)
ccall((:Pa_GetDeviceInfo, libportaudio), Ptr{PaDeviceInfo}, (PaDeviceIndex,), device)
end
struct PaStreamParameters
device::PaDeviceIndex device::PaDeviceIndex
channelCount::Cint channelCount::Cint
sampleFormat::PaSampleFormat sampleFormat::PaSampleFormat
suggestedLatency::PaTime suggestedLatency::PaTime
hostAPISpecificStreamInfo::Ptr{Cvoid} hostApiSpecificStreamInfo::Ptr{Cvoid}
end
function Pa_IsFormatSupported(inputParameters, outputParameters, sampleRate)
ccall(
(:Pa_IsFormatSupported, libportaudio),
PaError,
(Ptr{PaStreamParameters}, Ptr{PaStreamParameters}, Cdouble),
inputParameters,
outputParameters,
sampleRate,
)
end
const PaStream = Cvoid
const PaStreamFlags = Culong
mutable struct PaStreamCallbackTimeInfo
inputBufferAdcTime::PaTime
currentTime::PaTime
outputBufferDacTime::PaTime
end
const PaStreamCallbackFlags = Culong
@enum PaStreamCallbackResult::UInt32 begin
paContinue = 0
paComplete = 1
paAbort = 2
end
# typedef int PaStreamCallback ( const void * input , void * output , unsigned long frameCount , const PaStreamCallbackTimeInfo * timeInfo , PaStreamCallbackFlags statusFlags , void * userData )
const PaStreamCallback = Cvoid
function Pa_OpenStream(
stream,
inputParameters,
outputParameters,
sampleRate,
framesPerBuffer,
streamFlags,
streamCallback,
userData,
)
ccall(
(:Pa_OpenStream, libportaudio),
PaError,
(
Ptr{Ptr{PaStream}},
Ptr{PaStreamParameters},
Ptr{PaStreamParameters},
Cdouble,
Culong,
PaStreamFlags,
Ptr{Cvoid},
Ptr{Cvoid},
),
stream,
inputParameters,
outputParameters,
sampleRate,
framesPerBuffer,
streamFlags,
streamCallback,
userData,
)
end
function Pa_OpenDefaultStream(
stream,
numInputChannels,
numOutputChannels,
sampleFormat,
sampleRate,
framesPerBuffer,
streamCallback,
userData,
)
ccall(
(:Pa_OpenDefaultStream, libportaudio),
PaError,
(
Ptr{Ptr{PaStream}},
Cint,
Cint,
PaSampleFormat,
Cdouble,
Culong,
Ptr{Cvoid},
Ptr{Cvoid},
),
stream,
numInputChannels,
numOutputChannels,
sampleFormat,
sampleRate,
framesPerBuffer,
streamCallback,
userData,
)
end
function Pa_CloseStream(stream)
ccall((:Pa_CloseStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
# typedef void PaStreamFinishedCallback ( void * userData )
const PaStreamFinishedCallback = Cvoid
function Pa_SetStreamFinishedCallback(stream, streamFinishedCallback)
ccall(
(:Pa_SetStreamFinishedCallback, libportaudio),
PaError,
(Ptr{PaStream}, Ptr{Cvoid}),
stream,
streamFinishedCallback,
)
end
function Pa_StartStream(stream)
ccall((:Pa_StartStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_StopStream(stream)
ccall((:Pa_StopStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_AbortStream(stream)
ccall((:Pa_AbortStream, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_IsStreamStopped(stream)
ccall((:Pa_IsStreamStopped, libportaudio), PaError, (Ptr{PaStream},), stream)
end
function Pa_IsStreamActive(stream)
ccall((:Pa_IsStreamActive, libportaudio), PaError, (Ptr{PaStream},), stream)
end end
mutable struct PaStreamInfo mutable struct PaStreamInfo
@ -194,158 +332,108 @@ mutable struct PaStreamInfo
sampleRate::Cdouble sampleRate::Cdouble
end end
convert_nothing(::Nothing) = C_NULL function Pa_GetStreamInfo(stream)
convert_nothing(something) = something ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo}, (Ptr{PaStream},), stream)
# function Pa_OpenDefaultStream(inChannels, outChannels,
# sampleFormat::PaSampleFormat,
# sampleRate, framesPerBuffer)
# streamPtr = Ref{PaStream}(0)
# handle_status(ccall((:Pa_OpenDefaultStream, libportaudio),
# PaError, (Ref{PaStream}, Cint, Cint,
# PaSampleFormat, Cdouble, Culong,
# Ref{Cvoid}, Ref{Cvoid}),
# streamPtr, inChannels, outChannels, sampleFormat, sampleRate,
# framesPerBuffer, C_NULL, C_NULL))
# streamPtr[]
# end
#
function Pa_OpenStream(
inParams,
outParams,
sampleRate,
framesPerBuffer,
flags::PaStreamFlags,
callback,
userdata::UserData,
) where {UserData}
streamPtr = Ref{PaStream}(0)
handle_status(
@locked ccall(
(:Pa_OpenStream, libportaudio),
PaError,
(
Ref{PaStream},
Ref{Pa_StreamParameters},
Ref{Pa_StreamParameters},
Cdouble,
Culong,
PaStreamFlags,
Ref{Cvoid},
Ref{UserData},
),
streamPtr,
inParams,
outParams,
float(sampleRate),
framesPerBuffer,
flags,
convert_nothing(callback),
convert_nothing(userdata),
)
)
streamPtr
end end
function Pa_StartStream(stream::PaStream) function Pa_GetStreamTime(stream)
handle_status( ccall((:Pa_GetStreamTime, libportaudio), PaTime, (Ptr{PaStream},), stream)
@locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
end end
function Pa_StopStream(stream::PaStream) function Pa_GetStreamCpuLoad(stream)
handle_status( ccall((:Pa_GetStreamCpuLoad, libportaudio), Cdouble, (Ptr{PaStream},), stream)
@locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
end end
function Pa_CloseStream(stream::PaStream) function Pa_ReadStream(stream, buffer, frames)
handle_status( ccall(
@locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream) (:Pa_ReadStream, libportaudio),
) PaError,
nothing (Ptr{PaStream}, Ptr{Cvoid}, Culong),
end stream,
buffer,
function Pa_GetStreamReadAvailable(stream::PaStream) frames,
handle_status(
@locked ccall(
(:Pa_GetStreamReadAvailable, libportaudio),
Clong,
(PaStream,),
stream,
)
) )
end end
function Pa_GetStreamWriteAvailable(stream::PaStream) function Pa_WriteStream(stream, buffer, frames)
handle_status( ccall(
@locked ccall( (:Pa_WriteStream, libportaudio),
(:Pa_GetStreamWriteAvailable, libportaudio), PaError,
Clong, (Ptr{PaStream}, Ptr{Cvoid}, Culong),
(PaStream,), stream,
stream, buffer,
) frames,
) )
end end
function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer; warn_xruns = true) function Pa_GetStreamReadAvailable(stream)
# without disable_sigint I get a segfault with the error: ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, (Ptr{PaStream},), stream)
# "error thrown and no exception handler available."
# if the user tries to ctrl-C. Note I've still had some crash problems with
# ctrl-C within `pasuspend`, so for now I think either don't use `pasuspend` or
# don't use ctrl-C.
handle_status(
disable_sigint() do
@tcall @locked ccall(
(:Pa_ReadStream, libportaudio),
PaError,
(PaStream, Ptr{Cvoid}, Culong),
stream,
buf,
frames,
)
end,
warn_xruns = warn_xruns,
)
end end
function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer; warn_xruns = true) function Pa_GetStreamWriteAvailable(stream)
handle_status( ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, (Ptr{PaStream},), stream)
disable_sigint() do
@tcall @locked ccall(
(:Pa_WriteStream, libportaudio),
PaError,
(PaStream, Ptr{Cvoid}, Culong),
stream,
buf,
frames,
)
end,
warn_xruns = warn_xruns,
)
end end
# function Pa_GetStreamInfo(stream::PaStream) function Pa_GetSampleSize(format)
# safe_load( ccall((:Pa_GetSampleSize, libportaudio), PaError, (PaSampleFormat,), format)
# ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo}, end
# (PaStream, ), stream),
# ArgumentError("Error getting stream info. Is the stream already closed?") function Pa_Sleep(msec)
# ) ccall((:Pa_Sleep, libportaudio), Cvoid, (Clong,), msec)
# end end
#
# General utility function to handle the status from the Pa_* functions const paNoDevice = PaDeviceIndex(-1)
function handle_status(err::Integer; warn_xruns::Bool = true)
if err < 0 const paUseHostApiSpecificDeviceSpecification = PaDeviceIndex(-2)
msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err)
if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED const paFloat32 = PaSampleFormat(0x00000001)
if warn_xruns
@warn("libportaudio: " * unsafe_string(msg)) const paInt32 = PaSampleFormat(0x00000002)
end
else const paInt24 = PaSampleFormat(0x00000004)
throw(ErrorException("libportaudio: " * unsafe_string(msg)))
end const paInt16 = PaSampleFormat(0x00000008)
const paInt8 = PaSampleFormat(0x00000010)
const paUInt8 = PaSampleFormat(0x00000020)
const paCustomFormat = PaSampleFormat(0x00010000)
const paNonInterleaved = PaSampleFormat(0x80000000)
const paFormatIsSupported = 0
const paFramesPerBufferUnspecified = 0
const paNoFlag = PaStreamFlags(0)
const paClipOff = PaStreamFlags(0x00000001)
const paDitherOff = PaStreamFlags(0x00000002)
const paNeverDropInput = PaStreamFlags(0x00000004)
const paPrimeOutputBuffersUsingStreamCallback = PaStreamFlags(0x00000008)
const paPlatformSpecificFlags = PaStreamFlags(0xffff0000)
const paInputUnderflow = PaStreamCallbackFlags(0x00000001)
const paInputOverflow = PaStreamCallbackFlags(0x00000002)
const paOutputUnderflow = PaStreamCallbackFlags(0x00000004)
const paOutputOverflow = PaStreamCallbackFlags(0x00000008)
const paPrimingOutput = PaStreamCallbackFlags(0x00000010)
# exports
const PREFIXES = ["Pa", "pa"]
for name in names(@__MODULE__; all = true), prefix in PREFIXES
if startswith(string(name), prefix)
@eval export $name
end end
err
end end
end # module

View file

@ -4,18 +4,19 @@ using Logging: Debug
using PortAudio using PortAudio
using PortAudio: using PortAudio:
combine_default_sample_rates, combine_default_sample_rates,
get_device_info,
handle_status, handle_status,
Pa_GetDefaultInputDevice, Pa_GetDefaultInputDevice,
Pa_GetDefaultOutputDevice, Pa_GetDefaultOutputDevice,
Pa_GetDeviceInfo,
Pa_GetHostApiInfo,
Pa_Initialize, Pa_Initialize,
PA_OUTPUT_UNDERFLOWED, paOutputUnderflowed,
Pa_Terminate, Pa_Terminate,
PortAudioDevice, PortAudioDevice,
recover_xrun, recover_xrun,
seek_alsa_conf, seek_alsa_conf,
@stderr_as_debug @stderr_as_debug,
@locked
using PortAudio.LibPortAudio: paNotInitialized
using SampledSignals using SampledSignals
using Test using Test
@ -40,21 +41,20 @@ end
end end
@testset "Null errors" begin @testset "Null errors" begin
@test_throws BoundsError Pa_GetDeviceInfo(-1) @test_throws BoundsError get_device_info(-1)
@test_throws BoundsError Pa_GetHostApiInfo(-1)
end end
end end
if !isempty(PortAudio.devices()) if !isempty(PortAudio.devices())
# make sure we can terminate, then reinitialize # make sure we can terminate, then reinitialize
Pa_Terminate() handle_status(@locked Pa_Terminate())
@stderr_as_debug Pa_Initialize() @stderr_as_debug handle_status(@locked Pa_Initialize())
# these default values are specific to my machines # these default values are specific to my machines
inidx = Pa_GetDefaultInputDevice() inidx = handle_status(@locked Pa_GetDefaultInputDevice())
default_indev = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx).name default_indev = PortAudioDevice(get_device_info(inidx), inidx).name
outidx = Pa_GetDefaultOutputDevice() outidx = handle_status(@locked Pa_GetDefaultOutputDevice())
default_outdev = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx).name default_outdev = PortAudioDevice(get_device_info(outidx), outidx).name
@testset "Local Tests" begin @testset "Local Tests" begin
@testset "Open Default Device" begin @testset "Open Default Device" begin
@ -126,10 +126,10 @@ if !isempty(PortAudio.devices())
@test_throws ErrorException PortAudioStream("foobarbaz") @test_throws ErrorException PortAudioStream("foobarbaz")
@test_throws ErrorException PortAudioStream(default_indev, "foobarbaz") @test_throws ErrorException PortAudioStream(default_indev, "foobarbaz")
@test_logs (:warn, "libportaudio: Output underflowed") handle_status( @test_logs (:warn, "libportaudio: Output underflowed") handle_status(
PA_OUTPUT_UNDERFLOWED, paOutputUnderflowed,
) )
@test_throws ErrorException("libportaudio: PortAudio not initialized") handle_status( @test_throws ErrorException("libportaudio: PortAudio not initialized") handle_status(
-10000, paNotInitialized,
) )
@test_throws ErrorException(""" @test_throws ErrorException("""
Could not find ALSA config directory. Searched: Could not find ALSA config directory. Searched: