use CLANG wrappers
This commit is contained in:
parent
619c79c489
commit
4f62de7fda
5 changed files with 584 additions and 332 deletions
16
gen/generator.jl
Normal file
16
gen/generator.jl
Normal 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
9
gen/generator.toml
Normal 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
|
217
src/PortAudio.jl
217
src/PortAudio.jl
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Add table
Reference in a new issue