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
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)
quote
@ -31,8 +117,8 @@ end
const CHUNKFRAMES = 128
function versioninfo(io::IO = stdout)
println(io, Pa_GetVersionText())
println(io, "Version: ", Pa_GetVersion())
println(io, unsafe_string(@locked Pa_GetVersionText()))
println(io, "Version: ", @locked Pa_GetVersion())
end
struct Bounds
@ -53,26 +139,36 @@ end
function PortAudioDevice(info::PaDeviceInfo, idx)
PortAudioDevice(
unsafe_string(info.name),
unsafe_string(Pa_GetHostApiInfo(info.host_api).name),
info.default_sample_rate,
unsafe_string(safe_load(
(@locked Pa_GetHostApiInfo(info.hostApi)),
BoundsError(Pa_GetHostApiInfo, idx),
).name),
info.defaultSampleRate,
idx,
Bounds(
info.max_input_channels,
info.default_low_input_latency,
info.default_high_input_latency,
info.maxInputChannels,
info.defaultLowInputLatency,
info.defaultHighInputLatency,
),
Bounds(
info.max_output_channels,
info.default_low_output_latency,
info.default_high_output_latency,
info.maxOutputChannels,
info.defaultLowOutputLatency,
info.defaultHighInputLatency,
),
)
end
name(device::PortAudioDevice) = device.name
function get_device_info(i)
safe_load(
(@locked Pa_GetDeviceInfo(i)),
BoundsError(Pa_GetDeviceInfo, i),
)
end
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
struct Buffer{T}
@ -88,19 +184,28 @@ end
struct PortAudioStream{T}
samplerate::Float64
latency::Float64
pointer_ref::Ref{PaStream}
pointer_ref::Ref{Ptr{PaStream}}
warn_xruns::Bool
recover_xruns::Bool
sink_buffer::Buffer{T}
source_buffer::Buffer{T}
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)
if channels == 0
Ptr{Pa_StreamParameters}(0)
Ptr{PaStreamParameters}(0)
else
Ref(
Pa_StreamParameters(
PaStreamParameters(
device.idx,
channels,
type_to_fmt[T],
@ -205,16 +310,20 @@ function PortAudioStream(
)
inchans = fill_max_channels(inchans, indev.input_bounds)
outchans = fill_max_channels(outchans, outdev.output_bounds)
pointer_ref = @stderr_as_debug Pa_OpenStream(
make_parameters(indev, inchans, eltype, latency, input_info),
make_parameters(outdev, outchans, eltype, latency, output_info),
samplerate,
frames_per_buffer,
flags,
callback,
user_data,
pointer_ref = streamPtr = Ref{Ptr{PaStream}}(0)
handle_status(
@locked @stderr_as_debug Pa_OpenStream(
streamPtr,
make_parameters(indev, inchans, eltype, latency, input_info),
make_parameters(outdev, outchans, eltype, latency, output_info),
float(samplerate),
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}(
samplerate,
latency,
@ -273,11 +382,11 @@ end
# use the default input and output devices
function PortAudioStream(inchans = 2, outchans = 2; kwargs...)
inidx = Pa_GetDefaultInputDevice()
outidx = Pa_GetDefaultOutputDevice()
inidx = handle_status(@locked Pa_GetDefaultInputDevice())
outidx = handle_status(@locked Pa_GetDefaultOutputDevice())
PortAudioStream(
PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx),
PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx),
PortAudioDevice(get_device_info(inidx), inidx),
PortAudioDevice(get_device_info(outidx), outidx),
inchans,
outchans;
kwargs...,
@ -298,8 +407,8 @@ function close(stream::PortAudioStream)
pointer_ref = stream.pointer_ref
pointer = pointer_ref[]
if pointer != C_NULL
Pa_StopStream(pointer)
Pa_CloseStream(pointer)
handle_status(@locked Pa_StopStream(pointer))
handle_status(@locked Pa_CloseStream(pointer))
pointer_ref[] = C_NULL
end
nothing
@ -402,12 +511,24 @@ function interleave!(long, wide, n, already, offset, wide_to_long)
end
function handle_xrun(stream, error_code, recover_xruns)
if recover_xruns &&
(error_code == PA_OUTPUT_UNDERFLOWED || error_code == PA_INPUT_OVERFLOWED)
if recover_xruns && is_xrun(error_code)
recover_xrun(stream)
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(
sink::PortAudioSink,
buf::Array,
@ -428,7 +549,7 @@ function SampledSignals.unsafe_write(
# shorter-than-requested frame count instead of throwing an error
handle_xrun(
stream,
Pa_WriteStream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
write_stream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
recover_xruns,
)
nwritten += n
@ -437,6 +558,24 @@ function SampledSignals.unsafe_write(
nwritten
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!(
source::PortAudioSource,
buf::Array,
@ -455,7 +594,7 @@ function SampledSignals.unsafe_read!(
# shorter-than-requested frame count instead of throwing an error
handle_xrun(
stream,
Pa_ReadStream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
read_stream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
recover_xruns,
)
# de-interleave the samples
@ -476,11 +615,11 @@ function prefill_output(sink::PortAudioSink)
stream = sink.stream
pointer = stream.pointer_ref[]
chunkbuf = stream.sink_buffer.chunkbuf
towrite = Pa_GetStreamWriteAvailable(pointer)
towrite = handle_status(@locked Pa_GetStreamWriteAvailable(pointer))
while towrite > 0
n = min(towrite, CHUNKFRAMES)
fill!(chunkbuf, zero(eltype(chunkbuf)))
Pa_WriteStream(pointer, chunkbuf, n, warn_xruns = false)
write_stream(pointer, chunkbuf, n, warn_xruns = false)
towrite -= n
end
end
@ -495,10 +634,10 @@ function discard_input(source::PortAudioSource)
stream = source.stream
pointer = stream.pointer_ref[]
chunkbuf = stream.source_buffer.chunkbuf
toread = Pa_GetStreamReadAvailable(pointer)
toread = handle_status(@locked Pa_GetStreamReadAvailable(pointer))
while toread > 0
n = min(toread, CHUNKFRAMES)
Pa_ReadStream(pointer, chunkbuf, n, warn_xruns = false)
read_stream(pointer, chunkbuf, n, warn_xruns = false)
toread -= n
end
end
@ -543,10 +682,10 @@ function __init__()
# junk to STDOUT on initialization, so we swallow it.
# TODO: actually check the junk to make sure there's nothing in there we
# don't expect
@stderr_as_debug Pa_Initialize()
@stderr_as_debug handle_status(@locked Pa_Initialize())
atexit() do
Pa_Terminate()
handle_status(@locked Pa_Terminate())
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 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)
const PA_NO_ERROR = 0
const PA_INPUT_OVERFLOWED = -10000 + 19
const PA_OUTPUT_UNDERFLOWED = -10000 + 20
# sample format types
const paFloat32 = PaSampleFormat(0x01)
const paInt32 = PaSampleFormat(0x02)
const paInt24 = PaSampleFormat(0x04)
const paInt16 = PaSampleFormat(0x08)
const paInt8 = PaSampleFormat(0x10)
const paUInt8 = PaSampleFormat(0x20)
const paNonInterleaved = PaSampleFormat(0x80000000)
const type_to_fmt = Dict{Type, PaSampleFormat}(
Float32 => 1,
Int32 => 2,
# Int24 => 4,
Int16 => 8,
Int8 => 16,
UInt8 => 3,
)
const PaStreamCallbackResult = Cint
# Callback return values
const paContinue = PaStreamCallbackResult(0)
const paComplete = PaStreamCallbackResult(1)
const paAbort = PaStreamCallbackResult(2)
function safe_load(result, an_error)
if result == C_NULL
throw(an_error)
end
unsafe_load(result)
@enum PaErrorCode::Int32 begin
paNoError = 0
paNotInitialized = -10000
paUnanticipatedHostError = -9999
paInvalidChannelCount = -9998
paInvalidSampleRate = -9997
paInvalidDevice = -9996
paInvalidFlag = -9995
paSampleFormatNotSupported = -9994
paBadIODeviceCombination = -9993
paInsufficientMemory = -9992
paBufferTooBig = -9991
paBufferTooSmall = -9990
paNullCallback = -9989
paBadStreamPtr = -9988
paTimedOut = -9987
paInternalError = -9986
paDeviceUnavailable = -9985
paIncompatibleHostApiSpecificStreamInfo = -9984
paStreamIsStopped = -9983
paStreamIsNotStopped = -9982
paInputOverflowed = -9981
paOutputUnderflowed = -9980
paHostApiNotFound = -9979
paInvalidHostApi = -9978
paCanNotReadFromACallbackStream = -9977
paCanNotWriteToACallbackStream = -9976
paCanNotReadFromAnOutputOnlyStream = -9975
paCanNotWriteToAnInputOnlyStream = -9974
paIncompatibleStreamHostApi = -9973
paBadBufferPtr = -9972
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
function Pa_GetErrorText(errorCode)
ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), errorCode)
end
function Pa_Initialize()
handle_status(@locked ccall((:Pa_Initialize, libportaudio), PaError, ()))
nothing
ccall((:Pa_Initialize, libportaudio), PaError, ())
end
function Pa_Terminate()
handle_status(@locked ccall((:Pa_Terminate, libportaudio), PaError, ()))
nothing
ccall((:Pa_Terminate, libportaudio), PaError, ())
end
Pa_GetVersion() = @locked ccall((:Pa_GetVersion, libportaudio), Cint, ())
const PaDeviceIndex = Cint
function Pa_GetVersionText()
unsafe_string(@locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()))
const PaHostApiIndex = Cint
function Pa_GetHostApiCount()
ccall((:Pa_GetHostApiCount, libportaudio), PaHostApiIndex, ())
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
# unique type ID that tells you which native backend it is (JACK, ALSA, ASIO,
# etc.). On a given system you can identify each backend by its index, which
# will range between 0 and Pa_GetHostApiCount() - 1. You can enumerate through
# all the host APIs on the system by iterating through those values.
# PaHostApiTypeId values
const pa_host_api_names = Dict{PaHostApiTypeId, String}(
0 => "In Development", # use while developing support for a new host API
1 => "Direct Sound",
2 => "MME",
3 => "ASIO",
4 => "Sound Manager",
5 => "Core Audio",
7 => "OSS",
8 => "ALSA",
9 => "AL",
10 => "BeOS",
11 => "WDMKS",
12 => "Jack",
13 => "WASAPI",
14 => "AudioScience HPI",
)
@enum PaHostApiTypeId::UInt32 begin
paInDevelopment = 0
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
end
mutable struct PaHostApiInfo
struct_version::Cint
api_type::PaHostApiTypeId
structVersion::Cint
type::PaHostApiTypeId
name::Ptr{Cchar}
deviceCount::Cint
defaultInputDevice::PaDeviceIndex
defaultOutputDevice::PaDeviceIndex
end
function Pa_GetHostApiInfo(i)
safe_load(
(@locked ccall(
(:Pa_GetHostApiInfo, libportaudio),
Ptr{PaHostApiInfo},
(PaHostApiIndex,),
i,
)),
BoundsError(Pa_GetHostApiInfo, i),
function Pa_GetHostApiInfo(hostApi)
ccall(
(:Pa_GetHostApiInfo, libportaudio),
Ptr{PaHostApiInfo},
(PaHostApiIndex,),
hostApi,
)
end
# Device Functions
function Pa_HostApiTypeIdToHostApiIndex(type)
ccall(
(:Pa_HostApiTypeIdToHostApiIndex, libportaudio),
PaHostApiIndex,
(PaHostApiTypeId,),
type,
)
end
mutable struct PaDeviceInfo
struct_version::Cint
name::Ptr{Cchar}
host_api::PaHostApiIndex
max_input_channels::Cint
max_output_channels::Cint
default_low_input_latency::PaTime
default_low_output_latency::PaTime
default_high_input_latency::PaTime
default_high_output_latency::PaTime
default_sample_rate::Cdouble
function Pa_HostApiDeviceIndexToDeviceIndex(hostApi, hostApiDeviceIndex)
ccall(
(:Pa_HostApiDeviceIndexToDeviceIndex, libportaudio),
PaDeviceIndex,
(PaHostApiIndex, Cint),
hostApi,
hostApiDeviceIndex,
)
end
mutable struct PaHostErrorInfo
hostApiType::PaHostApiTypeId
errorCode::Clong
errorText::Ptr{Cchar}
end
function Pa_GetLastHostErrorInfo()
ccall((:Pa_GetLastHostErrorInfo, libportaudio), Ptr{PaHostErrorInfo}, ())
end
function Pa_GetDeviceCount()
handle_status(@locked 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),
)
ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ())
end
function Pa_GetDefaultInputDevice()
handle_status(
@locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
)
ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
end
function Pa_GetDefaultOutputDevice()
handle_status(
@locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
)
ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
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
channelCount::Cint
sampleFormat::PaSampleFormat
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
mutable struct PaStreamInfo
@ -194,158 +332,108 @@ mutable struct PaStreamInfo
sampleRate::Cdouble
end
convert_nothing(::Nothing) = C_NULL
convert_nothing(something) = something
# 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
function Pa_GetStreamInfo(stream)
ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo}, (Ptr{PaStream},), stream)
end
function Pa_StartStream(stream::PaStream)
handle_status(
@locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
function Pa_GetStreamTime(stream)
ccall((:Pa_GetStreamTime, libportaudio), PaTime, (Ptr{PaStream},), stream)
end
function Pa_StopStream(stream::PaStream)
handle_status(
@locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
function Pa_GetStreamCpuLoad(stream)
ccall((:Pa_GetStreamCpuLoad, libportaudio), Cdouble, (Ptr{PaStream},), stream)
end
function Pa_CloseStream(stream::PaStream)
handle_status(
@locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
end
function Pa_GetStreamReadAvailable(stream::PaStream)
handle_status(
@locked ccall(
(:Pa_GetStreamReadAvailable, libportaudio),
Clong,
(PaStream,),
stream,
)
function Pa_ReadStream(stream, buffer, frames)
ccall(
(:Pa_ReadStream, libportaudio),
PaError,
(Ptr{PaStream}, Ptr{Cvoid}, Culong),
stream,
buffer,
frames,
)
end
function Pa_GetStreamWriteAvailable(stream::PaStream)
handle_status(
@locked ccall(
(:Pa_GetStreamWriteAvailable, libportaudio),
Clong,
(PaStream,),
stream,
)
function Pa_WriteStream(stream, buffer, frames)
ccall(
(:Pa_WriteStream, libportaudio),
PaError,
(Ptr{PaStream}, Ptr{Cvoid}, Culong),
stream,
buffer,
frames,
)
end
function Pa_ReadStream(stream::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 ccall(
(:Pa_ReadStream, libportaudio),
PaError,
(PaStream, Ptr{Cvoid}, Culong),
stream,
buf,
frames,
)
end,
warn_xruns = warn_xruns,
)
function Pa_GetStreamReadAvailable(stream)
ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, (Ptr{PaStream},), stream)
end
function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer; warn_xruns = true)
handle_status(
disable_sigint() do
@tcall @locked ccall(
(:Pa_WriteStream, libportaudio),
PaError,
(PaStream, Ptr{Cvoid}, Culong),
stream,
buf,
frames,
)
end,
warn_xruns = warn_xruns,
)
function Pa_GetStreamWriteAvailable(stream)
ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, (Ptr{PaStream},), stream)
end
# function Pa_GetStreamInfo(stream::PaStream)
# safe_load(
# ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo},
# (PaStream, ), stream),
# ArgumentError("Error getting stream info. Is the stream already closed?")
# )
# end
#
# General utility function to handle the status from the Pa_* functions
function handle_status(err::Integer; warn_xruns::Bool = true)
if err < 0
msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err)
if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED
if warn_xruns
@warn("libportaudio: " * unsafe_string(msg))
end
else
throw(ErrorException("libportaudio: " * unsafe_string(msg)))
end
function Pa_GetSampleSize(format)
ccall((:Pa_GetSampleSize, libportaudio), PaError, (PaSampleFormat,), format)
end
function Pa_Sleep(msec)
ccall((:Pa_Sleep, libportaudio), Cvoid, (Clong,), msec)
end
const paNoDevice = PaDeviceIndex(-1)
const paUseHostApiSpecificDeviceSpecification = PaDeviceIndex(-2)
const paFloat32 = PaSampleFormat(0x00000001)
const paInt32 = PaSampleFormat(0x00000002)
const paInt24 = PaSampleFormat(0x00000004)
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
err
end
end # module

View file

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