Avoid circular type definition (#78)
* avoid recursion * reuse ref * fix
This commit is contained in:
parent
b3cddf5669
commit
6a018cfc32
2 changed files with 70 additions and 44 deletions
112
src/PortAudio.jl
112
src/PortAudio.jl
|
@ -5,7 +5,7 @@ using libportaudio_jll: libportaudio
|
|||
using SampledSignals
|
||||
using Suppressor: @capture_err
|
||||
|
||||
import Base: eltype, show
|
||||
import Base: eltype, getproperty, show
|
||||
import Base: close, isopen
|
||||
import Base: read, read!, write
|
||||
|
||||
|
@ -36,7 +36,7 @@ function versioninfo(io::IO = stdout)
|
|||
println(io, "Version: ", Pa_GetVersion())
|
||||
end
|
||||
|
||||
mutable struct PortAudioDevice
|
||||
struct PortAudioDevice
|
||||
name::String
|
||||
hostapi::String
|
||||
maxinchans::Int
|
||||
|
@ -73,18 +73,24 @@ end
|
|||
# not for external use, used in error message printing
|
||||
devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n")
|
||||
|
||||
struct Buffer{T}
|
||||
device::PortAudioDevice
|
||||
chunkbuf::Array{T, 2}
|
||||
nchannels::Int
|
||||
end
|
||||
|
||||
#
|
||||
# PortAudioStream
|
||||
#
|
||||
|
||||
mutable struct PortAudioStream{T}
|
||||
struct PortAudioStream{T}
|
||||
samplerate::Float64
|
||||
latency::Float64
|
||||
stream::PaStream
|
||||
pointer_ref::Ref{PaStream}
|
||||
warn_xruns::Bool
|
||||
recover_xruns::Bool
|
||||
sink::Any # untyped because of circular type definition
|
||||
source::Any # untyped because of circular type definition
|
||||
sink_buffer::Buffer{T}
|
||||
source_buffer::Buffer{T}
|
||||
|
||||
# this inner constructor is generally called via the top-level outer
|
||||
# constructor below
|
||||
|
@ -115,11 +121,8 @@ mutable struct PortAudioStream{T}
|
|||
else
|
||||
Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], latency, C_NULL))
|
||||
end
|
||||
this = new(sr, latency, C_NULL, warn_xruns, recover_xruns)
|
||||
# finalizer(close, this)
|
||||
this.sink = PortAudioSink{T}(outdev.name, this, outchans)
|
||||
this.source = PortAudioSource{T}(indev.name, this, inchans)
|
||||
this.stream = @stderr_as_debug Pa_OpenStream(
|
||||
pointer_ref = @stderr_as_debug Pa_OpenStream(
|
||||
inparams,
|
||||
outparams,
|
||||
sr,
|
||||
|
@ -128,8 +131,18 @@ mutable struct PortAudioStream{T}
|
|||
nothing,
|
||||
nothing,
|
||||
)
|
||||
|
||||
Pa_StartStream(this.stream)
|
||||
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)
|
||||
|
||||
|
@ -290,16 +303,15 @@ function PortAudioStream(fn::Function, args...; kwargs...)
|
|||
end
|
||||
|
||||
function close(stream::PortAudioStream)
|
||||
if stream.stream != C_NULL
|
||||
Pa_StopStream(stream.stream)
|
||||
Pa_CloseStream(stream.stream)
|
||||
stream.stream = C_NULL
|
||||
if stream.pointer_ref[] != C_NULL
|
||||
Pa_StopStream(stream.pointer_ref[])
|
||||
Pa_CloseStream(stream.pointer_ref[])
|
||||
stream.pointer_ref[] = C_NULL
|
||||
end
|
||||
|
||||
nothing
|
||||
end
|
||||
|
||||
isopen(stream::PortAudioStream) = stream.stream != C_NULL
|
||||
isopen(stream::PortAudioStream) = stream.pointer_ref[] != C_NULL
|
||||
|
||||
SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate
|
||||
eltype(stream::PortAudioStream{T}) where {T} = T
|
||||
|
@ -342,22 +354,31 @@ end
|
|||
|
||||
# Define our source and sink types
|
||||
for (TypeName, Super) in ((:PortAudioSink, :SampleSink), (:PortAudioSource, :SampleSource))
|
||||
@eval mutable struct $TypeName{T} <: $Super
|
||||
name::String
|
||||
@eval struct $TypeName{T} <: $Super
|
||||
stream::PortAudioStream{T}
|
||||
chunkbuf::Array{T, 2}
|
||||
nchannels::Int
|
||||
|
||||
function $TypeName{T}(name, stream, 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)
|
||||
new(name, stream, chunkbuf, channels)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels
|
||||
# provided for backwards compatibility
|
||||
function getproperty(stream::PortAudioStream, property::Symbol)
|
||||
if property === :sink
|
||||
PortAudioSink(stream)
|
||||
elseif property === :source
|
||||
PortAudioSource(stream)
|
||||
else
|
||||
getfield(stream, property)
|
||||
end
|
||||
end
|
||||
|
||||
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.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream)
|
||||
eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T
|
||||
function close(s::Union{PortAudioSink, PortAudioSource})
|
||||
|
@ -367,7 +388,8 @@ function close(s::Union{PortAudioSink, PortAudioSource})
|
|||
"""))
|
||||
end
|
||||
isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.stream)
|
||||
name(s::Union{PortAudioSink, PortAudioSource}) = s.name
|
||||
name(s::PortAudioSink) = s.stream.sink_buffer.device.name
|
||||
name(s::PortAudioSource) = s.stream.source_buffer.device.name
|
||||
|
||||
function show(io::IO, ::Type{PortAudioSink{T}}) where {T}
|
||||
print(io, "PortAudioSink{$T}")
|
||||
|
@ -378,7 +400,7 @@ function show(io::IO, ::Type{PortAudioSource{T}}) where {T}
|
|||
end
|
||||
|
||||
function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}}
|
||||
print(io, nchannels(stream), "-channel ", T, "(\"", stream.name, "\")")
|
||||
print(io, nchannels(stream), "-channel ", T, "(\"", name(stream), "\")")
|
||||
end
|
||||
|
||||
function SampledSignals.unsafe_write(
|
||||
|
@ -388,16 +410,17 @@ function SampledSignals.unsafe_write(
|
|||
framecount,
|
||||
)
|
||||
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.chunkbuf, :, 1:n),
|
||||
view(sink_buffer.chunkbuf, :, 1:n),
|
||||
view(buf, (1:n) .+ nwritten .+ frameoffset, :),
|
||||
)
|
||||
# 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.stream, sink.chunkbuf, n, sink.stream.warn_xruns)
|
||||
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
|
||||
|
@ -413,14 +436,15 @@ function SampledSignals.unsafe_read!(
|
|||
frameoffset,
|
||||
framecount,
|
||||
)
|
||||
source_buffer = source.stream.source_buffer
|
||||
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.stream,
|
||||
source.chunkbuf,
|
||||
source.stream.pointer_ref[],
|
||||
source_buffer.chunkbuf,
|
||||
n,
|
||||
source.stream.warn_xruns,
|
||||
)
|
||||
|
@ -430,7 +454,7 @@ function SampledSignals.unsafe_read!(
|
|||
# de-interleave the samples
|
||||
transpose!(
|
||||
view(buf, (1:n) .+ nread .+ frameoffset, :),
|
||||
view(source.chunkbuf, :, 1:n),
|
||||
view(source_buffer.chunkbuf, :, 1:n),
|
||||
)
|
||||
|
||||
nread += n
|
||||
|
@ -446,11 +470,12 @@ Fill the playback buffer of the given sink.
|
|||
"""
|
||||
function prefill_output(sink::PortAudioSink)
|
||||
if nchannels(sink) > 0
|
||||
towrite = Pa_GetStreamWriteAvailable(sink.stream.stream)
|
||||
towrite = Pa_GetStreamWriteAvailable(sink.stream.pointer_ref[])
|
||||
sink_buffer = sink.stream.sink_buffer
|
||||
while towrite > 0
|
||||
n = min(towrite, CHUNKFRAMES)
|
||||
fill!(sink.chunkbuf, zero(eltype(sink.chunkbuf)))
|
||||
Pa_WriteStream(sink.stream.stream, sink.chunkbuf, n, false)
|
||||
fill!(sink_buffer.chunkbuf, zero(eltype(sink_buffer.chunkbuf)))
|
||||
Pa_WriteStream(sink.stream.pointer_ref[], sink_buffer.chunkbuf, n, false)
|
||||
towrite -= n
|
||||
end
|
||||
end
|
||||
|
@ -462,10 +487,11 @@ end
|
|||
Read and discard data from the capture buffer.
|
||||
"""
|
||||
function discard_input(source::PortAudioSource)
|
||||
toread = Pa_GetStreamReadAvailable(source.stream.stream)
|
||||
toread = Pa_GetStreamReadAvailable(source.stream.pointer_ref[])
|
||||
source_buffer = source.stream.source_buffer
|
||||
while toread > 0
|
||||
n = min(toread, CHUNKFRAMES)
|
||||
Pa_ReadStream(source.stream.stream, source.chunkbuf, n, false)
|
||||
Pa_ReadStream(source.stream.pointer_ref[], source_buffer.chunkbuf, n, false)
|
||||
toread -= n
|
||||
end
|
||||
end
|
||||
|
@ -517,4 +543,4 @@ function __init__()
|
|||
end
|
||||
end
|
||||
|
||||
end # module PortAudio
|
||||
end # module PortAudio
|
|
@ -236,7 +236,7 @@ function Pa_OpenStream(
|
|||
userdata === nothing ? C_NULL : pointer_from_objref(userdata),
|
||||
)
|
||||
handle_status(err)
|
||||
streamPtr[]
|
||||
streamPtr
|
||||
end
|
||||
|
||||
function Pa_StartStream(stream::PaStream)
|
||||
|
|
Loading…
Reference in a new issue