This commit is contained in:
Brandon Taylor 2021-06-14 10:10:02 -04:00
parent 6a018cfc32
commit 4cf4bc8e7a
2 changed files with 327 additions and 310 deletions

View file

@ -9,8 +9,7 @@ import Base: eltype, getproperty, show
import Base: close, isopen
import Base: read, read!, write
using LinearAlgebra: LinearAlgebra
import LinearAlgebra: transpose!
using LinearAlgebra: transpose!
export PortAudioStream
@ -36,42 +35,45 @@ function versioninfo(io::IO = stdout)
println(io, "Version: ", Pa_GetVersion())
end
struct Bounds
max_channels::Int
low_latency::Float64
high_latency::Float64
end
struct PortAudioDevice
name::String
hostapi::String
maxinchans::Int
maxoutchans::Int
defaultsamplerate::Float64
idx::PaDeviceIndex
lowinputlatency::Float64
lowoutputlatency::Float64
highinputlatency::Float64
highoutputlatency::Float64
input_bounds::Bounds
output_bounds::Bounds
end
function PortAudioDevice(info::PaDeviceInfo, idx)
PortAudioDevice(
unsafe_string(info.name),
unsafe_string(Pa_GetHostApiInfo(info.host_api).name),
info.max_input_channels,
info.max_output_channels,
info.default_sample_rate,
idx,
info.default_low_input_latency,
info.default_low_output_latency,
info.default_high_input_latency,
info.default_high_output_latency,
Bounds(
info.max_input_channels,
info.default_low_input_latency,
info.default_high_input_latency,
),
Bounds(
info.max_output_channels,
info.default_low_output_latency,
info.default_high_output_latency,
),
)
end
function devices()
ndevices = Pa_GetDeviceCount()
infos = PaDeviceInfo[Pa_GetDeviceInfo(i) for i in 0:(ndevices - 1)]
PortAudioDevice[PortAudioDevice(info, idx - 1) for (idx, info) in enumerate(infos)]
end
name(device::PortAudioDevice) = device.name
# not for external use, used in error message printing
devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n")
function devices()
[PortAudioDevice(Pa_GetDeviceInfo(i), i) for i in 0:(Pa_GetDeviceCount() - 1)]
end
struct Buffer{T}
device::PortAudioDevice
@ -91,69 +93,36 @@ struct PortAudioStream{T}
recover_xruns::Bool
sink_buffer::Buffer{T}
source_buffer::Buffer{T}
end
# this inner constructor is generally called via the top-level outer
# constructor below
# TODO: pre-fill outbut buffer on init
# TODO: recover from xruns - currently with low latencies (e.g. 0.01) it
# will run fine for a while and then fail with the first xrun.
# TODO: figure out whether we can get deterministic latency...
function PortAudioStream{T}(
indev::PortAudioDevice,
outdev::PortAudioDevice,
inchans,
outchans,
sr,
latency,
warn_xruns,
recover_xruns,
) where {T}
inchans = inchans == -1 ? indev.maxinchans : inchans
outchans = outchans == -1 ? outdev.maxoutchans : outchans
inparams = if (inchans == 0)
Ptr{Pa_StreamParameters}(0)
else
Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], latency, C_NULL))
end
outparams = if (outchans == 0)
Ptr{Pa_StreamParameters}(0)
else
Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], latency, C_NULL))
end
# finalizer(close, this)
pointer_ref = @stderr_as_debug Pa_OpenStream(
inparams,
outparams,
sr,
0,
paNoFlag,
nothing,
nothing,
function make_parameters(device, channels, T, latency, host_api_specific_stream_info)
if channels == 0
Ptr{Pa_StreamParameters}(0)
else
Ref(
Pa_StreamParameters(
device.idx,
channels,
type_to_fmt[T],
latency,
convert_nothing(host_api_specific_stream_info),
),
)
sink_buffer = Buffer{T}(outdev, outchans)
source_buffer = Buffer{T}(indev, inchans)
Pa_StartStream(pointer_ref[])
this = new(
sr,
latency,
pointer_ref,
warn_xruns,
recover_xruns,
sink_buffer,
source_buffer
)
# pre-fill the output stream so we're less likely to underrun
prefill_output(this.sink)
end
end
this
function fill_max_channels(channels, bounds)
if channels === max
bounds.max_channels
else
channels
end
end
function recover_xrun(stream::PortAudioStream)
playback = nchannels(stream.sink) > 0
capture = nchannels(stream.source) > 0
if playback && capture
sink = stream.sink
source = stream.source
if nchannels(sink) > 0 && nchannels(source) > 0
# the best we can do to avoid further xruns is to fill the playback buffer and
# discard the capture buffer. Really there's a fundamental problem with our
# read/write-based API where you don't know whether we're currently in a state
@ -161,13 +130,13 @@ function recover_xrun(stream::PortAudioStream)
# move to some kind of transaction API that forces them to be balanced, and also
# gives a way for the application to signal that the same number of samples
# should have been read as written.
discard_input(stream.source)
prefill_output(stream.sink)
discard_input(source)
prefill_output(sink)
end
end
function defaultlatency(devices...)
maximum(d -> max(d.highoutputlatency, d.highinputlatency), devices)
function defaultlatency(input_device, output_device)
max(input_device.input_bounds.high_latency, output_device.output_bounds.high_latency)
end
function combine_default_sample_rates(inchans, sampleratein, outchans, samplerateout)
@ -218,29 +187,62 @@ function PortAudioStream(
inchans = 2,
outchans = 2;
eltype = Float32,
samplerate = -1,
samplerate = combine_default_sample_rates(
inchans,
indev.defaultsamplerate,
outchans,
outdev.defaultsamplerate,
),
latency = defaultlatency(indev, outdev),
warn_xruns = false,
recover_xruns = true,
frames_per_buffer = 0,
flags = paNoFlag,
callback = nothing,
user_data = nothing,
input_info = nothing,
output_info = nothing,
)
if samplerate == -1
samplerate = combine_default_sample_rates(
inchans,
indev.defaultsamplerate,
outchans,
outdev.defaultsamplerate,
)
end
PortAudioStream{eltype}(
indev,
outdev,
inchans,
outchans,
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,
)
Pa_StartStream(pointer_ref[])
this = PortAudioStream{eltype}(
samplerate,
latency,
pointer_ref,
warn_xruns,
recover_xruns,
Buffer{eltype}(outdev, outchans),
Buffer{eltype}(indev, inchans),
)
# pre-fill the output stream so we're less likely to underrun
prefill_output(this.sink)
this
end
function device_by_name(device_name)
error_message = IOBuffer()
write(error_message, "No device matching ")
write(error_message, repr(device_name))
write(error_message, " found.\nAvailable Devices:\n")
for device in devices()
potential_match = name(device)
if potential_match == device_name
return device
end
write(error_message, repr(potential_match))
write(error_message, '\n')
end
error(String(take!(error_message)))
end
# handle device names given as streams
@ -250,33 +252,19 @@ function PortAudioStream(
args...;
kwargs...,
)
indev = nothing
outdev = nothing
for d in devices()
if d.name == indevname
indev = d
end
if d.name == outdevname
outdev = d
end
end
if indev == nothing
error("No device matching \"$indevname\" found.\nAvailable Devices:\n$(devnames())")
end
if outdev == nothing
error(
"No device matching \"$outdevname\" found.\nAvailable Devices:\n$(devnames())",
)
end
PortAudioStream(indev, outdev, args...; kwargs...)
PortAudioStream(
device_by_name(indevname),
device_by_name(outdevname),
args...;
kwargs...,
)
end
# if one device is given, use it for input and output, but set inchans=0 so we
# end up with an output-only stream
function PortAudioStream(
device::Union{PortAudioDevice, AbstractString},
inchans = 2,
inchans = 0,
outchans = 2;
kwargs...,
)
@ -286,10 +274,14 @@ end
# use the default input and output devices
function PortAudioStream(inchans = 2, outchans = 2; kwargs...)
inidx = Pa_GetDefaultInputDevice()
indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx)
outidx = Pa_GetDefaultOutputDevice()
outdevice = PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx)
PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...)
PortAudioStream(
PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx),
PortAudioDevice(Pa_GetDeviceInfo(outidx), outidx),
inchans,
outchans;
kwargs...,
)
end
# handle do-syntax
@ -303,10 +295,12 @@ function PortAudioStream(fn::Function, args...; kwargs...)
end
function close(stream::PortAudioStream)
if stream.pointer_ref[] != C_NULL
Pa_StopStream(stream.pointer_ref[])
Pa_CloseStream(stream.pointer_ref[])
stream.pointer_ref[] = C_NULL
pointer_ref = stream.pointer_ref
pointer = pointer_ref[]
if pointer != C_NULL
Pa_StopStream(pointer)
Pa_CloseStream(pointer)
pointer_ref[] = C_NULL
end
nothing
end
@ -314,7 +308,7 @@ end
isopen(stream::PortAudioStream) = stream.pointer_ref[] != C_NULL
SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate
eltype(stream::PortAudioStream{T}) where {T} = T
eltype(::Type{PortAudioStream{T}}) where {T} = T
read(stream::PortAudioStream, args...) = read(stream.source, args...)
read!(stream::PortAudioStream, args...) = read!(stream.source, args...)
@ -325,27 +319,11 @@ end
function show(io::IO, stream::PortAudioStream)
println(io, typeof(stream))
println(io, " Samplerate: ", samplerate(stream), "Hz")
if nchannels(stream.sink) > 0
print(
io,
"\n ",
nchannels(stream.sink),
" channel sink: \"",
name(stream.sink),
"\"",
)
end
if nchannels(stream.source) > 0
print(
io,
"\n ",
nchannels(stream.source),
" channel source: \"",
name(stream.source),
"\"",
)
end
print(io, " Samplerate: ", samplerate(stream), "Hz")
print(io, "\n ")
show(io, stream.sink)
print(io, "\n ")
show(io, stream.source)
end
#
@ -361,7 +339,7 @@ end
# provided for backwards compatibility
function getproperty(stream::PortAudioStream, property::Symbol)
if property === :sink
if property === :sink
PortAudioSink(stream)
elseif property === :source
PortAudioSource(stream)
@ -370,37 +348,58 @@ function getproperty(stream::PortAudioStream, property::Symbol)
end
end
function Buffer{T}(device, channels) where T
function Buffer{T}(device, channels) where {T}
# portaudio data comes in interleaved, so we'll end up transposing
# it back and forth to julia column-major
chunkbuf = zeros(T, channels, CHUNKFRAMES)
Buffer(device, chunkbuf, channels)
end
SampledSignals.nchannels(s::PortAudioSource) = s.stream.source_buffer.nchannels
SampledSignals.nchannels(s::PortAudioSink) = s.stream.sink_buffer.nchannels
SampledSignals.nchannels(buffer::Buffer) = buffer.nchannels
name(buffer::Buffer) = name(buffer.device)
SampledSignals.nchannels(s::PortAudioSource) = nchannels(s.stream.source_buffer)
SampledSignals.nchannels(s::PortAudioSink) = nchannels(s.stream.sink_buffer)
SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream)
eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T
function close(s::Union{PortAudioSink, PortAudioSource})
eltype(::Type{<:Union{PortAudioSink{T}, PortAudioSource{T}}}) where {T} = T
function close(::Union{PortAudioSink, PortAudioSource})
throw(ErrorException("""
Attempted to close PortAudioSink or PortAudioSource.
Close the containing PortAudioStream instead
"""))
end
isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.stream)
name(s::PortAudioSink) = s.stream.sink_buffer.device.name
name(s::PortAudioSource) = s.stream.source_buffer.device.name
name(s::PortAudioSink) = name(s.stream.sink_buffer)
name(s::PortAudioSource) = name(s.stream.source_buffer)
function show(io::IO, ::Type{PortAudioSink{T}}) where {T}
print(io, "PortAudioSink{$T}")
kind(::PortAudioSink) = "sink"
kind(::PortAudioSource) = "source"
function show(io::IO, sink_or_source::Union{PortAudioSink, PortAudioSource})
print(
io,
nchannels(sink_or_source),
" channel ",
kind(sink_or_source),
": ",
repr(name(sink_or_source)),
)
end
function show(io::IO, ::Type{PortAudioSource{T}}) where {T}
print(io, "PortAudioSource{$T}")
function interleave!(long, wide, n, already, offset, wide_to_long)
long_view = view(long, (1:n) .+ already .+ offset, :)
wide_view = view(wide, :, 1:n)
if wide_to_long
transpose!(long_view, wide_view)
else
transpose!(wide_view, long_view)
end
end
function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}}
print(io, nchannels(stream), "-channel ", T, "(\"", name(stream), "\")")
function handle_xrun(stream, error_code, recover_xruns)
if recover_xruns &&
(error_code == PA_OUTPUT_UNDERFLOWED || error_code == PA_INPUT_OVERFLOWED)
recover_xrun(stream)
end
end
function SampledSignals.unsafe_write(
@ -409,21 +408,23 @@ function SampledSignals.unsafe_write(
frameoffset,
framecount,
)
stream = sink.stream
pointer = stream.pointer_ref[]
chunkbuf = stream.sink_buffer.chunkbuf
warn_xruns = stream.warn_xruns
recover_xruns = stream.recover_xruns
nwritten = 0
sink_buffer = sink.stream.sink_buffer
while nwritten < framecount
n = min(framecount - nwritten, CHUNKFRAMES)
# make a buffer of interleaved samples
transpose!(
view(sink_buffer.chunkbuf, :, 1:n),
view(buf, (1:n) .+ nwritten .+ frameoffset, :),
)
interleave!(buf, chunkbuf, n, nwritten, frameoffset, false)
# TODO: if the stream is closed we just want to return a
# shorter-than-requested frame count instead of throwing an error
err = Pa_WriteStream(sink.stream.pointer_ref[], sink_buffer.chunkbuf, n, sink.stream.warn_xruns)
if err (PA_OUTPUT_UNDERFLOWED, PA_INPUT_OVERFLOWED) && sink.stream.recover_xruns
recover_xrun(sink.stream)
end
handle_xrun(
stream,
Pa_WriteStream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
recover_xruns,
)
nwritten += n
end
@ -436,27 +437,23 @@ function SampledSignals.unsafe_read!(
frameoffset,
framecount,
)
source_buffer = source.stream.source_buffer
stream = source.stream
pointer = stream.pointer_ref[]
chunkbuf = stream.source_buffer.chunkbuf
warn_xruns = stream.warn_xruns
recover_xruns = stream.recover_xruns
nread = 0
while nread < framecount
n = min(framecount - nread, CHUNKFRAMES)
# TODO: if the stream is closed we just want to return a
# shorter-than-requested frame count instead of throwing an error
err = Pa_ReadStream(
source.stream.pointer_ref[],
source_buffer.chunkbuf,
n,
source.stream.warn_xruns,
handle_xrun(
stream,
Pa_ReadStream(pointer, chunkbuf, n, warn_xruns = warn_xruns),
recover_xruns,
)
if err (PA_OUTPUT_UNDERFLOWED, PA_INPUT_OVERFLOWED) && source.stream.recover_xruns
recover_xrun(source.stream)
end
# de-interleave the samples
transpose!(
view(buf, (1:n) .+ nread .+ frameoffset, :),
view(source_buffer.chunkbuf, :, 1:n),
)
interleave!(buf, chunkbuf, n, nread, frameoffset, true)
nread += n
end
@ -470,12 +467,14 @@ Fill the playback buffer of the given sink.
"""
function prefill_output(sink::PortAudioSink)
if nchannels(sink) > 0
towrite = Pa_GetStreamWriteAvailable(sink.stream.pointer_ref[])
sink_buffer = sink.stream.sink_buffer
stream = sink.stream
pointer = stream.pointer_ref[]
chunkbuf = stream.sink_buffer.chunkbuf
towrite = Pa_GetStreamWriteAvailable(pointer)
while towrite > 0
n = min(towrite, CHUNKFRAMES)
fill!(sink_buffer.chunkbuf, zero(eltype(sink_buffer.chunkbuf)))
Pa_WriteStream(sink.stream.pointer_ref[], sink_buffer.chunkbuf, n, false)
fill!(chunkbuf, zero(eltype(chunkbuf)))
Pa_WriteStream(pointer, chunkbuf, n, warn_xruns = false)
towrite -= n
end
end
@ -487,11 +486,13 @@ end
Read and discard data from the capture buffer.
"""
function discard_input(source::PortAudioSource)
toread = Pa_GetStreamReadAvailable(source.stream.pointer_ref[])
source_buffer = source.stream.source_buffer
stream = source.stream
pointer = stream.pointer_ref[]
chunkbuf = stream.source_buffer.chunkbuf
toread = Pa_GetStreamReadAvailable(pointer)
while toread > 0
n = min(toread, CHUNKFRAMES)
Pa_ReadStream(source.stream.pointer_ref[], source_buffer.chunkbuf, n, false)
Pa_ReadStream(pointer, chunkbuf, n, warn_xruns = false)
toread -= n
end
end
@ -543,4 +544,4 @@ function __init__()
end
end
end # module PortAudio
end # module PortAudio

View file

@ -43,6 +43,13 @@ 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)
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
@ -65,20 +72,19 @@ macro locked(ex)
end
function Pa_Initialize()
err = @locked ccall((:Pa_Initialize, libportaudio), PaError, ())
handle_status(err)
handle_status(@locked ccall((:Pa_Initialize, libportaudio), PaError, ()))
nothing
end
function Pa_Terminate()
err = @locked ccall((:Pa_Terminate, libportaudio), PaError, ())
handle_status(err)
handle_status(@locked ccall((:Pa_Terminate, libportaudio), PaError, ()))
nothing
end
Pa_GetVersion() = @locked ccall((:Pa_GetVersion, libportaudio), Cint, ())
function Pa_GetVersionText()
versionPtr = @locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ())
unsafe_string(versionPtr)
unsafe_string(@locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()))
end
# Host API Functions
@ -117,16 +123,15 @@ mutable struct PaHostApiInfo
end
function Pa_GetHostApiInfo(i)
result = @locked ccall(
(:Pa_GetHostApiInfo, libportaudio),
Ptr{PaHostApiInfo},
(PaHostApiIndex,),
i,
safe_load(
(@locked ccall(
(:Pa_GetHostApiInfo, libportaudio),
Ptr{PaHostApiInfo},
(PaHostApiIndex,),
i,
)),
BoundsError(Pa_GetHostApiInfo, i),
)
if result == C_NULL
throw(BoundsError(Pa_GetHostApiInfo, i))
end
unsafe_load(result)
end
# Device Functions
@ -144,27 +149,32 @@ mutable struct PaDeviceInfo
default_sample_rate::Cdouble
end
Pa_GetDeviceCount() = @locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ())
function Pa_GetDeviceCount()
handle_status(@locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()))
end
function Pa_GetDeviceInfo(i)
result = @locked ccall(
(:Pa_GetDeviceInfo, libportaudio),
Ptr{PaDeviceInfo},
(PaDeviceIndex,),
i,
safe_load(
(@locked ccall(
(:Pa_GetDeviceInfo, libportaudio),
Ptr{PaDeviceInfo},
(PaDeviceIndex,),
i,
)),
BoundsError(Pa_GetDeviceInfo, i),
)
if result == C_NULL
throw(BoundsError(Pa_GetDeviceInfo, i))
end
unsafe_load(result)
end
function Pa_GetDefaultInputDevice()
@locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
handle_status(
@locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ())
)
end
function Pa_GetDefaultOutputDevice()
@locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
handle_status(
@locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ())
)
end
# Stream Functions
@ -184,18 +194,19 @@ 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)
# err = ccall((:Pa_OpenDefaultStream, libportaudio),
# 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)
# handle_status(err)
#
# framesPerBuffer, C_NULL, C_NULL))
# streamPtr[]
# end
#
@ -206,130 +217,135 @@ function Pa_OpenStream(
framesPerBuffer,
flags::PaStreamFlags,
callback,
userdata,
)
userdata::UserData,
) where {UserData}
streamPtr = Ref{PaStream}(0)
err = @locked ccall(
(:Pa_OpenStream, libportaudio),
PaError,
(
Ref{PaStream},
Ref{Pa_StreamParameters},
Ref{Pa_StreamParameters},
Cdouble,
Culong,
PaStreamFlags,
Ref{Cvoid},
# it seems like we should be able to use Ref{T} here, with
# userdata::T above, and avoid the `pointer_from_objref` below.
# that's not working on 0.6 though, and it shouldn't really
# matter because userdata should be GC-rooted anyways
Ptr{Cvoid},
),
streamPtr,
inParams,
outParams,
float(sampleRate),
framesPerBuffer,
flags,
callback === nothing ? C_NULL : callback,
userdata === nothing ? C_NULL : pointer_from_objref(userdata),
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),
)
)
handle_status(err)
streamPtr
end
function Pa_StartStream(stream::PaStream)
err = @locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream)
handle_status(err)
handle_status(
@locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
end
function Pa_StopStream(stream::PaStream)
err = @locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream)
handle_status(err)
handle_status(
@locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
end
function Pa_CloseStream(stream::PaStream)
err = @locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream)
handle_status(err)
handle_status(
@locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream)
)
nothing
end
function Pa_GetStreamReadAvailable(stream::PaStream)
avail = @locked ccall(
(:Pa_GetStreamReadAvailable, libportaudio),
Clong,
(PaStream,),
stream,
handle_status(
@locked ccall(
(:Pa_GetStreamReadAvailable, libportaudio),
Clong,
(PaStream,),
stream,
)
)
avail >= 0 || handle_status(avail)
avail
end
function Pa_GetStreamWriteAvailable(stream::PaStream)
avail = @locked ccall(
(:Pa_GetStreamWriteAvailable, libportaudio),
Clong,
(PaStream,),
stream,
handle_status(
@locked ccall(
(:Pa_GetStreamWriteAvailable, libportaudio),
Clong,
(PaStream,),
stream,
)
)
avail >= 0 || handle_status(avail)
avail
end
function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer, show_warnings = true)
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.
err = disable_sigint() do
@tcall @locked ccall(
(:Pa_ReadStream, libportaudio),
PaError,
(PaStream, Ptr{Cvoid}, Culong),
stream,
buf,
frames,
)
end
handle_status(err, show_warnings)
err
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
function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer, show_warnings = true)
err = disable_sigint() do
@tcall @locked ccall(
(:Pa_WriteStream, libportaudio),
PaError,
(PaStream, Ptr{Cvoid}, Culong),
stream,
buf,
frames,
)
end
handle_status(err, show_warnings)
err
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,
)
end
# function Pa_GetStreamInfo(stream::PaStream)
# infoptr = ccall((:Pa_GetStreamInfo, libportaudio), Ptr{PaStreamInfo},
# (PaStream, ), stream)
# if infoptr == C_NULL
# error("Error getting stream info. Is the stream already closed?")
# end
# unsafe_load(infoptr)
# 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, show_warnings::Bool = true)
if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED
if show_warnings
msg =
@locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err)
@warn("libportaudio: " * unsafe_string(msg))
end
elseif err != PA_NO_ERROR
function handle_status(err::Integer; warn_xruns::Bool = true)
if err < 0
msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err)
throw(ErrorException("libportaudio: " * unsafe_string(msg)))
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
end
err
end