From 5823404f1a7f31590513b8b0f3c257b381da321f Mon Sep 17 00:00:00 2001 From: WooKyoung Noh Date: Thu, 21 Jun 2018 16:59:43 +0900 Subject: [PATCH 01/31] Compat Julia 0.7 --- .gitignore | 1 + .travis.yml | 1 + REQUIRE | 9 +++--- appveyor.yml | 1 + deps/build.jl | 3 +- src/PortAudio.jl | 73 +++++++++++++++++++++++++++++------------- src/libportaudio.jl | 22 ++++++------- src/pa_shim.jl | 40 ++++++++--------------- src/suppressor.jl | 4 +-- test/runtests.jl | 6 ++-- test/runtests_local.jl | 22 ++++++------- 11 files changed, 102 insertions(+), 80 deletions(-) diff --git a/.gitignore b/.gitignore index dd2812d..5362a77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.swp *.o deps/deps.jl +deps/build.log *.wav *.flac *.cov diff --git a/.travis.yml b/.travis.yml index 5d1b762..8534eb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ os: sudo: required julia: - 0.6 + - nightly notifications: email: false script: diff --git a/REQUIRE b/REQUIRE index ad4f72f..9379715 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,6 +1,7 @@ -julia 0.6.0-dev.2746 -BinDeps -SampledSignals 0.3.0 -RingBuffers 1.0.0 +julia 0.6 +BinDeps 0.8.8 +SampledSignals 1.1.2 +RingBuffers 1.1.2 +Compat 0.66.0 @osx Homebrew @windows WinRPM diff --git a/appveyor.yml b/appveyor.yml index a18e182..0570501 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,6 +2,7 @@ environment: matrix: - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" + - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" notifications: - provider: Email diff --git a/deps/build.jl b/deps/build.jl index 16eb07c..e672e0b 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,8 +1,9 @@ using BinDeps +using Compat @BinDeps.setup -ENV["JULIA_ROOT"] = abspath(JULIA_HOME, "../../") +ENV["JULIA_ROOT"] = abspath(Compat.Sys.BINDIR, "../../") # include alias for WinRPM library libportaudio = library_dependency("libportaudio", aliases=["libportaudio-2"]) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index c301b77..9fd9510 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1,30 +1,26 @@ -__precompile__() +__precompile__(true) module PortAudio using SampledSignals using RingBuffers -#using Suppressor +using Compat +import Compat: undef, fetch, @compat +import Compat.LinearAlgebra: transpose! import Base: eltype, show import Base: close, isopen import Base: read, read!, write, flush +export PortAudioStream + + # Get binary dependencies loaded from BinDeps include("../deps/deps.jl") include("suppressor.jl") include("pa_shim.jl") include("libportaudio.jl") -function __init__() - init_pa_shim() - global const notifycb_c = cfunction(notifycb, Cint, (Ptr{Void}, )) - # initialize PortAudio on module load - @suppress_err Pa_Initialize() -end - -export PortAudioStream - # These sizes are all in frames # the block size is what we request from portaudio if no blocksize is given. @@ -46,7 +42,7 @@ function versioninfo(io::IO=STDOUT) println(io, "Shim Source Hash: ", shimhash()[1:10]) end -type PortAudioDevice +mutable struct PortAudioDevice name::String hostapi::String maxinchans::Int @@ -76,7 +72,7 @@ devnames() = join(["\"$(dev.name)\"" for dev in devices()], "\n") # PortAudioStream ################## -type PortAudioStream{T} +mutable struct PortAudioStream{T} samplerate::Float64 blocksize::Int stream::PaStream @@ -98,7 +94,7 @@ type PortAudioStream{T} Ptr{Pa_StreamParameters}(0) : Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL)) this = new(sr, blocksize, C_NULL) - finalizer(this, close) + @compat finalizer(close, this) this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2) this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2) this.errbuf = RingBuffer{pa_shim_errmsg_t}(1, ERR_BUFSIZE) @@ -127,6 +123,7 @@ type PortAudioStream{T} end end +# this is the top-level outer constructor that all the other outer constructors end up calling """ PortAudioStream(inchannels=2, outchannels=2; options...) PortAudioStream(duplexdevice, inchannels=2, outchannels=2; options...) @@ -150,8 +147,6 @@ Options: `false`, you are free to read and write separately, but overflow or underflow can affect the round-trip latency. """ -# this is the top-level outer constructor that all the other outer constructors -# end up calling function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, synced=false) if samplerate == -1 @@ -226,7 +221,7 @@ end isopen(stream::PortAudioStream) = stream.stream != C_NULL SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate -eltype{T}(stream::PortAudioStream{T}) = T +eltype(stream::PortAudioStream{T}) where T = T read(stream::PortAudioStream, args...) = read(stream.source, args...) read!(stream::PortAudioStream, args...) = read!(stream.source, args...) @@ -253,7 +248,7 @@ Handle errors coming over the error stream from PortAudio. This is run as an independent task while the stream is active. """ function handle_errors(stream::PortAudioStream) - err = Vector{pa_shim_errmsg_t}(1) + err = Vector{pa_shim_errmsg_t}(undef, 1) while true nread = read!(stream.errbuf, err) nread == 1 || break @@ -279,7 +274,7 @@ end # Define our source and sink types for (TypeName, Super) in ((:PortAudioSink, :SampleSink), (:PortAudioSource, :SampleSource)) - @eval type $TypeName{T} <: $Super + @eval mutable struct $TypeName{T} <: $Super name::String stream::PortAudioStream{T} chunkbuf::Array{T, 2} @@ -319,7 +314,7 @@ function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffse towrite = min(framecount-nwritten, CHUNKSIZE) # make a buffer of interleaved samples transpose!(view(sink.chunkbuf, :, 1:towrite), - view(buf, (1:towrite)+nwritten+frameoffset, :)) + view(buf, (1:towrite) .+ nwritten .+ frameoffset, :)) n = write(sink.ringbuf, sink.chunkbuf, towrite) nwritten += n # break early if the stream is closed @@ -335,7 +330,7 @@ function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameo toread = min(framecount-nread, CHUNKSIZE) n = read!(source.ringbuf, source.chunkbuf, toread) # de-interleave the samples - transpose!(view(buf, (1:toread)+nread+frameoffset, :), + transpose!(view(buf, (1:toread) .+ nread .+ frameoffset, :), view(source.chunkbuf, :, 1:toread)) nread += toread @@ -346,9 +341,43 @@ function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameo nread end + +const libpa_shim = find_pa_shim() + +""" + PortAudio.shimhash() + +Return the sha256 hash(as a string) of the source file used to build the shim. +We may use this sometime to verify that the distributed binary stays in sync +with the rest of the package. +""" +shimhash() = unsafe_string(ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, ())) + + # this is called by the shim process callback to notify that there is new data. # it's run in the audio context so don't do anything besides wake up the # AsyncCondition handle associated with that ring buffer -notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Void}, ), handle) +notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Cvoid},), handle) + +global shim_processcb_c, notifycb_c + +function set_global_callbacks() + shim_dlib = Libdl.dlopen(libpa_shim) + + # pointer to the shim's process callback + global shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb) + if shim_processcb_c == C_NULL + error("Got NULL pointer loading `pa_shim_processcb`") + end + + global notifycb_c = @cfunction notifycb Cint (Ptr{Cvoid},) +end + +function __init__() + set_global_callbacks() + + # initialize PortAudio on module load + @suppress_err Pa_Initialize() +end end # module PortAudio diff --git a/src/libportaudio.jl b/src/libportaudio.jl index ffae4ea..ad6db27 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -9,8 +9,8 @@ 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{Void} -const PaStreamCallback = Void +const PaStream = Ptr{Cvoid} +const PaStreamCallback = Cvoid const PaStreamFlags = Culong const paNoFlag = PaStreamFlags(0x00) @@ -86,7 +86,7 @@ const pa_host_api_names = Dict{PaHostApiTypeId, String}( 14 => "AudioScience HPI" ) -type PaHostApiInfo +mutable struct PaHostApiInfo struct_version::Cint api_type::PaHostApiTypeId name::Ptr{Cchar} @@ -100,7 +100,7 @@ Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), # Device Functions -type PaDeviceInfo +mutable struct PaDeviceInfo struct_version::Cint name::Ptr{Cchar} host_api::PaHostApiIndex @@ -126,15 +126,15 @@ Pa_GetDefaultOutputDevice() = ccall((:Pa_GetDefaultOutputDevice, libportaudio), # Stream Functions -type Pa_StreamParameters +mutable struct Pa_StreamParameters device::PaDeviceIndex channelCount::Cint sampleFormat::PaSampleFormat suggestedLatency::PaTime - hostAPISpecificStreamInfo::Ptr{Void} + hostAPISpecificStreamInfo::Ptr{Cvoid} end -type PaStreamInfo +mutable struct PaStreamInfo structVersion::Cint inputLatency::PaTime outputLatency::PaTime @@ -148,7 +148,7 @@ end # err = ccall((:Pa_OpenDefaultStream, libportaudio), # PaError, (Ref{PaStream}, Cint, Cint, # PaSampleFormat, Cdouble, Culong, -# Ref{Void}, Ref{Void}), +# Ref{Cvoid}, Ref{Cvoid}), # streamPtr, inChannels, outChannels, sampleFormat, sampleRate, # framesPerBuffer, C_NULL, C_NULL) # handle_status(err) @@ -166,7 +166,7 @@ function Pa_OpenStream(inParams, outParams, Ptr{Pa_StreamParameters}, Ptr{Pa_StreamParameters}, Cdouble, Culong, PaStreamFlags, - Ptr{Void}, Ptr{Void}), + Ptr{Cvoid}, Ptr{Cvoid}), streamPtr, inParams, outParams, sampleRate, framesPerBuffer, flags, @@ -211,7 +211,7 @@ function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf) show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), + (PaStream, Ptr{Cvoid}, Culong), stream, buf, frames) handle_status(err, show_warnings) buf @@ -221,7 +221,7 @@ function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") err = ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Void}, Culong), + (PaStream, Ptr{Cvoid}, Culong), stream, buf, frames) handle_status(err, show_warnings) nothing diff --git a/src/pa_shim.jl b/src/pa_shim.jl index c1100be..31c5a8d 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -1,16 +1,16 @@ -function init_pa_shim() +function find_pa_shim() libdir = joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib") libsuffix = "" basename = "pa_shim" - @static if is_linux() && Sys.ARCH == :x86_64 + @static if Compat.Sys.islinux() && Sys.ARCH == :x86_64 libsuffix = "x86_64-linux-gnu" - elseif is_linux() && Sys.ARCH == :i686 + elseif Compat.Sys.islinux() && Sys.ARCH == :i686 libsuffix = "i686-linux-gnu" - elseif is_apple() && Sys.ARCH == :x86_64 + elseif Compat.Sys.isapple() && Sys.ARCH == :x86_64 libsuffix = "x86_64-apple-darwin14" - elseif is_windows() && Sys.ARCH == :x86_64 + elseif Compat.Sys.iswindows() && Sys.ARCH == :x86_64 libsuffix = "x86_64-w64-mingw32" - elseif is_windows() && Sys.ARCH == :i686 + elseif Compat.Sys.iswindows() && Sys.ARCH == :i686 libsuffix = "i686-w64-mingw32" elseif !any( (sfx) -> isfile(joinpath(libdir, "$basename.$sfx")), @@ -19,16 +19,11 @@ function init_pa_shim() end # if there's a suffix-less library, it was built natively on this machine, # so load that one first, otherwise load the pre-built one - global const libpa_shim = Base.Libdl.find_library( + libpa_shim = Libdl.find_library( [basename, "$(basename)_$libsuffix"], [libdir]) libpa_shim == "" && error("Could not load $basename library, please file an issue at https://github.com/JuliaAudio/RingBuffers.jl/issues with your `versioninfo()` output") - shim_dlib = Libdl.dlopen(libpa_shim) - # pointer to the shim's process callback - global const shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb) - if shim_processcb_c == C_NULL - error("Got NULL pointer loading `pa_shim_processcb`") - end + return libpa_shim end const pa_shim_errmsg_t = Cint @@ -43,19 +38,10 @@ mutable struct pa_shim_info_t outputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for output errorbuf::Ptr{PaUtilRingBuffer} # ringbuffer to send error notifications sync::Cint # keep input/output ring buffers synchronized (0/1) - notifycb::Ptr{Void} # Julia callback to notify on updates (called from audio thread) - inputhandle::Ptr{Void} # condition to notify on new input data - outputhandle::Ptr{Void} # condition to notify when ready for output - errorhandle::Ptr{Void} # condition to notify on new errors + notifycb::Ptr{Cvoid} # Julia callback to notify on updates (called from audio thread) + inputhandle::Ptr{Cvoid} # condition to notify on new input data + outputhandle::Ptr{Cvoid} # condition to notify when ready for output + errorhandle::Ptr{Cvoid} # condition to notify on new errors end -""" - PortAudio.shimhash() - -Return the sha256 hash(as a string) of the source file used to build the shim. -We may use this sometime to verify that the distributed binary stays in sync -with the rest of the package. -""" -shimhash() = unsafe_string( - ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, ())) -Base.unsafe_convert(::Type{Ptr{Void}}, info::pa_shim_info_t) = pointer_from_objref(info) +Base.unsafe_convert(::Type{Ptr{Cvoid}}, info::pa_shim_info_t) = pointer_from_objref(info) diff --git a/src/suppressor.jl b/src/suppressor.jl index ab297bc..abf532c 100644 --- a/src/suppressor.jl +++ b/src/suppressor.jl @@ -4,9 +4,9 @@ macro suppress_err(block) quote if ccall(:jl_generating_output, Cint, ()) == 0 - ORIGINAL_STDERR = STDERR + ORIGINAL_STDERR = stderr err_rd, err_wr = redirect_stderr() - err_reader = @async readstring(err_rd) + err_reader = @async read(err_rd, String) end value = $(esc(block)) diff --git a/test/runtests.jl b/test/runtests.jl index bd77615..e533ab0 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,8 @@ #!/usr/bin/env julia -using Base.Test +using Compat +using Compat.Test +import Compat: Cvoid using TestSetExtensions using PortAudio using SampledSignals @@ -34,7 +36,7 @@ function setup_callback(inchans, outchans, nframes, synced) function processfunc() ccall(shim_processcb_c, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Void}, Culong, Ptr{Void}), + (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Cvoid}, Culong, Ptr{Cvoid}), cb_input, cb_output, nframes, C_NULL, flags, pointer_from_objref(info)) end diff --git a/test/runtests_local.jl b/test/runtests_local.jl index 76356d9..9725990 100644 --- a/test/runtests_local.jl +++ b/test/runtests_local.jl @@ -5,13 +5,13 @@ include("runtests.jl") # these default values are specific to my machines -if is_windows() +if Compat.Sys.iswindows() default_indev = "Microphone Array (Realtek High " default_outdev = "Speaker/Headphone (Realtek High" -elseif is_apple() - default_indev = "Built-in Microph" +elseif Compat.Sys.isapple() + default_indev = "Built-in Microphone" default_outdev = "Built-in Output" -elseif is_linux() +elseif Compat.Sys.islinux() default_indev = "default" default_outdev = "default" end @@ -50,12 +50,12 @@ end write(stream, buf) io = IOBuffer() show(io, stream) - @test String(take!(io)) == """ - PortAudio.PortAudioStream{Float32} + @test Compat.occursin(""" + PortAudioStream{Float32} Samplerate: 44100.0Hz Buffer Size: 4096 frames 2 channel sink: "$default_outdev" - 2 channel source: "$default_indev\"""" + 2 channel source: "$default_indev\"""", String(take!(io))) close(stream) end @testset "Error on wrong name" begin @@ -68,8 +68,8 @@ end buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream)) t1 = @async write(stream, buf) t2 = @async write(stream, buf) - @test wait(t1) == 48000 - @test wait(t2) == 48000 + @test fetch(t1) == 48000 + @test fetch(t2) == 48000 flush(stream) close(stream) end @@ -78,8 +78,8 @@ end buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, samplerate(stream)) t1 = @async read!(stream, buf) t2 = @async read!(stream, buf) - @test wait(t1) == 48000 - @test wait(t2) == 48000 + @test fetch(t1) == 48000 + @test fetch(t2) == 48000 close(stream) end end From f6213dc5efef1d383c16e1228cabea705f4bff89 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 15 Aug 2018 23:18:44 -0400 Subject: [PATCH 02/31] some more upgrades, changes a bunch of Ptrs to Refs --- src/PortAudio.jl | 29 +++++++++++++++++++---------- src/libportaudio.jl | 19 +++++++------------ src/pa_shim.jl | 4 +--- src/suppressor.jl | 20 -------------------- test/REQUIRE | 1 - test/runtests.jl | 12 +++++++----- test/runtests_local.jl | 2 +- 7 files changed, 35 insertions(+), 52 deletions(-) delete mode 100644 src/suppressor.jl diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 9fd9510..29083cd 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -5,8 +5,9 @@ module PortAudio using SampledSignals using RingBuffers using Compat -import Compat: undef, fetch, @compat -import Compat.LinearAlgebra: transpose! +using Compat: undef, fetch, @compat +using Compat.LinearAlgebra: transpose! +using Compat: stdout import Base: eltype, show import Base: close, isopen @@ -17,7 +18,6 @@ export PortAudioStream # Get binary dependencies loaded from BinDeps include("../deps/deps.jl") -include("suppressor.jl") include("pa_shim.jl") include("libportaudio.jl") @@ -36,7 +36,7 @@ const CHUNKSIZE=128 # ringbuffer to receive errors from the audio processing thread const ERR_BUFSIZE=512 -function versioninfo(io::IO=STDOUT) +function versioninfo(io::IO=stdout) println(io, Pa_GetVersionText()) println(io, "Version: ", Pa_GetVersion()) println(io, "Shim Source Hash: ", shimhash()[1:10]) @@ -111,10 +111,10 @@ mutable struct PortAudioStream{T} inchans > 0 ? notifyhandle(this.source) : C_NULL, outchans > 0 ? notifyhandle(this.sink) : C_NULL, notifyhandle(this.errbuf)) - this.stream = @suppress_err Pa_OpenStream(inparams, outparams, - float(sr), blocksize, - paNoFlag, shim_processcb_c, - this.bufinfo) + this.stream = suppress_err() do + Pa_OpenStream(inparams, outparams, float(sr), blocksize, paNoFlag, + shim_processcb_c, this.bufinfo) + end Pa_StartStream(this.stream) @async handle_errors(this) @@ -357,7 +357,7 @@ shimhash() = unsafe_string(ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, # this is called by the shim process callback to notify that there is new data. # it's run in the audio context so don't do anything besides wake up the # AsyncCondition handle associated with that ring buffer -notifycb(handle) = ccall(:uv_async_send, Cint, (Ptr{Cvoid},), handle) +notifycb(handle) = ccall(:uv_async_send, Cint, (Ref{Cvoid},), handle) global shim_processcb_c, notifycb_c @@ -373,11 +373,20 @@ function set_global_callbacks() global notifycb_c = @cfunction notifycb Cint (Ptr{Cvoid},) end +function suppress_err(dofunc::Function) + nullfile = @static Sys.iswindows() ? "nul" : "/dev/null" + open(nullfile, "w") do io + redirect_stdout(dofunc, io) + end +end + function __init__() set_global_callbacks() # initialize PortAudio on module load - @suppress_err Pa_Initialize() + suppress_err() do + Pa_Initialize() + end end end # module PortAudio diff --git a/src/libportaudio.jl b/src/libportaudio.jl index ad6db27..b4b97ff 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -159,18 +159,13 @@ end function Pa_OpenStream(inParams, outParams, sampleRate, framesPerBuffer, flags::PaStreamFlags, - callback, userdata) + callback, userdata::T) where T streamPtr = Ref{PaStream}(0) err = ccall((:Pa_OpenStream, libportaudio), PaError, - (Ref{PaStream}, - Ptr{Pa_StreamParameters}, - Ptr{Pa_StreamParameters}, - Cdouble, Culong, PaStreamFlags, - Ptr{Cvoid}, Ptr{Cvoid}), - streamPtr, - inParams, outParams, - sampleRate, framesPerBuffer, flags, - callback, userdata) + (Ref{PaStream}, Ref{Pa_StreamParameters}, Ref{Pa_StreamParameters}, + Cdouble, Culong, PaStreamFlags, Ref{Cvoid}, Ref{T}), + streamPtr, inParams, outParams, + sampleRate, framesPerBuffer, flags, callback, userdata) handle_status(err) streamPtr[] end @@ -211,7 +206,7 @@ function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf) show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Cvoid}, Culong), + (PaStream, Ref{Cvoid}, Culong), stream, buf, frames) handle_status(err, show_warnings) buf @@ -221,7 +216,7 @@ function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") err = ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Cvoid}, Culong), + (PaStream, Ref{Cvoid}, Culong), stream, buf, frames) handle_status(err, show_warnings) nothing diff --git a/src/pa_shim.jl b/src/pa_shim.jl index 31c5a8d..6ac7e2f 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -1,5 +1,5 @@ function find_pa_shim() - libdir = joinpath(dirname(@__FILE__), "..", "deps", "usr", "lib") + libdir = joinpath(@__DIR__, "..", "deps", "usr", "lib") libsuffix = "" basename = "pa_shim" @static if Compat.Sys.islinux() && Sys.ARCH == :x86_64 @@ -43,5 +43,3 @@ mutable struct pa_shim_info_t outputhandle::Ptr{Cvoid} # condition to notify when ready for output errorhandle::Ptr{Cvoid} # condition to notify on new errors end - -Base.unsafe_convert(::Type{Ptr{Cvoid}}, info::pa_shim_info_t) = pointer_from_objref(info) diff --git a/src/suppressor.jl b/src/suppressor.jl deleted file mode 100644 index abf532c..0000000 --- a/src/suppressor.jl +++ /dev/null @@ -1,20 +0,0 @@ -# while waiting for this PR to get merged: https://github.com/Ismael-VC/Suppressor.jl/pull/12 -# we'll just include the relevant code here - -macro suppress_err(block) - quote - if ccall(:jl_generating_output, Cint, ()) == 0 - ORIGINAL_STDERR = stderr - err_rd, err_wr = redirect_stderr() - err_reader = @async read(err_rd, String) - end - - value = $(esc(block)) - - if ccall(:jl_generating_output, Cint, ()) == 0 - redirect_stderr(ORIGINAL_STDERR) - close(err_wr) - end - value - end -end diff --git a/test/REQUIRE b/test/REQUIRE index 557b023..e69de29 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -1 +0,0 @@ -TestSetExtensions diff --git a/test/runtests.jl b/test/runtests.jl index e533ab0..750cc70 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,6 @@ using Compat using Compat.Test import Compat: Cvoid -using TestSetExtensions using PortAudio using SampledSignals using RingBuffers @@ -36,8 +35,8 @@ function setup_callback(inchans, outchans, nframes, synced) function processfunc() ccall(shim_processcb_c, Cint, - (Ptr{Float32}, Ptr{Float32}, Culong, Ptr{Cvoid}, Culong, Ptr{Cvoid}), - cb_input, cb_output, nframes, C_NULL, flags, pointer_from_objref(info)) + (Ref{Float32}, Ref{Float32}, Culong, Ref{Cvoid}, Culong, Ref{pa_shim_info_t}), + cb_input, cb_output, nframes, C_NULL, flags, info) end (sourcebuf, sinkbuf, errbuf, cb_input, cb_output, processfunc) @@ -52,7 +51,10 @@ function test_callback(inchans, outchans, synced) testout = rand(Float32, outchans, nframes) # generate some test data to play write(sinkbuf, testout) # fill the output ringbuffer end - @test process() == PortAudio.paContinue + # the process closure only has a pointer (not a ref) to sinkbuf + GC.@preserve sinkbuf begin + @test process() == PortAudio.paContinue + end if outchans > 0 # testout -> sinkbuf -> cb_output @test cb_output == testout @@ -179,7 +181,7 @@ function test_callback_overflow(inchans, outchans, synced) end end -@testset ExtendedTestSet "PortAudio Tests" begin +@testset "PortAudio Tests" begin @testset "Reports version" begin io = IOBuffer() PortAudio.versioninfo(io) diff --git a/test/runtests_local.jl b/test/runtests_local.jl index 9725990..f39bcf9 100644 --- a/test/runtests_local.jl +++ b/test/runtests_local.jl @@ -16,7 +16,7 @@ elseif Compat.Sys.islinux() default_outdev = "default" end -@testset ExtendedTestSet "Local Tests" begin +@testset "Local Tests" begin @testset "Open Default Device" begin println("Recording...") stream = PortAudioStream(2, 0) From b1e0183538088a19d80dc25e947b7537368445b2 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 12:41:35 -0400 Subject: [PATCH 03/31] now testing on 0.6, 0.7, and 1.0 --- .travis.yml | 19 +++++++++++++++++-- appveyor.yml | 25 +++++++++++++++++++------ ci_setup.jl | 16 ++++++++++++++++ src/PortAudio.jl | 3 ++- test/runtests.jl | 6 +++++- 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 ci_setup.jl diff --git a/.travis.yml b/.travis.yml index 8534eb7..aee8bdc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,27 @@ language: julia os: - linux - osx -sudo: required julia: - 0.6 + - 0.7 + - 1.0 - nightly +matrix: + allow_failures: + - julia: nightly + fast_finish: true +branches: + only: + master notifications: email: false script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("PortAudio"); Pkg.test("PortAudio")' + - julia --color=yes ci_setup.jl + - julia --color=yes --code-coverage test/runtests.jl +after_success: + - julia -e 'VERSION >= v"0.7.0-" && using Pkg; + VERSION < v"0.7.0-" && cd(Pkg.dir("PortAudio")); + Pkg.add("Coverage"); + using Coverage; + Codecov.submit(process_folder())' diff --git a/appveyor.yml b/appveyor.yml index 0570501..36a5f2b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,16 @@ environment: matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" - - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.7/julia-0.7-latest-win32.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.7/julia-0.7-latest-win64.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/1.0/julia-1.0-latest-win32.exe" + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0-latest-win64.exe" + +matrix: + allow_failures: + # currently failing on 1.0 until https://github.com/JuliaLang/METADATA.jl/pull/16370 is merged + - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0-latest-win64.exe" notifications: - provider: Email @@ -10,6 +18,11 @@ notifications: on_build_failure: false on_build_status_changed: false +# only build master and PRs +branches: + only: + - master + install: - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" # Download most recent Julia Windows binary @@ -22,8 +35,8 @@ install: build_script: # Need to convert from shallow to complete for Pkg.clone to work - IF EXIST .git\shallow (git fetch --unshallow) - - C:\projects\julia\bin\julia -e "versioninfo(); - Pkg.clone(pwd(), \"PortAudio\"); Pkg.build(\"PortAudio\")" + - C:\projects\julia\bin\julia --color=yes ci_setup.jl test_script: - - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"PortAudio\")" + # - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"SampledSignals\")" + - C:\projects\julia\bin\julia --color=yes --code-coverage --check-bounds=yes test/runtests.jl diff --git a/ci_setup.jl b/ci_setup.jl new file mode 100644 index 0000000..d8ffb39 --- /dev/null +++ b/ci_setup.jl @@ -0,0 +1,16 @@ +VERSION >= v"0.7.0-" && using InteractiveUtils +versioninfo() + +if VERSION < v"0.7.0-" + Pkg.clone(pwd(), "PortAudio") + Pkg.build("PortAudio") + # for now we need SampledSignals master + Pkg.checkout("SampledSignals") +else + using Pkg + # for now we need to `clone` because there's no way to specify the + # package name for `add` + Pkg.clone(pwd(), "PortAudio") + Pkg.build("PortAudio") + Pkg.add(PackageSpec(name="SampledSignals", rev="master")) +end diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 29083cd..6560482 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -8,6 +8,7 @@ using Compat using Compat: undef, fetch, @compat using Compat.LinearAlgebra: transpose! using Compat: stdout +using Compat.Sys: iswindows import Base: eltype, show import Base: close, isopen @@ -374,7 +375,7 @@ function set_global_callbacks() end function suppress_err(dofunc::Function) - nullfile = @static Sys.iswindows() ? "nul" : "/dev/null" + nullfile = @static iswindows() ? "nul" : "/dev/null" open(nullfile, "w") do io redirect_stdout(dofunc, io) end diff --git a/test/runtests.jl b/test/runtests.jl index 750cc70..b7ad07c 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -52,7 +52,11 @@ function test_callback(inchans, outchans, synced) write(sinkbuf, testout) # fill the output ringbuffer end # the process closure only has a pointer (not a ref) to sinkbuf - GC.@preserve sinkbuf begin + @static if VERSION >= v"0.7.0-" + GC.@preserve sinkbuf begin + @test process() == PortAudio.paContinue + end + else @test process() == PortAudio.paContinue end if outchans > 0 From d14a5f4b1fa966c7cc705df45ab45b328fe07880 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 12:47:11 -0400 Subject: [PATCH 04/31] adds back sudo for travis --- .travis.yml | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index aee8bdc..2d9cad4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: julia os: - linux - osx +sudo: required julia: - 0.6 - 0.7 diff --git a/README.md b/README.md index 42d3790..0c276ee 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ PortAudio.jl [![Build Status](https://travis-ci.org/JuliaAudio/PortAudio.jl.svg?branch=master)](https://travis-ci.org/JuliaAudio/PortAudio.jl) [![Build status](https://ci.appveyor.com/api/projects/status/6x1ha7uvrnel060g/branch/master?svg=true)](https://ci.appveyor.com/project/ssfrr/portaudio-jl/branch/master) +**NOTE: PortAudio.jl master currently requires both SampledSignals and RingBuffers to be on master as well** PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which gives cross-platform access to audio devices. It is compatible with the types defined in [SampledSignals.jl](https://github.com/JuliaAudio/SampledSignals.jl). It provides a `PortAudioStream` type, which can be read from and written to. From 06d3a4b0999d96454d707b70a368048b0a52a406 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 13:03:22 -0400 Subject: [PATCH 05/31] now also using master of RingBuffers --- ci_setup.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci_setup.jl b/ci_setup.jl index d8ffb39..79286bb 100644 --- a/ci_setup.jl +++ b/ci_setup.jl @@ -4,8 +4,9 @@ versioninfo() if VERSION < v"0.7.0-" Pkg.clone(pwd(), "PortAudio") Pkg.build("PortAudio") - # for now we need SampledSignals master + # for now we need SampledSignals and RingBuffers master Pkg.checkout("SampledSignals") + Pkg.checkout("RingBuffers") else using Pkg # for now we need to `clone` because there's no way to specify the @@ -13,4 +14,5 @@ else Pkg.clone(pwd(), "PortAudio") Pkg.build("PortAudio") Pkg.add(PackageSpec(name="SampledSignals", rev="master")) + Pkg.add(PackageSpec(name="RingBuffers", rev="master")) end From 4d73324a7f93abaa2a0d82d7386595d149dccb89 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 13:56:32 -0400 Subject: [PATCH 06/31] now only testing appveyor on 1.0, to save CI time --- appveyor.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 36a5f2b..f7717b9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,5 @@ environment: matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.7/julia-0.7-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.7/julia-0.7-latest-win64.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/1.0/julia-1.0-latest-win32.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0-latest-win64.exe" From 3551896de1298f859948c2f0a56e8641bb8421e9 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 14:11:17 -0400 Subject: [PATCH 07/31] install Compat during testing --- .travis.yml | 1 - ci_setup.jl | 3 +++ test/REQUIRE | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d9cad4..b5607f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ os: sudo: required julia: - 0.6 - - 0.7 - 1.0 - nightly matrix: diff --git a/ci_setup.jl b/ci_setup.jl index 79286bb..82a0a65 100644 --- a/ci_setup.jl +++ b/ci_setup.jl @@ -16,3 +16,6 @@ else Pkg.add(PackageSpec(name="SampledSignals", rev="master")) Pkg.add(PackageSpec(name="RingBuffers", rev="master")) end + +# add test deps manually because we'll be running test/runtests.jl manually +Pkg.add("Compat") diff --git a/test/REQUIRE b/test/REQUIRE index e69de29..9e686b5 100644 --- a/test/REQUIRE +++ b/test/REQUIRE @@ -0,0 +1,3 @@ +Compat +SampledSignals +RingBuffers From 308e88b7cfb837ca07a10209db986959acd1d04e Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 14:19:00 -0400 Subject: [PATCH 08/31] rounds up some stray underscores --- deps/build.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deps/build.jl b/deps/build.jl index e672e0b..956f21c 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,5 +1,6 @@ using BinDeps using Compat +using Compat.Sys: isapple, iswindows @BinDeps.setup @@ -13,12 +14,12 @@ provides(AptGet, "libportaudio2", libportaudio) provides(Pacman, "portaudio", libportaudio) -@static if is_apple() +@static if isapple() using Homebrew provides(Homebrew.HB, "portaudio", libportaudio) end -@static if is_windows() +@static if iswindows() using WinRPM provides(WinRPM.RPM, "libportaudio2", libportaudio, os = :Windows) end From bc32d13f7d8dc3269675d37bc03d6d6bfbe3f3f5 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 14:41:50 -0400 Subject: [PATCH 09/31] Ref usage wasn't working on 0.6 --- src/libportaudio.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libportaudio.jl b/src/libportaudio.jl index b4b97ff..3da627d 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -159,13 +159,19 @@ end function Pa_OpenStream(inParams, outParams, sampleRate, framesPerBuffer, flags::PaStreamFlags, - callback, userdata::T) where T + callback, userdata) streamPtr = Ref{PaStream}(0) err = ccall((:Pa_OpenStream, libportaudio), PaError, (Ref{PaStream}, Ref{Pa_StreamParameters}, Ref{Pa_StreamParameters}, - Cdouble, Culong, PaStreamFlags, Ref{Cvoid}, Ref{T}), + 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, - sampleRate, framesPerBuffer, flags, callback, userdata) + sampleRate, framesPerBuffer, flags, callback, + pointer_from_objref(userdata)) handle_status(err) streamPtr[] end From 45bfdc4830276b8453b89c544a4d25bcc4244c49 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 16 Aug 2018 15:59:00 -0400 Subject: [PATCH 10/31] now properly closing the error ringbuf --- src/PortAudio.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 6560482..320f8a2 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -213,6 +213,7 @@ function close(stream::PortAudioStream) Pa_CloseStream(stream.stream) close(stream.source) close(stream.sink) + close(stream.errbuf) stream.stream = C_NULL end From 7d1be74eaeb5ada4b1bf2b9bca9e90485b2f3481 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 28 Aug 2018 13:41:04 -0400 Subject: [PATCH 11/31] adds workaround for libuv/libuv#1951. PA_SHIM REQUIRES LOCAL BUILD --- deps/src/pa_shim.c | 25 ++++++++++++++--------- src/PortAudio.jl | 51 ++++++++++++++++++++++++++++++++++++++++++++-- src/pa_shim.jl | 1 + test/runtests.jl | 51 +++++++++++++++++++++++++++------------------- 4 files changed, 95 insertions(+), 33 deletions(-) diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c index c6d67cc..c6e6f45 100644 --- a/deps/src/pa_shim.c +++ b/deps/src/pa_shim.c @@ -26,17 +26,20 @@ typedef struct { void *inputhandle; // condition to notify on new input void *outputhandle; // condition to notify when ready for output void *errorhandle; // condition to notify on new error + void *globalhandle; // only needed for libuv workaround } pa_shim_info_t; void senderr(pa_shim_info_t *info, pa_shim_errmsg_t msg) { - if(PaUtil_GetRingBufferWriteAvailable(info->errorbuf) < 2) { + if(PaUtil_GetRingBufferWriteAvailable(info->errorbuf) == 1) { // we've overflowed our error buffer! notify the host. msg = PA_SHIM_ERRMSG_ERR_OVERFLOW; } PaUtil_WriteRingBuffer(info->errorbuf, &msg, 1); - if(info->notifycb) { - info->notifycb(info->errorhandle); - } + // for now we're relying on the global handle. uncomment the following + // when the libuv workaround is no longer necessary + // if(info->notifycb) { + // info->notifycb(info->errorhandle); + // } } // return the sha256 hash of the shim source so we can make sure things are in sync @@ -60,6 +63,7 @@ int pa_shim_processcb(const void *input, void *output, pa_shim_info_t *info = userData; if(info->notifycb == NULL) { fprintf(stderr, "pa_shim ERROR: notifycb is NULL\n"); + return paAbort; } int nwrite; if(info->inputbuf) { @@ -80,18 +84,18 @@ int pa_shim_processcb(const void *input, void *output, // read/write from the ringbuffers if(info->inputbuf) { PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite); - if(info->notifycb) { - info->notifycb(info->inputhandle); - } + // for now we're relying on the global handle. uncomment the following + // when the libuv workaround is no longer necessary + // info->notifycb(info->inputhandle); if(nwrite < frameCount) { senderr(info, PA_SHIM_ERRMSG_OVERFLOW); } } if(info->outputbuf) { PaUtil_ReadRingBuffer(info->outputbuf, output, nread); - if(info->notifycb) { - info->notifycb(info->outputhandle); - } + // for now we're relying on the global handle. uncomment the following + // when the libuv workaround is no longer necessary + // info->notifycb(info->outputhandle); if(nread < frameCount) { senderr(info, PA_SHIM_ERRMSG_UNDERFLOW); // we didn't fill the whole output buffer, so zero it out @@ -100,5 +104,6 @@ int pa_shim_processcb(const void *input, void *output, } } + info->notifycb(info->globalhandle); return paContinue; } diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 320f8a2..a3c5665 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -80,6 +80,7 @@ mutable struct PortAudioStream{T} sink # untyped because of circular type definition source # untyped because of circular type definition errbuf::RingBuffer{pa_shim_errmsg_t} # used to send errors from the portaudio callback + errtask::Task bufinfo::pa_shim_info_t # data used in the portaudio callback # this inner constructor is generally called via the top-level outer @@ -111,14 +112,16 @@ mutable struct PortAudioStream{T} synced, notifycb_c, inchans > 0 ? notifyhandle(this.source) : C_NULL, outchans > 0 ? notifyhandle(this.sink) : C_NULL, - notifyhandle(this.errbuf)) + notifyhandle(this.errbuf), + global_cond[].handle) # this is only needed for the libuv workaround this.stream = suppress_err() do Pa_OpenStream(inparams, outparams, float(sr), blocksize, paNoFlag, shim_processcb_c, this.bufinfo) end Pa_StartStream(this.stream) - @async handle_errors(this) + this.errtask = @async handle_errors(this) + push!(active_streams, this) this end @@ -207,6 +210,28 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...) PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...) end +const pa_inited = Ref(false) +const active_streams = Set{PortAudioStream}() + +function notify_active_streams() + # errors here can cause the system to hang if they're waiting on these + # conditions, so we do our own exception display for easier debugging + try + while true + wait(global_cond[]) + pa_inited[] || break + + for stream in active_streams + notify(stream.source.ringbuf.datanotify.cond) + notify(stream.sink.ringbuf.datanotify.cond) + notify(stream.errbuf.datanotify.cond) + end + end + catch ex + showerror(stderr, ex, backtrace()) + end +end + function close(stream::PortAudioStream) if stream.stream != C_NULL Pa_StopStream(stream.stream) @@ -215,6 +240,9 @@ function close(stream::PortAudioStream) close(stream.sink) close(stream.errbuf) stream.stream = C_NULL + # wait for the error task to clean up + fetch(stream.errtask) + delete!(active_streams, stream) end nothing @@ -382,13 +410,32 @@ function suppress_err(dofunc::Function) end end +# this ref has to be set during __init__ to register itself properly with libuv +const global_cond = Ref{Base.AsyncCondition}() function __init__() + # currently libuv has issues when you try to notify more than one condition + # (see https://github.com/libuv/libuv/issues/1951). So as a workaround we use + # a global AsyncCondition that gets notified from the audio callback, and it + # handles notifying the individual RingBuffer AsyncConditions. + global_cond[] = Base.AsyncCondition() set_global_callbacks() # initialize PortAudio on module load suppress_err() do Pa_Initialize() end + pa_inited[] = true + notifier = @async notify_active_streams() + + atexit() do + for str in active_streams + close(str) + end + Pa_Terminate() + pa_inited[] = false + notify(global_cond[].cond) + fetch(notifier) + end end end # module PortAudio diff --git a/src/pa_shim.jl b/src/pa_shim.jl index 6ac7e2f..e9919b1 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -42,4 +42,5 @@ mutable struct pa_shim_info_t inputhandle::Ptr{Cvoid} # condition to notify on new input data outputhandle::Ptr{Cvoid} # condition to notify when ready for output errorhandle::Ptr{Cvoid} # condition to notify on new errors + globalhandle::Ptr{Cvoid} # only needed for libuv workaround end diff --git a/test/runtests.jl b/test/runtests.jl index b7ad07c..b48172e 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,6 +12,9 @@ using PortAudio: notifyhandle, notifycb_c, shim_processcb_c using PortAudio: pa_shim_errmsg_t, pa_shim_info_t using PortAudio: PA_SHIM_ERRMSG_ERR_OVERFLOW, PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW +# only needed for the libuv workaround +const globalcond = Base.AsyncCondition() + "Setup buffers to test callback behavior" function setup_callback(inchans, outchans, nframes, synced) sourcebuf = RingBuffer{Float32}(inchans, nframes*2) # the microphone input should end up here @@ -26,7 +29,8 @@ function setup_callback(inchans, outchans, nframes, synced) synced, notifycb_c, inchans > 0 ? notifyhandle(sourcebuf) : C_NULL, outchans > 0 ? notifyhandle(sinkbuf) : C_NULL, - notifyhandle(errbuf) + notifyhandle(errbuf), + globalcond.handle ) flags = Culong(0) @@ -42,23 +46,28 @@ function setup_callback(inchans, outchans, nframes, synced) (sourcebuf, sinkbuf, errbuf, cb_input, cb_output, processfunc) end +# the process callback only has pointers (not refs) to some data, so we need +# to make sure the references are preserved +function wrapprocess(process, sourcebuf, sinkbuf) + @static if VERSION >= v"0.7.0-" + GC.@preserve sourcebuf sinkbuf begin + process() + end + else + process() + end +end + function test_callback(inchans, outchans, synced) nframes = 8 (sourcebuf, sinkbuf, errbuf, - cb_input, cb_output, process) = setup_callback(inchans, outchans, - nframes, synced) + cb_input, cb_output, + process) = setup_callback(inchans, outchans, nframes, synced) if outchans > 0 testout = rand(Float32, outchans, nframes) # generate some test data to play write(sinkbuf, testout) # fill the output ringbuffer end - # the process closure only has a pointer (not a ref) to sinkbuf - @static if VERSION >= v"0.7.0-" - GC.@preserve sinkbuf begin - @test process() == PortAudio.paContinue - end - else - @test process() == PortAudio.paContinue - end + @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue if outchans > 0 # testout -> sinkbuf -> cb_output @test cb_output == testout @@ -80,13 +89,13 @@ function test_callback_underflow(inchans, outchans, synced) nframes = 8 underfill = 3 # must be less than nframes (sourcebuf, sinkbuf, errbuf, - cb_input, cb_output, process) = setup_callback(inchans, outchans, - nframes, synced) + cb_input, cb_output, + process) = setup_callback(inchans, outchans, nframes, synced) outchans > 0 || error("Can't test underflow with no output") testout = rand(Float32, outchans, underfill) write(sinkbuf, testout) # underfill the output ringbuffer # call callback (partial underflow) - @test process() == PortAudio.paContinue + @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue @test cb_output[:, 1:underfill] == testout @test cb_output[:, (underfill+1):nframes] == zeros(Float32, outchans, (nframes-underfill)) errs = readavailable(errbuf) @@ -109,7 +118,7 @@ function test_callback_underflow(inchans, outchans, synced) end # call again (total underflow) - @test process() == PortAudio.paContinue + @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue @test cb_output == zeros(Float32, outchans, nframes) errs = readavailable(errbuf) if inchans > 0 @@ -133,8 +142,8 @@ end function test_callback_overflow(inchans, outchans, synced) nframes = 8 (sourcebuf, sinkbuf, errbuf, - cb_input, cb_output, process) = setup_callback(inchans, outchans, - nframes, synced) + cb_input, cb_output, + process) = setup_callback(inchans, outchans, nframes, synced) inchans > 0 || error("Can't test overflow with no input") @test frameswritable(sinkbuf) == nframes*2 @@ -145,7 +154,7 @@ function test_callback_overflow(inchans, outchans, synced) end @test framesreadable(sourcebuf) == 0 outchans > 0 && @test frameswritable(sinkbuf) == nframes - @test process() == PortAudio.paContinue + @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue @test framesreadable(errbuf) == 0 @test framesreadable(sourcebuf) == nframes outchans > 0 && @test frameswritable(sinkbuf) == nframes*2 @@ -154,7 +163,7 @@ function test_callback_overflow(inchans, outchans, synced) outchans > 0 && write(sinkbuf, testout) @test framesreadable(sourcebuf) == nframes outchans > 0 && @test frameswritable(sinkbuf) == nframes - @test process() == PortAudio.paContinue + @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue @test framesreadable(errbuf) == 0 @test framesreadable(sourcebuf) == nframes*2 outchans > 0 && @test frameswritable(sinkbuf) == nframes*2 @@ -163,7 +172,7 @@ function test_callback_overflow(inchans, outchans, synced) outchans > 0 && write(sinkbuf, testout) @test framesreadable(sourcebuf) == nframes*2 outchans > 0 && @test frameswritable(sinkbuf) == nframes - @test process() == PortAudio.paContinue + @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue @test framesreadable(sourcebuf) == nframes*2 errs = readavailable(errbuf) if outchans > 0 @@ -195,7 +204,7 @@ end end @testset "using correct shim version" begin - @test PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" + @test_broken PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" end @testset "Basic callback functionality" begin From 8d42b94a6a8d02d2083a67a80f063bb15178092c Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 5 Dec 2018 11:17:36 -0500 Subject: [PATCH 12/31] adds do syntax support --- README.md | 21 ++++++++++++++++++--- src/PortAudio.jl | 10 ++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0c276ee..467beef 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,26 @@ PortAudio.jl also provides convenience wrappers around the `PortAudioStream` typ ```julia stream = PortAudioStream(2, 2) -write(stream, stream) +try + # cancel with Ctrl-C + write(stream, stream) +finally + close(stream) +end +``` + +### Use `do` syntax to auto-close the stream +```julia +PortAudioStream(2, 2) do stream + write(stream, stream) +end ``` ### Open your built-in microphone and speaker by name ```julia -stream = PortAudioStream("Built-in Microph", "Built-in Output") -write(stream, stream) +PortAudioStream("Built-in Microph", "Built-in Output") do stream + write(stream, stream) +end ``` ### Record 10 seconds of audio and save to an ogg file @@ -79,6 +92,8 @@ julia> buf = read(stream, 10s) ▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁ ▁▄▂▃▅▃▂▄▃▂▂▁▁▂▂▁▁▄▃▁▁▄▂▁▁▁▄▃▁▁▃▃▁▁▁▁▁▁▁▁▄▄▄▄▄▂▂▂▁▃▃▁▃▄▂▁▁▁▁▃▃▂▁▁▁▁▁▁▃▃▂▂▁▃▃▃▁▁▁▁ +julia> close(stream) + julia> save(joinpath(homedir(), "Desktop", "myvoice.ogg"), buf) ``` diff --git a/src/PortAudio.jl b/src/PortAudio.jl index a3c5665..be769b0 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -210,6 +210,16 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...) PortAudioStream(indevice, outdevice, inchans, outchans; kwargs...) end +# handle do-syntax +function PortAudioStream(fn::Function, args...; kwargs...) + str = PortAudioStream(args...; kwargs...) + try + fn(str) + finally + close(str) + end +end + const pa_inited = Ref(false) const active_streams = Set{PortAudioStream}() From c66ad398bded437ea9acff5086529b58a62b3f8e Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 5 Dec 2018 11:22:28 -0500 Subject: [PATCH 13/31] adds warning TODO for do-syntax --- src/PortAudio.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index be769b0..1365a10 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -211,6 +211,9 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...) end # handle do-syntax +# TODO: there seems to be a buffering issue here. Running this multiple +# times creates weird echos, and even the first time there might be something +# fishy going on. Needs investigation function PortAudioStream(fn::Function, args...; kwargs...) str = PortAudioStream(args...; kwargs...) try From 8bd884d394a4686430a477615d6a9fe0a7ea9f28 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 27 Feb 2019 09:01:09 -0500 Subject: [PATCH 14/31] fixes field acess error when printing other errors --- src/PortAudio.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 1365a10..551fca4 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -296,7 +296,7 @@ function handle_errors(stream::PortAudioStream) nread = read!(stream.errbuf, err) nread == 1 || break if err[1] == PA_SHIM_ERRMSG_ERR_OVERFLOW - warn("Error buffer overflowed on stream $(stream.name)") + warn("Error buffer overflowed on portaudio stream") elseif err[1] == PA_SHIM_ERRMSG_OVERFLOW # warn("Input overflowed from $(name(stream.source))") elseif err[1] == PA_SHIM_ERRMSG_UNDERFLOW @@ -304,7 +304,7 @@ function handle_errors(stream::PortAudioStream) else error(""" Got unrecognized error code $(err[1]) from audio thread for - stream "$(stream.name)". Please file an issue at + portaudio stream. Please file an issue at https://github.com/juliaaudio/portaudio.jl/issues""") end end From 207ec25f1ebdfbe2094b190b447380547d670feb Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 27 Feb 2019 09:03:48 -0500 Subject: [PATCH 15/31] adds some examples of scrolling spctrograms with Makie. Probably with an old version of Makie --- examples/waterfall_heatmap.jl | 67 +++++++++++++++++++++++++++++++++++ examples/waterfall_lines.jl | 38 ++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 examples/waterfall_heatmap.jl create mode 100644 examples/waterfall_lines.jl diff --git a/examples/waterfall_heatmap.jl b/examples/waterfall_heatmap.jl new file mode 100644 index 0000000..7076f93 --- /dev/null +++ b/examples/waterfall_heatmap.jl @@ -0,0 +1,67 @@ +using Makie +using PortAudio +using DSP + +""" +Slide the values in the given matrix to the right by 1. +The rightmosts column is discarded and the leftmost column is +left alone. +""" +function shift1!(buf::AbstractMatrix) + for col in size(buf,2):-1:2 + @. buf[:, col] = buf[:, col-1] + end +end + +""" +takes a block of audio, FFT it, and write it to the beginning of the buffer +""" +function processbuf!(readbuf, win, dispbuf, fftbuf, fftplan) + readbuf .*= win + A_mul_B!(fftbuf, fftplan, readbuf) + shift1!(dispbuf) + @. dispbuf[end:-1:1,1] = log(clamp(abs(fftbuf[1:D]), 0.0001, Inf)) +end + +function processblock!(src, buf, win, dispbufs, fftbuf, fftplan) + read!(src, buf) + for dispbuf in dispbufs + processbuf!(buf, win, dispbuf, fftbuf, fftplan) + end +end + +N = 1024 # size of audio read +N2 = N÷2+1 # size of rfft output +D = 200 # number of bins to display +M = 200 # amount of history to keep +src = PortAudioStream(1, 2, blocksize=N) +buf = Array{Float32}(N) # buffer for reading +fftplan = plan_rfft(buf; flags=FFTW.EXHAUSTIVE) +fftbuf = Array{Complex{Float32}}(N2) # destination buf for FFT +dispbufs = [zeros(Float32, D, M) for i in 1:5, j in 1:5] # STFT bufs +win = gaussian(N, 0.125) + +scene = Scene(resolution=(1000,1000)) + +#pre-fill the display buffer so we can do a reasonable colormap +for _ in 1:M + processblock!(src, buf, win, dispbufs, fftbuf, fftplan) +end + +heatmaps = map(enumerate(IndexCartesian(), dispbufs)) do ibuf + i = ibuf[1] + buf = ibuf[2] + + # some function of the 2D index and the value + heatmap(buf, offset=(i[2]*size(buf, 2), i[1]*size(buf, 1))) +end + +center!(scene) + +while isopen(scene[:screen]) + processblock!(src, buf, dispbufs, fftbuf, fftplan) + for (hm, db) in zip(heatmaps, dispbufs) + hm[:heatmap] = db + end + render_frame(scene) +end diff --git a/examples/waterfall_lines.jl b/examples/waterfall_lines.jl new file mode 100644 index 0000000..bce3b4a --- /dev/null +++ b/examples/waterfall_lines.jl @@ -0,0 +1,38 @@ +using Makie, GeometryTypes +using PortAudio + +N = 1024 # size of audio read +N2 = N÷2+1 # size of rfft output +D = 200 # number of bins to display +M = 100 # number of lines to draw +S = 0.5 # motion speed of lines +src = PortAudioStream(1, 2, blocksize=N) +buf = Array{Float32}(N) +fftbuf = Array{Complex{Float32}}(N2) +magbuf = Array{Float32}(N2) +fftplan = plan_rfft(buf; flags=FFTW.EXHAUSTIVE) + +scene = Scene(resolution=(500,500)) +ax = axis(0:0.1:1, 0:0.1:1, 0:0.1:0.5) +center!(scene) + +ls = map(1:M) do _ + yoffset = to_node(to_value(scene[:time])) + offset = lift_node(scene[:time], yoffset) do t, yoff + Point3f0(0.0f0, (t-yoff)*S, 0.0f0) + end + l = lines(linspace(0,1,D), 0.0f0, zeros(Float32, D), + offset=offset, color=(:black, 0.1)) + (yoffset, l) +end + +while isopen(scene[:screen]) + for (yoffset, line) in ls + isopen(scene[:screen]) || break + read!(src, buf) + A_mul_B!(fftbuf, fftplan, buf) + @. magbuf = log(clamp(abs(fftbuf), 0.0001, Inf))/10+0.5 + line[:z] = magbuf[1:D] + push!(yoffset, to_value(scene[:time])) + end +end From 7f51c7859649d0cfc67ec05e697385e3ec2970d7 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 2 Apr 2019 13:39:12 -0400 Subject: [PATCH 16/31] enable warnings on xruns --- src/PortAudio.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 551fca4..ce25026 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -296,11 +296,11 @@ function handle_errors(stream::PortAudioStream) nread = read!(stream.errbuf, err) nread == 1 || break if err[1] == PA_SHIM_ERRMSG_ERR_OVERFLOW - warn("Error buffer overflowed on portaudio stream") + @warn "Error buffer overflowed on portaudio stream" elseif err[1] == PA_SHIM_ERRMSG_OVERFLOW - # warn("Input overflowed from $(name(stream.source))") + @warn "Input overflowed from $(name(stream.source))" elseif err[1] == PA_SHIM_ERRMSG_UNDERFLOW - # warn("Output underflowed to $(name(stream.sink))") + @warn "Output underflowed to $(name(stream.sink))" else error(""" Got unrecognized error code $(err[1]) from audio thread for From 01ddd6b83564426e766acb831c99411708560271 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Tue, 2 Apr 2019 14:06:00 -0400 Subject: [PATCH 17/31] adds `warn_xrun` option --- src/PortAudio.jl | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index ce25026..3a288c6 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -77,6 +77,7 @@ mutable struct PortAudioStream{T} samplerate::Float64 blocksize::Int stream::PaStream + warn_xruns::Bool sink # untyped because of circular type definition source # untyped because of circular type definition errbuf::RingBuffer{pa_shim_errmsg_t} # used to send errors from the portaudio callback @@ -86,7 +87,8 @@ mutable struct PortAudioStream{T} # this inner constructor is generally called via the top-level outer # constructor below function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans, outchans, sr, blocksize, synced) where {T} + inchans, outchans, sr, blocksize, synced, + warn_xruns) where {T} inchans = inchans == -1 ? indev.maxinchans : inchans outchans = outchans == -1 ? outdev.maxoutchans : outchans inparams = (inchans == 0) ? @@ -95,7 +97,7 @@ mutable struct PortAudioStream{T} outparams = (outchans == 0) ? Ptr{Pa_StreamParameters}(0) : Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL)) - this = new(sr, blocksize, C_NULL) + this = new(sr, blocksize, C_NULL, warn_xruns) @compat finalizer(close, this) this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2) this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2) @@ -150,9 +152,14 @@ Options: frames, and the round-trip latency is guaranteed constant. If `false`, you are free to read and write separately, but overflow or underflow can affect the round-trip latency. +* `warn_xruns`: Display a warning if there is a stream overrun or underrun, + which often happens when Julia is compiling, or with a + particularly large GC run. This can be quite verbose so is + false by default. """ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, synced=false) + inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, + synced=false, warn_xruns=false) if samplerate == -1 sampleratein = indev.defaultsamplerate samplerateout = outdev.defaultsamplerate @@ -167,7 +174,7 @@ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, samplerate = samplerateout end end - PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, synced) + PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, synced, warn_xruns) end # handle device names given as streams @@ -298,9 +305,9 @@ function handle_errors(stream::PortAudioStream) if err[1] == PA_SHIM_ERRMSG_ERR_OVERFLOW @warn "Error buffer overflowed on portaudio stream" elseif err[1] == PA_SHIM_ERRMSG_OVERFLOW - @warn "Input overflowed from $(name(stream.source))" + stream.warn_xruns && @warn "Input overflowed from $(name(stream.source))" elseif err[1] == PA_SHIM_ERRMSG_UNDERFLOW - @warn "Output underflowed to $(name(stream.sink))" + stream.warn_xruns && @warn "Output underflowed to $(name(stream.sink))" else error(""" Got unrecognized error code $(err[1]) from audio thread for From ea2c524426cb51093edf5e60a315e645879f5471 Mon Sep 17 00:00:00 2001 From: Martin Roa Villescas Date: Thu, 25 Jul 2019 13:46:26 +0200 Subject: [PATCH 18/31] Upgrade audiometer.jl example to Julia 1.* --- examples/audiometer.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/audiometer.jl b/examples/audiometer.jl index 8c03711..814d188 100644 --- a/examples/audiometer.jl +++ b/examples/audiometer.jl @@ -9,7 +9,7 @@ function micmeter(metersize) println("Press Ctrl-C to quit") while true block = read(mic, 512) - blockmax = maximum(abs(block)) # find the maximum value in the block + blockmax = maximum(abs.(block)) # find the maximum value in the block signalmax = max(signalmax, blockmax) # keep the maximum value ever print("\r") # reset the cursor to the beginning of the line printmeter(metersize, blockmax, signalmax) @@ -25,11 +25,11 @@ function printmeter(metersize, signal, peak) blankchars = max(0, peakpos-meterchars-1) for position in 1:meterchars - print_with_color(barcolor(metersize, position), ">") + printstyled(">", color=barcolor(metersize, position)) end print(" " ^ blankchars) - print_with_color(barcolor(metersize, peakpos), "|") + printstyled("|", color=barcolor(metersize, peakpos)) print(" " ^ (metersize - peakpos)) end From 221afe9b889bcb184d6f02c22eb8132249217c10 Mon Sep 17 00:00:00 2001 From: Martin Roa Villescas Date: Thu, 25 Jul 2019 14:14:14 +0200 Subject: [PATCH 19/31] Upgrade spectrum.jl example to Julia 1.* --- examples/spectrum.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/spectrum.jl b/examples/spectrum.jl index 18fbc5d..29fb90f 100644 --- a/examples/spectrum.jl +++ b/examples/spectrum.jl @@ -3,7 +3,7 @@ module SpectrumExample -using GR, PortAudio, SampledSignals +using GR, PortAudio, SampledSignals, FFTW const N = 1024 const stream = PortAudioStream(1, 0, blocksize=N) @@ -14,7 +14,7 @@ const fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])] while true read!(stream, buf) - plot(fs, abs(fft(buf)[fmin..fmax]), xlim=(fs[1],fs[end]), ylim=(0,100)) + plot(fs, abs.(fft(buf)[fmin..fmax]), xlim=(fs[1],fs[end]), ylim=(0,100)) end end From 1f1f721fec07cfa01989a521fae4e8c47f7d8d32 Mon Sep 17 00:00:00 2001 From: Julian P Samaroo Date: Sun, 8 Sep 2019 18:59:37 -0500 Subject: [PATCH 20/31] Make things work on Julia v1, use BB for some deps Removed Compat Switched to BB repo for libportaudio Re-enabled a now-passing pa_shim test --- Project.toml | 12 ++++++++++ deps/.gitignore | 2 ++ deps/build.jl | 51 ++++++++++++++++++++++++++---------------- src/PortAudio.jl | 9 ++++++-- src/pa_shim.jl | 10 ++++----- test/REQUIRE | 3 --- test/runtests.jl | 6 ++--- test/runtests_local.jl | 8 +++---- 8 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 Project.toml create mode 100644 deps/.gitignore delete mode 100644 test/REQUIRE diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..dcf07e3 --- /dev/null +++ b/Project.toml @@ -0,0 +1,12 @@ +name = "PortAudio" +uuid = "80ea8bcb-4634-5cb3-8ee8-a132660d1d2d" +repo = "https://github.com/JuliaAudio/PortAudio.jl.git" +version = "1.1.0" + +[deps] +BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +RingBuffers = "f6d8bcc6-4e01-5431-93c4-9d6004abab90" +SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/deps/.gitignore b/deps/.gitignore new file mode 100644 index 0000000..a8075e3 --- /dev/null +++ b/deps/.gitignore @@ -0,0 +1,2 @@ +build.log +deps.jl diff --git a/deps/build.jl b/deps/build.jl index 956f21c..820d21e 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,27 +1,40 @@ -using BinDeps -using Compat -using Compat.Sys: isapple, iswindows +using BinaryProvider # requires BinaryProvider 0.3.0 or later -@BinDeps.setup +# Parse some basic command-line arguments +const verbose = "--verbose" in ARGS +const prefix = Prefix(get([a for a in ARGS if a != "--verbose"], 1, joinpath(@__DIR__, "usr"))) +products = [ + LibraryProduct(prefix, ["libportaudio"], :libportaudio), +] -ENV["JULIA_ROOT"] = abspath(Compat.Sys.BINDIR, "../../") +# Download binaries from hosted location +bin_prefix = "https://github.com/jpsamaroo/PortAudioBuilder/releases/download/v19.6.0" -# include alias for WinRPM library -libportaudio = library_dependency("libportaudio", aliases=["libportaudio-2"]) +# Listing of files generated by BinaryBuilder: +download_info = Dict( + Windows(:i686) => ("$bin_prefix/libportaudio.v19.6.0.i686-w64-mingw32.tar.gz", "520efc322259b4eb05d41ba8d22f3b744a611b4b1c4ad651fbdd9160f793b4f1"), + Linux(:powerpc64le, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.powerpc64le-linux-gnu.tar.gz", "de994973556bd27ac96a02be4ca1f809650de5b4e714492e02bce414cae6f34b"), + MacOS(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-apple-darwin14.tar.gz", "406961f35083944e5503dd6a51385c18dfac7969aaa2593e37c1907e353032b7"), + Linux(:x86_64, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-linux-gnu.tar.gz", "bfb31ffd54fa48802f9af31ecbbae07506baaee52880e6d458a192ea0d6d6a21"), + Windows(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-w64-mingw32.tar.gz", "ebd73c312836a1370137335faaa45dee484a50adb183790fa488e7799ebe2299"), +) -# TODO: add other providers with correct names -provides(AptGet, "libportaudio2", libportaudio) -provides(Pacman, "portaudio", libportaudio) - - -@static if isapple() - using Homebrew - provides(Homebrew.HB, "portaudio", libportaudio) +# Install unsatisfied or updated dependencies: +unsatisfied = any(!satisfied(p; verbose=verbose) for p in products) +dl_info = choose_download(download_info, platform_key_abi()) +if dl_info === nothing && unsatisfied + # If we don't have a compatible .tar.gz to download, complain. + # Alternatively, you could attempt to install from a separate provider, + # build from source or something even more ambitious here. + error("Your platform (\"$(Sys.MACHINE)\", parsed as \"$(triplet(platform_key_abi()))\") is not supported by this package!") end -@static if iswindows() - using WinRPM - provides(WinRPM.RPM, "libportaudio2", libportaudio, os = :Windows) +# If we have a download, and we are unsatisfied (or the version we're +# trying to install is not itself installed) then load it up! +if unsatisfied || !isinstalled(dl_info...; prefix=prefix) + # Download and install binaries + install(dl_info...; prefix=prefix, force=true, verbose=verbose) end -@BinDeps.install Dict(:libportaudio => :libportaudio, ) +# Write out a deps.jl file that will contain mappings for our products +write_deps_file(joinpath(@__DIR__, "deps.jl"), products, verbose=verbose) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 3a288c6..65b706b 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -4,16 +4,21 @@ module PortAudio using SampledSignals using RingBuffers +#= using Compat using Compat: undef, fetch, @compat using Compat.LinearAlgebra: transpose! using Compat: stdout using Compat.Sys: iswindows +=# import Base: eltype, show import Base: close, isopen import Base: read, read!, write, flush +import LinearAlgebra +import LinearAlgebra: transpose! + export PortAudioStream @@ -98,7 +103,7 @@ mutable struct PortAudioStream{T} Ptr{Pa_StreamParameters}(0) : Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL)) this = new(sr, blocksize, C_NULL, warn_xruns) - @compat finalizer(close, this) + finalizer(close, this) this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2) this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2) this.errbuf = RingBuffer{pa_shim_errmsg_t}(1, ERR_BUFSIZE) @@ -424,7 +429,7 @@ function set_global_callbacks() end function suppress_err(dofunc::Function) - nullfile = @static iswindows() ? "nul" : "/dev/null" + nullfile = @static Sys.iswindows() ? "nul" : "/dev/null" open(nullfile, "w") do io redirect_stdout(dofunc, io) end diff --git a/src/pa_shim.jl b/src/pa_shim.jl index e9919b1..4ddc7c0 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -2,15 +2,15 @@ function find_pa_shim() libdir = joinpath(@__DIR__, "..", "deps", "usr", "lib") libsuffix = "" basename = "pa_shim" - @static if Compat.Sys.islinux() && Sys.ARCH == :x86_64 + @static if Sys.islinux() && Sys.ARCH == :x86_64 libsuffix = "x86_64-linux-gnu" - elseif Compat.Sys.islinux() && Sys.ARCH == :i686 + elseif Sys.islinux() && Sys.ARCH == :i686 libsuffix = "i686-linux-gnu" - elseif Compat.Sys.isapple() && Sys.ARCH == :x86_64 + elseif Sys.isapple() && Sys.ARCH == :x86_64 libsuffix = "x86_64-apple-darwin14" - elseif Compat.Sys.iswindows() && Sys.ARCH == :x86_64 + elseif Sys.iswindows() && Sys.ARCH == :x86_64 libsuffix = "x86_64-w64-mingw32" - elseif Compat.Sys.iswindows() && Sys.ARCH == :i686 + elseif Sys.iswindows() && Sys.ARCH == :i686 libsuffix = "i686-w64-mingw32" elseif !any( (sfx) -> isfile(joinpath(libdir, "$basename.$sfx")), diff --git a/test/REQUIRE b/test/REQUIRE deleted file mode 100644 index 9e686b5..0000000 --- a/test/REQUIRE +++ /dev/null @@ -1,3 +0,0 @@ -Compat -SampledSignals -RingBuffers diff --git a/test/runtests.jl b/test/runtests.jl index b48172e..d3ea489 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,9 @@ #!/usr/bin/env julia -using Compat -using Compat.Test -import Compat: Cvoid using PortAudio using SampledSignals using RingBuffers +using Test # pull in some extra stuff we need to test the callback directly using PortAudio: notifyhandle, notifycb_c, shim_processcb_c @@ -204,7 +202,7 @@ end end @testset "using correct shim version" begin - @test_broken PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" + @test PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" end @testset "Basic callback functionality" begin diff --git a/test/runtests_local.jl b/test/runtests_local.jl index f39bcf9..44cc42a 100644 --- a/test/runtests_local.jl +++ b/test/runtests_local.jl @@ -5,13 +5,13 @@ include("runtests.jl") # these default values are specific to my machines -if Compat.Sys.iswindows() +if Sys.iswindows() default_indev = "Microphone Array (Realtek High " default_outdev = "Speaker/Headphone (Realtek High" -elseif Compat.Sys.isapple() +elseif Sys.isapple() default_indev = "Built-in Microphone" default_outdev = "Built-in Output" -elseif Compat.Sys.islinux() +elseif Sys.islinux() default_indev = "default" default_outdev = "default" end @@ -50,7 +50,7 @@ end write(stream, buf) io = IOBuffer() show(io, stream) - @test Compat.occursin(""" + @test occursin(""" PortAudioStream{Float32} Samplerate: 44100.0Hz Buffer Size: 4096 frames From 918e9d6986e5e28c5b6aced5ad66a00692da0f5f Mon Sep 17 00:00:00 2001 From: Julian P Samaroo Date: Wed, 11 Sep 2019 07:10:05 -0500 Subject: [PATCH 21/31] Fix CI attempt 1 --- ci_setup.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci_setup.jl b/ci_setup.jl index 82a0a65..0e84770 100644 --- a/ci_setup.jl +++ b/ci_setup.jl @@ -11,11 +11,11 @@ else using Pkg # for now we need to `clone` because there's no way to specify the # package name for `add` - Pkg.clone(pwd(), "PortAudio") + #Pkg.clone(pwd(), "PortAudio") Pkg.build("PortAudio") Pkg.add(PackageSpec(name="SampledSignals", rev="master")) Pkg.add(PackageSpec(name="RingBuffers", rev="master")) end # add test deps manually because we'll be running test/runtests.jl manually -Pkg.add("Compat") +#Pkg.add("Compat") From 7944836b4970151227ffbd53c0eb19241b3ee34a Mon Sep 17 00:00:00 2001 From: Julian P Samaroo Date: Wed, 11 Sep 2019 07:23:13 -0500 Subject: [PATCH 22/31] Fix CI attempt 2 --- .travis.yml | 2 +- deps/build.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5607f0..a967b45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ os: - osx sudo: required julia: - - 0.6 - 1.0 + - 1.2 - nightly matrix: allow_failures: diff --git a/deps/build.jl b/deps/build.jl index 820d21e..e8b62e1 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -12,11 +12,11 @@ bin_prefix = "https://github.com/jpsamaroo/PortAudioBuilder/releases/download/v1 # Listing of files generated by BinaryBuilder: download_info = Dict( - Windows(:i686) => ("$bin_prefix/libportaudio.v19.6.0.i686-w64-mingw32.tar.gz", "520efc322259b4eb05d41ba8d22f3b744a611b4b1c4ad651fbdd9160f793b4f1"), - Linux(:powerpc64le, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.powerpc64le-linux-gnu.tar.gz", "de994973556bd27ac96a02be4ca1f809650de5b4e714492e02bce414cae6f34b"), - MacOS(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-apple-darwin14.tar.gz", "406961f35083944e5503dd6a51385c18dfac7969aaa2593e37c1907e353032b7"), + Windows(:i686) => ("$bin_prefix/libportaudio.v19.6.0.i686-w64-mingw32.tar.gz", "0d38fd434c21e8cd8a45193d50cc66c35d920ed0f4bf7d5682a3ad7819b9eb01"), + Linux(:powerpc64le, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.powerpc64le-linux-gnu.tar.gz", "4050d5b887f2afa5891d1b4e5f55bf1f5f140c97b7c9fe4140f495263d594c37"), + MacOS(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-apple-darwin14.tar.gz", "1c20a2884bd73e5827508a47d0794b845040569739326b2b37eaa1d9bcb2a7a0"), Linux(:x86_64, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-linux-gnu.tar.gz", "bfb31ffd54fa48802f9af31ecbbae07506baaee52880e6d458a192ea0d6d6a21"), - Windows(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-w64-mingw32.tar.gz", "ebd73c312836a1370137335faaa45dee484a50adb183790fa488e7799ebe2299"), + Windows(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-w64-mingw32.tar.gz", "e373f78eebe7b08a3e07c72a579209f305232281d0ff7c8b07ad48ffb1711259"), ) # Install unsatisfied or updated dependencies: From a7919c5b642f91becc244a050441891ae9bb9592 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Fri, 27 Dec 2019 22:50:45 -0800 Subject: [PATCH 23/31] Upgrade `PortAudio` to JLL packages --- Manifest.toml | 206 +++ Project.toml | 12 +- deps/.gitignore | 2 - deps/build.jl | 40 - deps/src/Makefile | 80 -- deps/src/build.sh | 54 - deps/src/dockerbuild_cb.sh | 8 - deps/src/dockerbuild_hbb.sh | 13 - deps/src/pa_shim.c | 109 -- deps/src/portaudio.h | 1225 ----------------- deps/usr/lib/pa_shim_arm-linux-gnueabihf.so | Bin 6376 -> 0 bytes deps/usr/lib/pa_shim_i686-linux-gnu.so | Bin 6373 -> 0 bytes deps/usr/lib/pa_shim_i686-w64-mingw32.dll | Bin 81524 -> 0 bytes deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so | Bin 9320 -> 0 bytes .../lib/pa_shim_x86_64-apple-darwin14.dylib | Bin 8972 -> 0 bytes deps/usr/lib/pa_shim_x86_64-linux-gnu.so | Bin 8340 -> 0 bytes deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll | Bin 110424 -> 0 bytes src/PortAudio.jl | 8 +- src/pa_shim.jl | 28 - test/runtests.jl | 2 +- 20 files changed, 219 insertions(+), 1568 deletions(-) create mode 100644 Manifest.toml delete mode 100644 deps/.gitignore delete mode 100644 deps/build.jl delete mode 100644 deps/src/Makefile delete mode 100755 deps/src/build.sh delete mode 100755 deps/src/dockerbuild_cb.sh delete mode 100755 deps/src/dockerbuild_hbb.sh delete mode 100644 deps/src/pa_shim.c delete mode 100644 deps/src/portaudio.h delete mode 100755 deps/usr/lib/pa_shim_arm-linux-gnueabihf.so delete mode 100755 deps/usr/lib/pa_shim_i686-linux-gnu.so delete mode 100755 deps/usr/lib/pa_shim_i686-w64-mingw32.dll delete mode 100755 deps/usr/lib/pa_shim_powerpc64le-linux-gnu.so delete mode 100755 deps/usr/lib/pa_shim_x86_64-apple-darwin14.dylib delete mode 100755 deps/usr/lib/pa_shim_x86_64-linux-gnu.so delete mode 100755 deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 0000000..2739782 --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,206 @@ +# This file is machine-generated - editing it directly is not advised + +[[AbstractFFTs]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "051c95d6836228d120f5f4b984dd5aba1624f716" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "0.5.0" + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[BinaryProvider]] +deps = ["Libdl", "SHA"] +git-tree-sha1 = "5b08ed6036d9d3f0ee6369410b830f8873d4024c" +uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232" +version = "0.5.8" + +[[Compat]] +deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] +git-tree-sha1 = "ed2c4abadf84c53d9e58510b5fc48912c2336fbb" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "2.2.0" + +[[DSP]] +deps = ["FFTW", "LinearAlgebra", "Polynomials", "Random", "Reexport", "SpecialFunctions", "Statistics"] +git-tree-sha1 = "fa9564dc41fb91716c2e3353fe447f268a08f14d" +uuid = "717857b8-e6f2-59f4-9121-6e50c889abd2" +version = "0.6.2" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + +[[Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[FFTW]] +deps = ["AbstractFFTs", "FFTW_jll", "IntelOpenMP_jll", "Libdl", "LinearAlgebra", "MKL_jll", "Reexport"] +git-tree-sha1 = "109d82fa4b00429f9afcce873e9f746f11f018d3" +uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +version = "1.2.0" + +[[FFTW_jll]] +deps = ["Libdl", "Pkg"] +git-tree-sha1 = "05674f209a6e3387dd103a945b0113eeb64b1a58" +uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a" +version = "3.3.9+3" + +[[FixedPointNumbers]] +git-tree-sha1 = "d14a6fa5890ea3a7e5dcab6811114f132fec2b4b" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.6.1" + +[[IntelOpenMP_jll]] +deps = ["Libdl", "Pkg"] +git-tree-sha1 = "fb8e1c7a5594ba56f9011310790e03b5384998d6" +uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0" +version = "2018.0.3+0" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[IntervalSets]] +deps = ["Dates", "Statistics"] +git-tree-sha1 = "4214b48a62eb8f2c292b2ee34a508c256c0cdbc9" +uuid = "8197267c-284f-5f27-9208-e0e47529a953" +version = "0.3.2" + +[[LibGit2]] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[LinearAlgebra]] +deps = ["Libdl"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[MKL_jll]] +deps = ["Libdl", "Pkg"] +git-tree-sha1 = "61069ae718b8ab1e325bbfb4e5268902e7ea08e3" +uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7" +version = "2019.0.117+0" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[OpenSpecFun_jll]] +deps = ["Libdl", "Pkg"] +git-tree-sha1 = "65f672edebf3f4e613ddf37db9dcbd7a407e5e90" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.3+1" + +[[Pkg]] +deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" + +[[Polynomials]] +deps = ["LinearAlgebra", "RecipesBase"] +git-tree-sha1 = "ae71c2329790af97b7682b11241b3609e4d48626" +uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" +version = "0.6.0" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[RecipesBase]] +git-tree-sha1 = "7bdce29bc9b2f5660a6e5e64d64d91ec941f6aa2" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "0.7.0" + +[[Reexport]] +deps = ["Pkg"] +git-tree-sha1 = "7b1d07f411bc8ddb7977ec7f377b97b158514fe0" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "0.2.0" + +[[RingBuffers]] +deps = ["BinaryProvider", "Compat"] +git-tree-sha1 = "a22900f141fae3e8c6f2ab9e4d48dfca274fafb8" +uuid = "f6d8bcc6-4e01-5431-93c4-9d6004abab90" +version = "1.2.0" + +[[SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" + +[[SampledSignals]] +deps = ["Compat", "DSP", "FFTW", "FixedPointNumbers", "IntervalSets", "LinearAlgebra", "TreeViews", "Unitful"] +git-tree-sha1 = "ce5705b342baa4a995ba6fa7ca9aa96e6647ac91" +uuid = "bd7594eb-a658-542f-9e75-4c4d8908c167" +version = "2.1.0" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[SpecialFunctions]] +deps = ["OpenSpecFun_jll"] +git-tree-sha1 = "268052ee908b2c086cc0011f528694f02f3e2408" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "0.9.0" + +[[Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[Test]] +deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[TreeViews]] +deps = ["Test"] +git-tree-sha1 = "8d0d7a3fe2f30d6a7f833a5f19f7c7a5b396eae6" +uuid = "a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7" +version = "0.3.0" + +[[UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[Unitful]] +deps = ["LinearAlgebra", "Random"] +git-tree-sha1 = "92bdf0ccfa9612b167d0adaadef832a09971ceb0" +uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" +version = "0.17.0" + +[[libportaudio_jll]] +deps = ["Libdl", "Pkg"] +path = "/Users/sabae/.julia/dev/libportaudio_jll" +uuid = "2d7b7beb-0762-5160-978e-1ab83a1e8a31" +version = "19.6.0+1" diff --git a/Project.toml b/Project.toml index dcf07e3..34dab79 100644 --- a/Project.toml +++ b/Project.toml @@ -4,9 +4,19 @@ repo = "https://github.com/JuliaAudio/PortAudio.jl.git" version = "1.1.0" [deps] -BinaryProvider = "b99e7846-7c00-51b0-8f62-c81ae34c0232" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" RingBuffers = "f6d8bcc6-4e01-5431-93c4-9d6004abab90" SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167" +libportaudio_jll = "2d7b7beb-0762-5160-978e-1ab83a1e8a31" + +[compat] +RingBuffers = "1.2" +julia = "1.3" + +[extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" + +[targets] +test = ["Test", "TestSetExtensions"] diff --git a/deps/.gitignore b/deps/.gitignore deleted file mode 100644 index a8075e3..0000000 --- a/deps/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build.log -deps.jl diff --git a/deps/build.jl b/deps/build.jl deleted file mode 100644 index e8b62e1..0000000 --- a/deps/build.jl +++ /dev/null @@ -1,40 +0,0 @@ -using BinaryProvider # requires BinaryProvider 0.3.0 or later - -# Parse some basic command-line arguments -const verbose = "--verbose" in ARGS -const prefix = Prefix(get([a for a in ARGS if a != "--verbose"], 1, joinpath(@__DIR__, "usr"))) -products = [ - LibraryProduct(prefix, ["libportaudio"], :libportaudio), -] - -# Download binaries from hosted location -bin_prefix = "https://github.com/jpsamaroo/PortAudioBuilder/releases/download/v19.6.0" - -# Listing of files generated by BinaryBuilder: -download_info = Dict( - Windows(:i686) => ("$bin_prefix/libportaudio.v19.6.0.i686-w64-mingw32.tar.gz", "0d38fd434c21e8cd8a45193d50cc66c35d920ed0f4bf7d5682a3ad7819b9eb01"), - Linux(:powerpc64le, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.powerpc64le-linux-gnu.tar.gz", "4050d5b887f2afa5891d1b4e5f55bf1f5f140c97b7c9fe4140f495263d594c37"), - MacOS(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-apple-darwin14.tar.gz", "1c20a2884bd73e5827508a47d0794b845040569739326b2b37eaa1d9bcb2a7a0"), - Linux(:x86_64, libc=:glibc) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-linux-gnu.tar.gz", "bfb31ffd54fa48802f9af31ecbbae07506baaee52880e6d458a192ea0d6d6a21"), - Windows(:x86_64) => ("$bin_prefix/libportaudio.v19.6.0.x86_64-w64-mingw32.tar.gz", "e373f78eebe7b08a3e07c72a579209f305232281d0ff7c8b07ad48ffb1711259"), -) - -# Install unsatisfied or updated dependencies: -unsatisfied = any(!satisfied(p; verbose=verbose) for p in products) -dl_info = choose_download(download_info, platform_key_abi()) -if dl_info === nothing && unsatisfied - # If we don't have a compatible .tar.gz to download, complain. - # Alternatively, you could attempt to install from a separate provider, - # build from source or something even more ambitious here. - error("Your platform (\"$(Sys.MACHINE)\", parsed as \"$(triplet(platform_key_abi()))\") is not supported by this package!") -end - -# If we have a download, and we are unsatisfied (or the version we're -# trying to install is not itself installed) then load it up! -if unsatisfied || !isinstalled(dl_info...; prefix=prefix) - # Download and install binaries - install(dl_info...; prefix=prefix, force=true, verbose=verbose) -end - -# Write out a deps.jl file that will contain mappings for our products -write_deps_file(joinpath(@__DIR__, "deps.jl"), products, verbose=verbose) diff --git a/deps/src/Makefile b/deps/src/Makefile deleted file mode 100644 index c711226..0000000 --- a/deps/src/Makefile +++ /dev/null @@ -1,80 +0,0 @@ -# Makefile originally lifted from Clang.jl -# Copyright (c) 2012-: Isaiah Norton and [contributors](https://github.com/ihnorton/Clang.jl/graphs/contributors) - -ifeq (exists, $(shell [ -e Make.user ] && echo exists )) -include Make.user -endif - -TARGETDIR=../usr/lib -TARGETBASENAME=pa_shim -OBJS = pa_shim.o - -# check to see if the user passed in a HOST variable for cross-compiling -ifeq ($(HOST),) - # Figure out OS and architecture - OS=$(shell uname) - ifneq ($(findstring MINGW,$(OS)),) - OS=WINNT - endif -else - HOSTSUFFIX=_$(HOST) - ifneq ($(findstring linux,$(HOST)),) - OS=Linux - else ifneq ($(findstring darwin,$(HOST)),) - OS=Darwin - else ifneq ($(findstring mingw,$(HOST)),) - OS=WINNT - endif -endif - -CFLAGS = -Wall -Wno-strict-aliasing -fno-omit-frame-pointer -I../../../RingBuffers/deps/src -LDFLAGS += - -ifeq ($(OS), WINNT) - LIBS += - LDFLAGS += -shared -L../../../RingBuffers/deps/usr/lib -lpa_ringbuffer$(HOSTSUFFIX) - INC += - SHACMD = sha256sum - SHLIB_EXT = dll -else ifeq ($(OS), Darwin) - LIBS += - INC += - # we'll rely on Julia to load RingBuffers.jl, which will in turn load the C - # library that we depend on for these symbols - LDFLAGS += -dynamiclib -Wl,-undefined,dynamic_lookup - SHLIB_EXT = dylib - SHACMD = shasum -a256 -else - CFLAGS += -fPIC - LIBS += - INC += - LDFLAGS += -shared - SHLIB_EXT = so - SHACMD = sha256sum -endif - -SOURCEHASH = $(shell $(SHACMD) pa_shim.c | awk '{print $$1}') -CFLAGS += -DSOURCEHASH=\"$(SOURCEHASH)\" - -TARGET=$(TARGETDIR)/$(TARGETBASENAME)$(HOSTSUFFIX).$(SHLIB_EXT) - -.PHONY: clean cleantemp default - -default: $(TARGET) - -%.o: %.c Makefile - $(CC) $< -c -o $@ $(INC) $(CFLAGS) - -$(TARGETDIR): - mkdir -p $@ - -$(TARGET): $(OBJS) $(TARGETDIR) Makefile - $(CC) $(OBJS) $(LDFLAGS) -o $@ $(LIBS) - -cleantemp: - rm -f $(OBJS) - -clean: cleantemp - rm -f $(TARGETDIR)/$(TARGETBASENAME)*.so - rm -f $(TARGETDIR)/$(TARGETBASENAME)*.dylib - rm -f $(TARGETDIR)/$(TARGETBASENAME)*.dll diff --git a/deps/src/build.sh b/deps/src/build.sh deleted file mode 100755 index 4e210e1..0000000 --- a/deps/src/build.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -# User docker to build pa_shim library for all supported platforms. - -set -e - -make clean - -echo "" -# NOTE: the darwin build ends up actually being x86_64-apple-darwin14. It gets -# mapped within the docker machine -for platform in \ - arm-linux-gnueabihf \ - powerpc64le-linux-gnu \ - x86_64-apple-darwin \ - x86_64-w64-mingw32 \ - i686-w64-mingw32; do - echo "================================" - echo "building for $platform..." - docker run --rm \ - -v $(pwd)/../..:/workdir \ - -v $(pwd)/../../../RingBuffers:/RingBuffers \ - -w /workdir/deps/src \ - -e CROSS_TRIPLE=$platform \ - multiarch/crossbuild \ - ./dockerbuild_cb.sh - echo "================================" - echo "" -done - -# we use holy-build-box for the x86 linux builds because it uses an older -# glibc so it should be compatible with more user environments -echo "================================" -echo "building for x86_64-linux-gnu..." -docker run --rm \ - -v $(pwd)/../..:/workdir \ - -v $(pwd)/../../../RingBuffers:/RingBuffers \ - -w /workdir/deps/src \ - -e HOST=x86_64-linux-gnu \ - phusion/holy-build-box-64 \ - ./dockerbuild_hbb.sh -echo "================================" -echo "" - -echo "================================" -echo "building for i686-linux-gnu..." -docker run --rm \ - -v $(pwd)/../..:/workdir \ - -v $(pwd)/../../../RingBuffers:/RingBuffers \ - -w /workdir/deps/src \ - -e HOST=i686-linux-gnu \ - phusion/holy-build-box-32 \ - ./dockerbuild_hbb.sh -echo "================================" diff --git a/deps/src/dockerbuild_cb.sh b/deps/src/dockerbuild_cb.sh deleted file mode 100755 index 7b04f2b..0000000 --- a/deps/src/dockerbuild_cb.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# this script is run by build.sh within each docker instance to do the build. - -set -e - -make HOST=$CROSS_TRIPLE -make cleantemp diff --git a/deps/src/dockerbuild_hbb.sh b/deps/src/dockerbuild_hbb.sh deleted file mode 100755 index 090611a..0000000 --- a/deps/src/dockerbuild_hbb.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# this script is run by build.sh within each docker instance to do the build. -# it's meant to be run from within a "Holy Build Box" docker instance. - -set -e - -# Activate Holy Build Box environment. -source /hbb_exe/activate - -set -x -make HOST=$HOST -make cleantemp diff --git a/deps/src/pa_shim.c b/deps/src/pa_shim.c deleted file mode 100644 index c6e6f45..0000000 --- a/deps/src/pa_shim.c +++ /dev/null @@ -1,109 +0,0 @@ -#include "portaudio.h" -#include -#include -#include -#include - -#define MIN(x, y) ((x) < (y) ? (x) : (y)) - -typedef enum { - PA_SHIM_ERRMSG_OVERFLOW, // input overflow - PA_SHIM_ERRMSG_UNDERFLOW, // output underflow - PA_SHIM_ERRMSG_ERR_OVERFLOW, // error buffer overflowed -} pa_shim_errmsg_t; - -// this callback type is used to notify the Julia side that the portaudio -// callback has run -typedef void (*pa_shim_notifycb_t)(void *userdata); - -// This struct is shared between the Julia side and C -typedef struct { - PaUtilRingBuffer *inputbuf; // ringbuffer for input - PaUtilRingBuffer *outputbuf; // ringbuffer for output - PaUtilRingBuffer *errorbuf; // ringbuffer to send error notifications - int sync; // keep input/output ring buffers synchronized (0/1) - pa_shim_notifycb_t notifycb; // Julia callback to notify conditions - void *inputhandle; // condition to notify on new input - void *outputhandle; // condition to notify when ready for output - void *errorhandle; // condition to notify on new error - void *globalhandle; // only needed for libuv workaround -} pa_shim_info_t; - -void senderr(pa_shim_info_t *info, pa_shim_errmsg_t msg) { - if(PaUtil_GetRingBufferWriteAvailable(info->errorbuf) == 1) { - // we've overflowed our error buffer! notify the host. - msg = PA_SHIM_ERRMSG_ERR_OVERFLOW; - } - PaUtil_WriteRingBuffer(info->errorbuf, &msg, 1); - // for now we're relying on the global handle. uncomment the following - // when the libuv workaround is no longer necessary - // if(info->notifycb) { - // info->notifycb(info->errorhandle); - // } -} - -// return the sha256 hash of the shim source so we can make sure things are in sync -const char *pa_shim_getsourcehash(void) -{ - // defined on the command-line at build-time - return SOURCEHASH; -} - -/* - * This routine will be called by the PortAudio engine when audio is needed. - * It may called at interrupt level on some machines so don't do anything that - * could mess up the system like calling malloc() or free(). - */ -int pa_shim_processcb(const void *input, void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) -{ - pa_shim_info_t *info = userData; - if(info->notifycb == NULL) { - fprintf(stderr, "pa_shim ERROR: notifycb is NULL\n"); - return paAbort; - } - int nwrite; - if(info->inputbuf) { - nwrite = PaUtil_GetRingBufferWriteAvailable(info->inputbuf); - nwrite = MIN(frameCount, nwrite); - } - int nread; - if(info->outputbuf) { - nread = PaUtil_GetRingBufferReadAvailable(info->outputbuf); - nread = MIN(frameCount, nread); - } - if(info->inputbuf && info->outputbuf && info->sync) { - // to keep the buffers synchronized, set readable and writable to - // their minimum value - nread = MIN(nread, nwrite); - nwrite = nread; - } - // read/write from the ringbuffers - if(info->inputbuf) { - PaUtil_WriteRingBuffer(info->inputbuf, input, nwrite); - // for now we're relying on the global handle. uncomment the following - // when the libuv workaround is no longer necessary - // info->notifycb(info->inputhandle); - if(nwrite < frameCount) { - senderr(info, PA_SHIM_ERRMSG_OVERFLOW); - } - } - if(info->outputbuf) { - PaUtil_ReadRingBuffer(info->outputbuf, output, nread); - // for now we're relying on the global handle. uncomment the following - // when the libuv workaround is no longer necessary - // info->notifycb(info->outputhandle); - if(nread < frameCount) { - senderr(info, PA_SHIM_ERRMSG_UNDERFLOW); - // we didn't fill the whole output buffer, so zero it out - memset(output+nread*info->outputbuf->elementSizeBytes, 0, - (frameCount - nread)*info->outputbuf->elementSizeBytes); - } - } - - info->notifycb(info->globalhandle); - return paContinue; -} diff --git a/deps/src/portaudio.h b/deps/src/portaudio.h deleted file mode 100644 index 8a94aaf..0000000 --- a/deps/src/portaudio.h +++ /dev/null @@ -1,1225 +0,0 @@ -#ifndef PORTAUDIO_H -#define PORTAUDIO_H -/* - * $Id$ - * PortAudio Portable Real-Time Audio Library - * PortAudio API Header File - * Latest version available at: http://www.portaudio.com/ - * - * Copyright (c) 1999-2002 Ross Bencina and Phil Burk - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * The text above constitutes the entire PortAudio license; however, - * the PortAudio community also makes the following non-binding requests: - * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the - * license above. - */ - -/** @file - @ingroup public_header - @brief The portable PortAudio API. -*/ - - -#ifdef __cplusplus -extern "C" -{ -#endif /* __cplusplus */ - -/** Retrieve the release number of the currently running PortAudio build. - For example, for version "19.5.1" this will return 0x00130501. - - @see paMakeVersionNumber -*/ -int Pa_GetVersion( void ); - -/** Retrieve a textual description of the current PortAudio build, - e.g. "PortAudio V19.5.0-devel, revision 1952M". - The format of the text may change in the future. Do not try to parse the - returned string. - - @deprecated As of 19.5.0, use Pa_GetVersionInfo()->versionText instead. -*/ -const char* Pa_GetVersionText( void ); - -/** - Generate a packed integer version number in the same format used - by Pa_GetVersion(). Use this to compare a specified version number with - the currently running version. For example: - - @code - if( Pa_GetVersion() < paMakeVersionNumber(19,5,1) ) {} - @endcode - - @see Pa_GetVersion, Pa_GetVersionInfo - @version Available as of 19.5.0. -*/ -#define paMakeVersionNumber(major, minor, subminor) \ - (((major)&0xFF)<<16 | ((minor)&0xFF)<<8 | ((subminor)&0xFF)) - - -/** - A structure containing PortAudio API version information. - @see Pa_GetVersionInfo, paMakeVersionNumber - @version Available as of 19.5.0. -*/ -typedef struct PaVersionInfo { - int versionMajor; - int versionMinor; - int versionSubMinor; - /** - This is currently the Git revision hash but may change in the future. - The versionControlRevision is updated by running a script before compiling the library. - If the update does not occur, this value may refer to an earlier revision. - */ - const char *versionControlRevision; - /** Version as a string, for example "PortAudio V19.5.0-devel, revision 1952M" */ - const char *versionText; -} PaVersionInfo; - -/** Retrieve version information for the currently running PortAudio build. - @return A pointer to an immutable PaVersionInfo structure. - - @note This function can be called at any time. It does not require PortAudio - to be initialized. The structure pointed to is statically allocated. Do not - attempt to free it or modify it. - - @see PaVersionInfo, paMakeVersionNumber - @version Available as of 19.5.0. -*/ -const PaVersionInfo* Pa_GetVersionInfo(); - - -/** Error codes returned by PortAudio functions. - Note that with the exception of paNoError, all PaErrorCodes are negative. -*/ - -typedef int PaError; -typedef enum PaErrorCode -{ - paNoError = 0, - - paNotInitialized = -10000, - paUnanticipatedHostError, - paInvalidChannelCount, - paInvalidSampleRate, - paInvalidDevice, - paInvalidFlag, - paSampleFormatNotSupported, - paBadIODeviceCombination, - paInsufficientMemory, - paBufferTooBig, - paBufferTooSmall, - paNullCallback, - paBadStreamPtr, - paTimedOut, - paInternalError, - paDeviceUnavailable, - paIncompatibleHostApiSpecificStreamInfo, - paStreamIsStopped, - paStreamIsNotStopped, - paInputOverflowed, - paOutputUnderflowed, - paHostApiNotFound, - paInvalidHostApi, - paCanNotReadFromACallbackStream, - paCanNotWriteToACallbackStream, - paCanNotReadFromAnOutputOnlyStream, - paCanNotWriteToAnInputOnlyStream, - paIncompatibleStreamHostApi, - paBadBufferPtr -} PaErrorCode; - - -/** Translate the supplied PortAudio error code into a human readable - message. -*/ -const char *Pa_GetErrorText( PaError errorCode ); - - -/** Library initialization function - call this before using PortAudio. - This function initializes internal data structures and prepares underlying - host APIs for use. With the exception of Pa_GetVersion(), Pa_GetVersionText(), - and Pa_GetErrorText(), this function MUST be called before using any other - PortAudio API functions. - - If Pa_Initialize() is called multiple times, each successful - call must be matched with a corresponding call to Pa_Terminate(). - Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not - required to be fully nested. - - Note that if Pa_Initialize() returns an error code, Pa_Terminate() should - NOT be called. - - @return paNoError if successful, otherwise an error code indicating the cause - of failure. - - @see Pa_Terminate -*/ -PaError Pa_Initialize( void ); - - -/** Library termination function - call this when finished using PortAudio. - This function deallocates all resources allocated by PortAudio since it was - initialized by a call to Pa_Initialize(). In cases where Pa_Initialise() has - been called multiple times, each call must be matched with a corresponding call - to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically - close any PortAudio streams that are still open. - - Pa_Terminate() MUST be called before exiting a program which uses PortAudio. - Failure to do so may result in serious resource leaks, such as audio devices - not being available until the next reboot. - - @return paNoError if successful, otherwise an error code indicating the cause - of failure. - - @see Pa_Initialize -*/ -PaError Pa_Terminate( void ); - - - -/** The type used to refer to audio devices. Values of this type usually - range from 0 to (Pa_GetDeviceCount()-1), and may also take on the PaNoDevice - and paUseHostApiSpecificDeviceSpecification values. - - @see Pa_GetDeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification -*/ -typedef int PaDeviceIndex; - - -/** A special PaDeviceIndex value indicating that no device is available, - or should be used. - - @see PaDeviceIndex -*/ -#define paNoDevice ((PaDeviceIndex)-1) - - -/** A special PaDeviceIndex value indicating that the device(s) to be used - are specified in the host api specific stream info structure. - - @see PaDeviceIndex -*/ -#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) - - -/* Host API enumeration mechanism */ - -/** The type used to enumerate to host APIs at runtime. Values of this type - range from 0 to (Pa_GetHostApiCount()-1). - - @see Pa_GetHostApiCount -*/ -typedef int PaHostApiIndex; - - -/** Retrieve the number of available host APIs. Even if a host API is - available it may have no devices available. - - @return A non-negative value indicating the number of available host APIs - or, a PaErrorCode (which are always negative) if PortAudio is not initialized - or an error is encountered. - - @see PaHostApiIndex -*/ -PaHostApiIndex Pa_GetHostApiCount( void ); - - -/** Retrieve the index of the default host API. The default host API will be - the lowest common denominator host API on the current platform and is - unlikely to provide the best performance. - - @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) - indicating the default host API index or, a PaErrorCode (which are always - negative) if PortAudio is not initialized or an error is encountered. -*/ -PaHostApiIndex Pa_GetDefaultHostApi( void ); - - -/** Unchanging unique identifiers for each supported host API. This type - is used in the PaHostApiInfo structure. The values are guaranteed to be - unique and to never change, thus allowing code to be written that - conditionally uses host API specific extensions. - - New type ids will be allocated when support for a host API reaches - "public alpha" status, prior to that developers should use the - paInDevelopment type id. - - @see PaHostApiInfo -*/ -typedef enum PaHostApiTypeId -{ - paInDevelopment=0, /* use while developing support for a new host API */ - 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 -} PaHostApiTypeId; - - -/** A structure containing information about a particular host API. */ - -typedef struct PaHostApiInfo -{ - /** this is struct version 1 */ - int structVersion; - /** The well known unique identifier of this host API @see PaHostApiTypeId */ - PaHostApiTypeId type; - /** A textual description of the host API for display on user interfaces. */ - const char *name; - - /** The number of devices belonging to this host API. This field may be - used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate - all devices for this host API. - @see Pa_HostApiDeviceIndexToDeviceIndex - */ - int deviceCount; - - /** The default input device for this host API. The value will be a - device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice - if no default input device is available. - */ - PaDeviceIndex defaultInputDevice; - - /** The default output device for this host API. The value will be a - device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice - if no default output device is available. - */ - PaDeviceIndex defaultOutputDevice; - -} PaHostApiInfo; - - -/** Retrieve a pointer to a structure containing information about a specific - host Api. - - @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) - - @return A pointer to an immutable PaHostApiInfo structure describing - a specific host API. If the hostApi parameter is out of range or an error - is encountered, the function returns NULL. - - The returned structure is owned by the PortAudio implementation and must not - be manipulated or freed. The pointer is only guaranteed to be valid between - calls to Pa_Initialize() and Pa_Terminate(). -*/ -const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); - - -/** Convert a static host API unique identifier, into a runtime - host API index. - - @param type A unique host API identifier belonging to the PaHostApiTypeId - enumeration. - - @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, - a PaErrorCode (which are always negative) if PortAudio is not initialized - or an error is encountered. - - The paHostApiNotFound error code indicates that the host API specified by the - type parameter is not available. - - @see PaHostApiTypeId -*/ -PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); - - -/** Convert a host-API-specific device index to standard PortAudio device index. - This function may be used in conjunction with the deviceCount field of - PaHostApiInfo to enumerate all devices for the specified host API. - - @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) - - @param hostApiDeviceIndex A valid per-host device index in the range - 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) - - @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) - or, a PaErrorCode (which are always negative) if PortAudio is not initialized - or an error is encountered. - - A paInvalidHostApi error code indicates that the host API index specified by - the hostApi parameter is out of range. - - A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter - is out of range. - - @see PaHostApiInfo -*/ -PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, - int hostApiDeviceIndex ); - - - -/** Structure used to return information about a host error condition. -*/ -typedef struct PaHostErrorInfo{ - PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ - long errorCode; /**< the error code returned */ - const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ -}PaHostErrorInfo; - - -/** Return information about the last host error encountered. The error - information returned by Pa_GetLastHostErrorInfo() will never be modified - asynchronously by errors occurring in other PortAudio owned threads - (such as the thread that manages the stream callback.) - - This function is provided as a last resort, primarily to enhance debugging - by providing clients with access to all available error information. - - @return A pointer to an immutable structure constraining information about - the host error. The values in this structure will only be valid if a - PortAudio function has previously returned the paUnanticipatedHostError - error code. -*/ -const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); - - - -/* Device enumeration and capabilities */ - -/** Retrieve the number of available devices. The number of available devices - may be zero. - - @return A non-negative value indicating the number of available devices or, - a PaErrorCode (which are always negative) if PortAudio is not initialized - or an error is encountered. -*/ -PaDeviceIndex Pa_GetDeviceCount( void ); - - -/** Retrieve the index of the default input device. The result can be - used in the inputDevice parameter to Pa_OpenStream(). - - @return The default input device index for the default host API, or paNoDevice - if no default input device is available or an error was encountered. -*/ -PaDeviceIndex Pa_GetDefaultInputDevice( void ); - - -/** Retrieve the index of the default output device. The result can be - used in the outputDevice parameter to Pa_OpenStream(). - - @return The default output device index for the default host API, or paNoDevice - if no default output device is available or an error was encountered. - - @note - On the PC, the user can specify a default device by - setting an environment variable. For example, to use device #1. -
- set PA_RECOMMENDED_OUTPUT_DEVICE=1
-
- The user should first determine the available device ids by using - the supplied application "pa_devs". -*/ -PaDeviceIndex Pa_GetDefaultOutputDevice( void ); - - -/** The type used to represent monotonic time in seconds. PaTime is - used for the fields of the PaStreamCallbackTimeInfo argument to the - PaStreamCallback and as the result of Pa_GetStreamTime(). - - PaTime values have unspecified origin. - - @see PaStreamCallback, PaStreamCallbackTimeInfo, Pa_GetStreamTime -*/ -typedef double PaTime; - - -/** A type used to specify one or more sample formats. Each value indicates - a possible format for sound data passed to and from the stream callback, - Pa_ReadStream and Pa_WriteStream. - - The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 - and aUInt8 are usually implemented by all implementations. - - The floating point representation (paFloat32) uses +1.0 and -1.0 as the - maximum and minimum respectively. - - paUInt8 is an unsigned 8 bit format where 128 is considered "ground" - - The paNonInterleaved flag indicates that audio data is passed as an array - of pointers to separate buffers, one buffer for each channel. Usually, - when this flag is not used, audio data is passed as a single buffer with - all channels interleaved. - - @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo - @see paFloat32, paInt16, paInt32, paInt24, paInt8 - @see paUInt8, paCustomFormat, paNonInterleaved -*/ -typedef unsigned long PaSampleFormat; - - -#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ -#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ -#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ -#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ -#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ -#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ -#define paCustomFormat ((PaSampleFormat) 0x00010000) /**< @see PaSampleFormat */ - -#define paNonInterleaved ((PaSampleFormat) 0x80000000) /**< @see PaSampleFormat */ - -/** A structure providing information and capabilities of PortAudio devices. - Devices may support input, output or both input and output. -*/ -typedef struct PaDeviceInfo -{ - int structVersion; /* this is struct version 2 */ - const char *name; - PaHostApiIndex hostApi; /**< note this is a host API index, not a type id*/ - - int maxInputChannels; - int maxOutputChannels; - - /** Default latency values for interactive performance. */ - PaTime defaultLowInputLatency; - PaTime defaultLowOutputLatency; - /** Default latency values for robust non-interactive applications (eg. playing sound files). */ - PaTime defaultHighInputLatency; - PaTime defaultHighOutputLatency; - - double defaultSampleRate; -} PaDeviceInfo; - - -/** Retrieve a pointer to a PaDeviceInfo structure containing information - about the specified device. - @return A pointer to an immutable PaDeviceInfo structure. If the device - parameter is out of range the function returns NULL. - - @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) - - @note PortAudio manages the memory referenced by the returned pointer, - the client must not manipulate or free the memory. The pointer is only - guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). - - @see PaDeviceInfo, PaDeviceIndex -*/ -const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); - - -/** Parameters for one direction (input or output) of a stream. -*/ -typedef struct PaStreamParameters -{ - /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) - specifying the device to be used or the special constant - paUseHostApiSpecificDeviceSpecification which indicates that the actual - device(s) to use are specified in hostApiSpecificStreamInfo. - This field must not be set to paNoDevice. - */ - PaDeviceIndex device; - - /** The number of channels of sound to be delivered to the - stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). - It can range from 1 to the value of maxInputChannels in the - PaDeviceInfo record for the device specified by the device parameter. - */ - int channelCount; - - /** The sample format of the buffer provided to the stream callback, - a_ReadStream() or Pa_WriteStream(). It may be any of the formats described - by the PaSampleFormat enumeration. - */ - PaSampleFormat sampleFormat; - - /** The desired latency in seconds. Where practical, implementations should - configure their latency based on these parameters, otherwise they may - choose the closest viable latency instead. Unless the suggested latency - is greater than the absolute upper limit for the device implementations - should round the suggestedLatency up to the next practical value - ie to - provide an equal or higher latency than suggestedLatency wherever possible. - Actual latency values for an open stream may be retrieved using the - inputLatency and outputLatency fields of the PaStreamInfo structure - returned by Pa_GetStreamInfo(). - @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo - */ - PaTime suggestedLatency; - - /** An optional pointer to a host api specific data structure - containing additional information for device setup and/or stream processing. - hostApiSpecificStreamInfo is never required for correct operation, - if not used it should be set to NULL. - */ - void *hostApiSpecificStreamInfo; - -} PaStreamParameters; - - -/** Return code for Pa_IsFormatSupported indicating success. */ -#define paFormatIsSupported (0) - -/** Determine whether it would be possible to open a stream with the specified - parameters. - - @param inputParameters A structure that describes the input parameters used to - open a stream. The suggestedLatency field is ignored. See PaStreamParameters - for a description of these parameters. inputParameters must be NULL for - output-only streams. - - @param outputParameters A structure that describes the output parameters used - to open a stream. The suggestedLatency field is ignored. See PaStreamParameters - for a description of these parameters. outputParameters must be NULL for - input-only streams. - - @param sampleRate The required sampleRate. For full-duplex streams it is the - sample rate for both input and output - - @return Returns 0 if the format is supported, and an error code indicating why - the format is not supported otherwise. The constant paFormatIsSupported is - provided to compare with the return value for success. - - @see paFormatIsSupported, PaStreamParameters -*/ -PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate ); - - - -/* Streaming types and functions */ - - -/** - A single PaStream can provide multiple channels of real-time - streaming audio input and output to a client application. A stream - provides access to audio hardware represented by one or more - PaDevices. Depending on the underlying Host API, it may be possible - to open multiple streams using the same device, however this behavior - is implementation defined. Portable applications should assume that - a PaDevice may be simultaneously used by at most one PaStream. - - Pointers to PaStream objects are passed between PortAudio functions that - operate on streams. - - @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, - Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, - Pa_GetStreamTime, Pa_GetStreamCpuLoad - -*/ -typedef void PaStream; - - -/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() - or Pa_OpenDefaultStream() to indicate that the stream callback will - accept buffers of any size. -*/ -#define paFramesPerBufferUnspecified (0) - - -/** Flags used to control the behavior of a stream. They are passed as - parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be - ORed together. - - @see Pa_OpenStream, Pa_OpenDefaultStream - @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, - paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags -*/ -typedef unsigned long PaStreamFlags; - -/** @see PaStreamFlags */ -#define paNoFlag ((PaStreamFlags) 0) - -/** Disable default clipping of out of range samples. - @see PaStreamFlags -*/ -#define paClipOff ((PaStreamFlags) 0x00000001) - -/** Disable default dithering. - @see PaStreamFlags -*/ -#define paDitherOff ((PaStreamFlags) 0x00000002) - -/** Flag requests that where possible a full duplex stream will not discard - overflowed input samples without calling the stream callback. This flag is - only valid for full duplex callback streams and only when used in combination - with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using - this flag incorrectly results in a paInvalidFlag error being returned from - Pa_OpenStream and Pa_OpenDefaultStream. - - @see PaStreamFlags, paFramesPerBufferUnspecified -*/ -#define paNeverDropInput ((PaStreamFlags) 0x00000004) - -/** Call the stream callback to fill initial output buffers, rather than the - default behavior of priming the buffers with zeros (silence). This flag has - no effect for input-only and blocking read/write streams. - - @see PaStreamFlags -*/ -#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) - -/** A mask specifying the platform specific bits. - @see PaStreamFlags -*/ -#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) - -/** - Timing information for the buffers passed to the stream callback. - - Time values are expressed in seconds and are synchronised with the time base used by Pa_GetStreamTime() for the associated stream. - - @see PaStreamCallback, Pa_GetStreamTime -*/ -typedef struct PaStreamCallbackTimeInfo{ - PaTime inputBufferAdcTime; /**< The time when the first sample of the input buffer was captured at the ADC input */ - PaTime currentTime; /**< The time when the stream callback was invoked */ - PaTime outputBufferDacTime; /**< The time when the first sample of the output buffer will output the DAC */ -} PaStreamCallbackTimeInfo; - - -/** - Flag bit constants for the statusFlags to PaStreamCallback. - - @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, - paPrimingOutput -*/ -typedef unsigned long PaStreamCallbackFlags; - -/** In a stream opened with paFramesPerBufferUnspecified, indicates that - input data is all silence (zeros) because no real data is available. In a - stream opened without paFramesPerBufferUnspecified, it indicates that one or - more zero samples have been inserted into the input buffer to compensate - for an input underflow. - @see PaStreamCallbackFlags -*/ -#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) - -/** In a stream opened with paFramesPerBufferUnspecified, indicates that data - prior to the first sample of the input buffer was discarded due to an - overflow, possibly because the stream callback is using too much CPU time. - Otherwise indicates that data prior to one or more samples in the - input buffer was discarded. - @see PaStreamCallbackFlags -*/ -#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) - -/** Indicates that output data (or a gap) was inserted, possibly because the - stream callback is using too much CPU time. - @see PaStreamCallbackFlags -*/ -#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) - -/** Indicates that output data will be discarded because no room is available. - @see PaStreamCallbackFlags -*/ -#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) - -/** Some of all of the output data will be used to prime the stream, input - data may be zero. - @see PaStreamCallbackFlags -*/ -#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) - -/** - Allowable return values for the PaStreamCallback. - @see PaStreamCallback -*/ -typedef enum PaStreamCallbackResult -{ - paContinue=0, /**< Signal that the stream should continue invoking the callback and processing audio. */ - paComplete=1, /**< Signal that the stream should stop invoking the callback and finish once all output samples have played. */ - paAbort=2 /**< Signal that the stream should stop invoking the callback and finish as soon as possible. */ -} PaStreamCallbackResult; - - -/** - Functions of type PaStreamCallback are implemented by PortAudio clients. - They consume, process or generate audio in response to requests from an - active PortAudio stream. - - When a stream is running, PortAudio calls the stream callback periodically. - The callback function is responsible for processing buffers of audio samples - passed via the input and output parameters. - - The PortAudio stream callback runs at very high or real-time priority. - It is required to consistently meet its time deadlines. Do not allocate - memory, access the file system, call library functions or call other functions - from the stream callback that may block or take an unpredictable amount of - time to complete. - - In order for a stream to maintain glitch-free operation the callback - must consume and return audio data faster than it is recorded and/or - played. PortAudio anticipates that each callback invocation may execute for - a duration approaching the duration of frameCount audio frames at the stream - sample rate. It is reasonable to expect to be able to utilise 70% or more of - the available CPU time in the PortAudio callback. However, due to buffer size - adaption and other factors, not all host APIs are able to guarantee audio - stability under heavy CPU load with arbitrary fixed callback buffer sizes. - When high callback CPU utilisation is required the most robust behavior - can be achieved by using paFramesPerBufferUnspecified as the - Pa_OpenStream() framesPerBuffer parameter. - - @param input and @param output are either arrays of interleaved samples or; - if non-interleaved samples were requested using the paNonInterleaved sample - format flag, an array of buffer pointers, one non-interleaved buffer for - each channel. - - The format, packing and number of channels used by the buffers are - determined by parameters to Pa_OpenStream(). - - @param frameCount The number of sample frames to be processed by - the stream callback. - - @param timeInfo Timestamps indicating the ADC capture time of the first sample - in the input buffer, the DAC output time of the first sample in the output buffer - and the time the callback was invoked. - See PaStreamCallbackTimeInfo and Pa_GetStreamTime() - - @param statusFlags Flags indicating whether input and/or output buffers - have been inserted or will be dropped to overcome underflow or overflow - conditions. - - @param userData The value of a user supplied pointer passed to - Pa_OpenStream() intended for storing synthesis data etc. - - @return - The stream callback should return one of the values in the - ::PaStreamCallbackResult enumeration. To ensure that the callback continues - to be called, it should return paContinue (0). Either paComplete or paAbort - can be returned to finish stream processing, after either of these values is - returned the callback will not be called again. If paAbort is returned the - stream will finish as soon as possible. If paComplete is returned, the stream - will continue until all buffers generated by the callback have been played. - This may be useful in applications such as soundfile players where a specific - duration of output is required. However, it is not necessary to utilize this - mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also - be used to stop the stream. The callback must always fill the entire output - buffer irrespective of its return value. - - @see Pa_OpenStream, Pa_OpenDefaultStream - - @note With the exception of Pa_GetStreamCpuLoad() it is not permissible to call - PortAudio API functions from within the stream callback. -*/ -typedef int PaStreamCallback( - const void *input, void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData ); - - -/** Opens a stream for either input, output or both. - - @param stream The address of a PaStream pointer which will receive - a pointer to the newly opened stream. - - @param inputParameters A structure that describes the input parameters used by - the opened stream. See PaStreamParameters for a description of these parameters. - inputParameters must be NULL for output-only streams. - - @param outputParameters A structure that describes the output parameters used by - the opened stream. See PaStreamParameters for a description of these parameters. - outputParameters must be NULL for input-only streams. - - @param sampleRate The desired sampleRate. For full-duplex streams it is the - sample rate for both input and output - - @param framesPerBuffer The number of frames passed to the stream callback - function, or the preferred block granularity for a blocking read/write stream. - The special value paFramesPerBufferUnspecified (0) may be used to request that - the stream callback will receive an optimal (and possibly varying) number of - frames based on host requirements and the requested latency settings. - Note: With some host APIs, the use of non-zero framesPerBuffer for a callback - stream may introduce an additional layer of buffering which could introduce - additional latency. PortAudio guarantees that the additional latency - will be kept to the theoretical minimum however, it is strongly recommended - that a non-zero framesPerBuffer value only be used when your algorithm - requires a fixed number of frames per stream callback. - - @param streamFlags Flags which modify the behavior of the streaming process. - This parameter may contain a combination of flags ORed together. Some flags may - only be relevant to certain buffer formats. - - @param streamCallback A pointer to a client supplied function that is responsible - for processing and filling input and output buffers. If this parameter is NULL - the stream will be opened in 'blocking read/write' mode. In blocking mode, - the client can receive sample data using Pa_ReadStream and write sample data - using Pa_WriteStream, the number of samples that may be read or written - without blocking is returned by Pa_GetStreamReadAvailable and - Pa_GetStreamWriteAvailable respectively. - - @param userData A client supplied pointer which is passed to the stream callback - function. It could for example, contain a pointer to instance data necessary - for processing the audio buffers. This parameter is ignored if streamCallback - is NULL. - - @return - Upon success Pa_OpenStream() returns paNoError and places a pointer to a - valid PaStream in the stream argument. The stream is inactive (stopped). - If a call to Pa_OpenStream() fails, a non-zero error code is returned (see - PaError for possible error codes) and the value of stream is invalid. - - @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, - Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable -*/ -PaError Pa_OpenStream( PaStream** stream, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate, - unsigned long framesPerBuffer, - PaStreamFlags streamFlags, - PaStreamCallback *streamCallback, - void *userData ); - - -/** A simplified version of Pa_OpenStream() that opens the default input - and/or output devices. - - @param stream The address of a PaStream pointer which will receive - a pointer to the newly opened stream. - - @param numInputChannels The number of channels of sound that will be supplied - to the stream callback or returned by Pa_ReadStream. It can range from 1 to - the value of maxInputChannels in the PaDeviceInfo record for the default input - device. If 0 the stream is opened as an output-only stream. - - @param numOutputChannels The number of channels of sound to be delivered to the - stream callback or passed to Pa_WriteStream. It can range from 1 to the value - of maxOutputChannels in the PaDeviceInfo record for the default output device. - If 0 the stream is opened as an output-only stream. - - @param sampleFormat The sample format of both the input and output buffers - provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. - sampleFormat may be any of the formats described by the PaSampleFormat - enumeration. - - @param sampleRate Same as Pa_OpenStream parameter of the same name. - @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. - @param streamCallback Same as Pa_OpenStream parameter of the same name. - @param userData Same as Pa_OpenStream parameter of the same name. - - @return As for Pa_OpenStream - - @see Pa_OpenStream, PaStreamCallback -*/ -PaError Pa_OpenDefaultStream( PaStream** stream, - int numInputChannels, - int numOutputChannels, - PaSampleFormat sampleFormat, - double sampleRate, - unsigned long framesPerBuffer, - PaStreamCallback *streamCallback, - void *userData ); - - -/** Closes an audio stream. If the audio stream is active it - discards any pending buffers as if Pa_AbortStream() had been called. -*/ -PaError Pa_CloseStream( PaStream *stream ); - - -/** Functions of type PaStreamFinishedCallback are implemented by PortAudio - clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback - function. Once registered they are called when the stream becomes inactive - (ie once a call to Pa_StopStream() will not block). - A stream will become inactive after the stream callback returns non-zero, - or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio - output, if the stream callback returns paComplete, or Pa_StopStream() is called, - the stream finished callback will not be called until all generated sample data - has been played. - - @param userData The userData parameter supplied to Pa_OpenStream() - - @see Pa_SetStreamFinishedCallback -*/ -typedef void PaStreamFinishedCallback( void *userData ); - - -/** Register a stream finished callback function which will be called when the - stream becomes inactive. See the description of PaStreamFinishedCallback for - further details about when the callback will be called. - - @param stream a pointer to a PaStream that is in the stopped state - if the - stream is not stopped, the stream's finished callback will remain unchanged - and an error code will be returned. - - @param streamFinishedCallback a pointer to a function with the same signature - as PaStreamFinishedCallback, that will be called when the stream becomes - inactive. Passing NULL for this parameter will un-register a previously - registered stream finished callback function. - - @return on success returns paNoError, otherwise an error code indicating the cause - of the error. - - @see PaStreamFinishedCallback -*/ -PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); - - -/** Commences audio processing. -*/ -PaError Pa_StartStream( PaStream *stream ); - - -/** Terminates audio processing. It waits until all pending - audio buffers have been played before it returns. -*/ -PaError Pa_StopStream( PaStream *stream ); - - -/** Terminates audio processing immediately without waiting for pending - buffers to complete. -*/ -PaError Pa_AbortStream( PaStream *stream ); - - -/** Determine whether the stream is stopped. - A stream is considered to be stopped prior to a successful call to - Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. - If a stream callback returns a value other than paContinue the stream is NOT - considered to be stopped. - - @return Returns one (1) when the stream is stopped, zero (0) when - the stream is running or, a PaErrorCode (which are always negative) if - PortAudio is not initialized or an error is encountered. - - @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive -*/ -PaError Pa_IsStreamStopped( PaStream *stream ); - - -/** Determine whether the stream is active. - A stream is active after a successful call to Pa_StartStream(), until it - becomes inactive either as a result of a call to Pa_StopStream() or - Pa_AbortStream(), or as a result of a return value other than paContinue from - the stream callback. In the latter case, the stream is considered inactive - after the last buffer has finished playing. - - @return Returns one (1) when the stream is active (ie playing or recording - audio), zero (0) when not playing or, a PaErrorCode (which are always negative) - if PortAudio is not initialized or an error is encountered. - - @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped -*/ -PaError Pa_IsStreamActive( PaStream *stream ); - - - -/** A structure containing unchanging information about an open stream. - @see Pa_GetStreamInfo -*/ - -typedef struct PaStreamInfo -{ - /** this is struct version 1 */ - int structVersion; - - /** The input latency of the stream in seconds. This value provides the most - accurate estimate of input latency available to the implementation. It may - differ significantly from the suggestedLatency value passed to Pa_OpenStream(). - The value of this field will be zero (0.) for output-only streams. - @see PaTime - */ - PaTime inputLatency; - - /** The output latency of the stream in seconds. This value provides the most - accurate estimate of output latency available to the implementation. It may - differ significantly from the suggestedLatency value passed to Pa_OpenStream(). - The value of this field will be zero (0.) for input-only streams. - @see PaTime - */ - PaTime outputLatency; - - /** The sample rate of the stream in Hertz (samples per second). In cases - where the hardware sample rate is inaccurate and PortAudio is aware of it, - the value of this field may be different from the sampleRate parameter - passed to Pa_OpenStream(). If information about the actual hardware sample - rate is not available, this field will have the same value as the sampleRate - parameter passed to Pa_OpenStream(). - */ - double sampleRate; - -} PaStreamInfo; - - -/** Retrieve a pointer to a PaStreamInfo structure containing information - about the specified stream. - @return A pointer to an immutable PaStreamInfo structure. If the stream - parameter is invalid, or an error is encountered, the function returns NULL. - - @param stream A pointer to an open stream previously created with Pa_OpenStream. - - @note PortAudio manages the memory referenced by the returned pointer, - the client must not manipulate or free the memory. The pointer is only - guaranteed to be valid until the specified stream is closed. - - @see PaStreamInfo -*/ -const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); - - -/** Returns the current time in seconds for a stream according to the same clock used - to generate callback PaStreamCallbackTimeInfo timestamps. The time values are - monotonically increasing and have unspecified origin. - - Pa_GetStreamTime returns valid time values for the entire life of the stream, - from when the stream is opened until it is closed. Starting and stopping the stream - does not affect the passage of time returned by Pa_GetStreamTime. - - This time may be used for synchronizing other events to the audio stream, for - example synchronizing audio to MIDI. - - @return The stream's current time in seconds, or 0 if an error occurred. - - @see PaTime, PaStreamCallback, PaStreamCallbackTimeInfo -*/ -PaTime Pa_GetStreamTime( PaStream *stream ); - - -/** Retrieve CPU usage information for the specified stream. - The "CPU Load" is a fraction of total CPU time consumed by a callback stream's - audio processing routines including, but not limited to the client supplied - stream callback. This function does not work with blocking read/write streams. - - This function may be called from the stream callback function or the - application. - - @return - A floating point value, typically between 0.0 and 1.0, where 1.0 indicates - that the stream callback is consuming the maximum number of CPU cycles possible - to maintain real-time operation. A value of 0.5 would imply that PortAudio and - the stream callback was consuming roughly 50% of the available CPU time. The - return value may exceed 1.0. A value of 0.0 will always be returned for a - blocking read/write stream, or if an error occurs. -*/ -double Pa_GetStreamCpuLoad( PaStream* stream ); - - -/** Read samples from an input stream. The function doesn't return until - the entire buffer has been filled - this may involve waiting for the operating - system to supply the data. - - @param stream A pointer to an open stream previously created with Pa_OpenStream. - - @param buffer A pointer to a buffer of sample frames. The buffer contains - samples in the format specified by the inputParameters->sampleFormat field - used to open the stream, and the number of channels specified by - inputParameters->numChannels. If non-interleaved samples were requested using - the paNonInterleaved sample format flag, buffer is a pointer to the first element - of an array of buffer pointers, one non-interleaved buffer for each channel. - - @param frames The number of frames to be read into buffer. This parameter - is not constrained to a specific range, however high performance applications - will want to match this parameter to the framesPerBuffer parameter used - when opening the stream. - - @return On success PaNoError will be returned, or PaInputOverflowed if input - data was discarded by PortAudio after the previous call and before this call. -*/ -PaError Pa_ReadStream( PaStream* stream, - void *buffer, - unsigned long frames ); - - -/** Write samples to an output stream. This function doesn't return until the - entire buffer has been written - this may involve waiting for the operating - system to consume the data. - - @param stream A pointer to an open stream previously created with Pa_OpenStream. - - @param buffer A pointer to a buffer of sample frames. The buffer contains - samples in the format specified by the outputParameters->sampleFormat field - used to open the stream, and the number of channels specified by - outputParameters->numChannels. If non-interleaved samples were requested using - the paNonInterleaved sample format flag, buffer is a pointer to the first element - of an array of buffer pointers, one non-interleaved buffer for each channel. - - @param frames The number of frames to be written from buffer. This parameter - is not constrained to a specific range, however high performance applications - will want to match this parameter to the framesPerBuffer parameter used - when opening the stream. - - @return On success PaNoError will be returned, or paOutputUnderflowed if - additional output data was inserted after the previous call and before this - call. -*/ -PaError Pa_WriteStream( PaStream* stream, - const void *buffer, - unsigned long frames ); - - -/** Retrieve the number of frames that can be read from the stream without - waiting. - - @return Returns a non-negative value representing the maximum number of frames - that can be read from the stream without blocking or busy waiting or, a - PaErrorCode (which are always negative) if PortAudio is not initialized or an - error is encountered. -*/ -signed long Pa_GetStreamReadAvailable( PaStream* stream ); - - -/** Retrieve the number of frames that can be written to the stream without - waiting. - - @return Returns a non-negative value representing the maximum number of frames - that can be written to the stream without blocking or busy waiting or, a - PaErrorCode (which are always negative) if PortAudio is not initialized or an - error is encountered. -*/ -signed long Pa_GetStreamWriteAvailable( PaStream* stream ); - - -/* Miscellaneous utilities */ - - -/** Retrieve the size of a given sample format in bytes. - - @return The size in bytes of a single sample in the specified format, - or paSampleFormatNotSupported if the format is not supported. -*/ -PaError Pa_GetSampleSize( PaSampleFormat format ); - - -/** Put the caller to sleep for at least 'msec' milliseconds. This function is - provided only as a convenience for authors of portable code (such as the tests - and examples in the PortAudio distribution.) - - The function may sleep longer than requested so don't rely on this for accurate - musical timing. -*/ -void Pa_Sleep( long msec ); - - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* PORTAUDIO_H */ diff --git a/deps/usr/lib/pa_shim_arm-linux-gnueabihf.so b/deps/usr/lib/pa_shim_arm-linux-gnueabihf.so deleted file mode 100755 index fc997729717e619fcb4fce3deabb7ee690b9b04a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6376 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 diff --git a/deps/usr/lib/pa_shim_i686-w64-mingw32.dll b/deps/usr/lib/pa_shim_i686-w64-mingw32.dll deleted file mode 100755 index 956032baf7284bb152631a84c6d37aaafd2140c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81524 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 C&;dFC diff --git a/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll b/deps/usr/lib/pa_shim_x86_64-w64-mingw32.dll deleted file mode 100755 index 1a39c4434e26670c415dba8639f3b4cec8b4eb5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110424 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 B`&j@0 diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 65b706b..f00a902 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1,9 +1,6 @@ -__precompile__(true) - module PortAudio -using SampledSignals -using RingBuffers +using libportaudio_jll, Libdl, SampledSignals, RingBuffers #= using Compat using Compat: undef, fetch, @compat @@ -23,7 +20,6 @@ export PortAudioStream # Get binary dependencies loaded from BinDeps -include("../deps/deps.jl") include("pa_shim.jl") include("libportaudio.jl") @@ -397,8 +393,6 @@ function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameo end -const libpa_shim = find_pa_shim() - """ PortAudio.shimhash() diff --git a/src/pa_shim.jl b/src/pa_shim.jl index 4ddc7c0..1e7c916 100644 --- a/src/pa_shim.jl +++ b/src/pa_shim.jl @@ -1,31 +1,3 @@ -function find_pa_shim() - libdir = joinpath(@__DIR__, "..", "deps", "usr", "lib") - libsuffix = "" - basename = "pa_shim" - @static if Sys.islinux() && Sys.ARCH == :x86_64 - libsuffix = "x86_64-linux-gnu" - elseif Sys.islinux() && Sys.ARCH == :i686 - libsuffix = "i686-linux-gnu" - elseif Sys.isapple() && Sys.ARCH == :x86_64 - libsuffix = "x86_64-apple-darwin14" - elseif Sys.iswindows() && Sys.ARCH == :x86_64 - libsuffix = "x86_64-w64-mingw32" - elseif Sys.iswindows() && Sys.ARCH == :i686 - libsuffix = "i686-w64-mingw32" - elseif !any( - (sfx) -> isfile(joinpath(libdir, "$basename.$sfx")), - ("so", "dll", "dylib")) - error("Unsupported platform $(Sys.MACHINE). You can build your own library by running `make` from $(joinpath(@__FILE__, "..", "deps", "src"))") - end - # if there's a suffix-less library, it was built natively on this machine, - # so load that one first, otherwise load the pre-built one - libpa_shim = Libdl.find_library( - [basename, "$(basename)_$libsuffix"], - [libdir]) - libpa_shim == "" && error("Could not load $basename library, please file an issue at https://github.com/JuliaAudio/RingBuffers.jl/issues with your `versioninfo()` output") - return libpa_shim -end - const pa_shim_errmsg_t = Cint const PA_SHIM_ERRMSG_OVERFLOW = Cint(0) # input overflow const PA_SHIM_ERRMSG_UNDERFLOW = Cint(1) # output underflow diff --git a/test/runtests.jl b/test/runtests.jl index d3ea489..3987f10 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -202,7 +202,7 @@ end end @testset "using correct shim version" begin - @test PortAudio.shimhash() == "87021557a9f999545828eb11e4ebad2cd278b734dd91a8bd3faf05c89912cf80" + @test PortAudio.shimhash() == "7570f145b3c1b5a6f0733378a45807c5fd47e30f04771e4eaa2c380d80dcd15d" end @testset "Basic callback functionality" begin From 4c2ad4dc06b0e951f43d0be87b1f2fcea08c652f Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Wed, 1 Jan 2020 14:59:46 -0500 Subject: [PATCH 24/31] some more update-related tweaks --- .appveyor.yml | 31 +++++++++++++++++++++++++++++++ .travis.yml | 19 +++---------------- README.md | 2 -- appveyor.yml | 38 -------------------------------------- src/PortAudio.jl | 7 ------- 5 files changed, 34 insertions(+), 63 deletions(-) create mode 100644 .appveyor.yml delete mode 100644 appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..d1fd5c9 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,31 @@ +# Documentation: https://github.com/JuliaCI/Appveyor.jl +environment: + matrix: + - julia_version: 1 + - julia_version: nightly +platform: + - x86 + - x64 +matrix: + allow_failures: + - julia_version: nightly +branches: + only: + - master + - /release-.*/ +notifications: + - provider: Email + on_build_success: false + on_build_failure: false + on_build_status_changed: true +install: + - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) +build_script: + - echo "%JL_BUILD_SCRIPT%" + - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" +test_script: + - echo "%JL_TEST_SCRIPT%" + - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" +on_success: + - echo "%JL_CODECOV_SCRIPT%" + - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" diff --git a/.travis.yml b/.travis.yml index a967b45..6025d00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,27 +3,14 @@ language: julia os: - linux - osx -sudo: required julia: - - 1.0 - - 1.2 + - 1 - nightly matrix: allow_failures: - julia: nightly fast_finish: true -branches: - only: - master notifications: - email: false -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia --color=yes ci_setup.jl - - julia --color=yes --code-coverage test/runtests.jl + email: true after_success: - - julia -e 'VERSION >= v"0.7.0-" && using Pkg; - VERSION < v"0.7.0-" && cd(Pkg.dir("PortAudio")); - Pkg.add("Coverage"); - using Coverage; - Codecov.submit(process_folder())' + - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' diff --git a/README.md b/README.md index 467beef..7d9ee9b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ PortAudio.jl [![Build Status](https://travis-ci.org/JuliaAudio/PortAudio.jl.svg?branch=master)](https://travis-ci.org/JuliaAudio/PortAudio.jl) [![Build status](https://ci.appveyor.com/api/projects/status/6x1ha7uvrnel060g/branch/master?svg=true)](https://ci.appveyor.com/project/ssfrr/portaudio-jl/branch/master) -**NOTE: PortAudio.jl master currently requires both SampledSignals and RingBuffers to be on master as well** - PortAudio.jl is a wrapper for [libportaudio](http://www.portaudio.com/), which gives cross-platform access to audio devices. It is compatible with the types defined in [SampledSignals.jl](https://github.com/JuliaAudio/SampledSignals.jl). It provides a `PortAudioStream` type, which can be read from and written to. ## Opening a stream diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index f7717b9..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,38 +0,0 @@ -environment: - matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/1.0/julia-1.0-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0-latest-win64.exe" - -matrix: - allow_failures: - # currently failing on 1.0 until https://github.com/JuliaLang/METADATA.jl/pull/16370 is merged - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/1.0/julia-1.0-latest-win64.exe" - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -# only build master and PRs -branches: - only: - - master - -install: - - ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12" -# Download most recent Julia Windows binary - - ps: (new-object net.webclient).DownloadFile( - $env:JULIA_URL, - "C:\projects\julia-binary.exe") -# Run installer silently, output to C:\projects\julia - - C:\projects\julia-binary.exe /S /D=C:\projects\julia - -build_script: -# Need to convert from shallow to complete for Pkg.clone to work - - IF EXIST .git\shallow (git fetch --unshallow) - - C:\projects\julia\bin\julia --color=yes ci_setup.jl - -test_script: - # - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"SampledSignals\")" - - C:\projects\julia\bin\julia --color=yes --code-coverage --check-bounds=yes test/runtests.jl diff --git a/src/PortAudio.jl b/src/PortAudio.jl index f00a902..17abe05 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1,13 +1,6 @@ module PortAudio using libportaudio_jll, Libdl, SampledSignals, RingBuffers -#= -using Compat -using Compat: undef, fetch, @compat -using Compat.LinearAlgebra: transpose! -using Compat: stdout -using Compat.Sys: iswindows -=# import Base: eltype, show import Base: close, isopen From 9eb565e487f2fe78ccd00ef41406e9cb1611ce31 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 2 Jan 2020 00:02:00 -0500 Subject: [PATCH 25/31] mostly working, but crashes sometimes --- Project.toml | 2 - src/PortAudio.jl | 222 +++++++++----------------------------------- src/libportaudio.jl | 43 +++++++-- src/pa_shim.jl | 18 ---- 4 files changed, 77 insertions(+), 208 deletions(-) delete mode 100644 src/pa_shim.jl diff --git a/Project.toml b/Project.toml index 34dab79..c70700c 100644 --- a/Project.toml +++ b/Project.toml @@ -6,12 +6,10 @@ version = "1.1.0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -RingBuffers = "f6d8bcc6-4e01-5431-93c4-9d6004abab90" SampledSignals = "bd7594eb-a658-542f-9e75-4c4d8908c167" libportaudio_jll = "2d7b7beb-0762-5160-978e-1ab83a1e8a31" [compat] -RingBuffers = "1.2" julia = "1.3" [extras] diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 17abe05..bb280d8 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1,6 +1,6 @@ module PortAudio -using libportaudio_jll, Libdl, SampledSignals, RingBuffers +using libportaudio_jll, SampledSignals import Base: eltype, show import Base: close, isopen @@ -11,30 +11,20 @@ import LinearAlgebra: transpose! export PortAudioStream - -# Get binary dependencies loaded from BinDeps -include("pa_shim.jl") include("libportaudio.jl") # These sizes are all in frames # the block size is what we request from portaudio if no blocksize is given. -# The ringbuffer and pre-fill will be twice the blocksize const DEFAULT_BLOCKSIZE=4096 -# data is passed to and from the ringbuffer in chunks with this many frames -# it should be at most the ringbuffer size, and must evenly divide into the -# the underlying portaudio buffer size. E.g. if PortAudio is running with a -# 2048-frame buffer period, the chunk size can be 2048, 1024, 512, 256, etc. +# data is passed to and from portaudio in chunks with this many frames, because +# we need to interleave the samples const CHUNKSIZE=128 -# ringbuffer to receive errors from the audio processing thread -const ERR_BUFSIZE=512 - function versioninfo(io::IO=stdout) println(io, Pa_GetVersionText()) println(io, "Version: ", Pa_GetVersion()) - println(io, "Shim Source Hash: ", shimhash()[1:10]) end mutable struct PortAudioDevice @@ -74,14 +64,11 @@ mutable struct PortAudioStream{T} warn_xruns::Bool sink # untyped because of circular type definition source # untyped because of circular type definition - errbuf::RingBuffer{pa_shim_errmsg_t} # used to send errors from the portaudio callback - errtask::Task - bufinfo::pa_shim_info_t # data used in the portaudio callback # this inner constructor is generally called via the top-level outer # constructor below function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans, outchans, sr, blocksize, synced, + inchans, outchans, sr, blocksize, warn_xruns) where {T} inchans = inchans == -1 ? indev.maxinchans : inchans outchans = outchans == -1 ? outdev.maxoutchans : outchans @@ -92,32 +79,15 @@ mutable struct PortAudioStream{T} Ptr{Pa_StreamParameters}(0) : Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL)) this = new(sr, blocksize, C_NULL, warn_xruns) - finalizer(close, this) - this.sink = PortAudioSink{T}(outdev.name, this, outchans, blocksize*2) - this.source = PortAudioSource{T}(indev.name, this, inchans, blocksize*2) - this.errbuf = RingBuffer{pa_shim_errmsg_t}(1, ERR_BUFSIZE) - if synced && inchans > 0 && outchans > 0 - # we've got a synchronized duplex stream. initialize with the output buffer full - write(this.sink, SampleBuf(zeros(T, blocksize*2, outchans), sr)) - end - # pass NULL for input/output we're not using - this.bufinfo = pa_shim_info_t( - inchans > 0 ? bufpointer(this.source) : C_NULL, - outchans > 0 ? bufpointer(this.sink) : C_NULL, - pointer(this.errbuf), - synced, notifycb_c, - inchans > 0 ? notifyhandle(this.source) : C_NULL, - outchans > 0 ? notifyhandle(this.sink) : C_NULL, - notifyhandle(this.errbuf), - global_cond[].handle) # this is only needed for the libuv workaround + # finalizer(close, this) + this.sink = PortAudioSink{T}(outdev.name, this, outchans) + this.source = PortAudioSource{T}(indev.name, this, inchans) this.stream = suppress_err() do - Pa_OpenStream(inparams, outparams, float(sr), blocksize, paNoFlag, - shim_processcb_c, this.bufinfo) + Pa_OpenStream(inparams, outparams, sr, blocksize, paNoFlag, + nothing, nothing) end Pa_StartStream(this.stream) - this.errtask = @async handle_errors(this) - push!(active_streams, this) this end @@ -141,11 +111,6 @@ Options: * `samplerate`: Sample rate (defaults to device sample rate) * `blocksize`: Size of the blocks that are written to and read from the audio device. (Defaults to $DEFAULT_BLOCKSIZE) -* `synced`: Determines whether the input and output streams are kept in - sync. If `true`, you must read and write an equal number of - frames, and the round-trip latency is guaranteed constant. If - `false`, you are free to read and write separately, but - overflow or underflow can affect the round-trip latency. * `warn_xruns`: Display a warning if there is a stream overrun or underrun, which often happens when Julia is compiling, or with a particularly large GC run. This can be quite verbose so is @@ -153,7 +118,7 @@ Options: """ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, inchans=2, outchans=2; eltype=Float32, samplerate=-1, blocksize=DEFAULT_BLOCKSIZE, - synced=false, warn_xruns=false) + warn_xruns=false) if samplerate == -1 sampleratein = indev.defaultsamplerate samplerateout = outdev.defaultsamplerate @@ -168,7 +133,7 @@ function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, samplerate = samplerateout end end - PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, synced, warn_xruns) + PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, blocksize, warn_xruns) end # handle device names given as streams @@ -212,9 +177,6 @@ function PortAudioStream(inchans=2, outchans=2; kwargs...) end # handle do-syntax -# TODO: there seems to be a buffering issue here. Running this multiple -# times creates weird echos, and even the first time there might be something -# fishy going on. Needs investigation function PortAudioStream(fn::Function, args...; kwargs...) str = PortAudioStream(args...; kwargs...) try @@ -224,39 +186,11 @@ function PortAudioStream(fn::Function, args...; kwargs...) end end -const pa_inited = Ref(false) -const active_streams = Set{PortAudioStream}() - -function notify_active_streams() - # errors here can cause the system to hang if they're waiting on these - # conditions, so we do our own exception display for easier debugging - try - while true - wait(global_cond[]) - pa_inited[] || break - - for stream in active_streams - notify(stream.source.ringbuf.datanotify.cond) - notify(stream.sink.ringbuf.datanotify.cond) - notify(stream.errbuf.datanotify.cond) - end - end - catch ex - showerror(stderr, ex, backtrace()) - end -end - function close(stream::PortAudioStream) if stream.stream != C_NULL Pa_StopStream(stream.stream) Pa_CloseStream(stream.stream) - close(stream.source) - close(stream.sink) - close(stream.errbuf) stream.stream = C_NULL - # wait for the error task to clean up - fetch(stream.errtask) - delete!(active_streams, stream) end nothing @@ -285,32 +219,6 @@ function show(io::IO, stream::PortAudioStream) end end -""" - handle_errors(stream::PortAudioStream) - -Handle errors coming over the error stream from PortAudio. This is run as an -independent task while the stream is active. -""" -function handle_errors(stream::PortAudioStream) - err = Vector{pa_shim_errmsg_t}(undef, 1) - while true - nread = read!(stream.errbuf, err) - nread == 1 || break - if err[1] == PA_SHIM_ERRMSG_ERR_OVERFLOW - @warn "Error buffer overflowed on portaudio stream" - elseif err[1] == PA_SHIM_ERRMSG_OVERFLOW - stream.warn_xruns && @warn "Input overflowed from $(name(stream.source))" - elseif err[1] == PA_SHIM_ERRMSG_UNDERFLOW - stream.warn_xruns && @warn "Output underflowed to $(name(stream.sink))" - else - error(""" - Got unrecognized error code $(err[1]) from audio thread for - portaudio stream. Please file an issue at - https://github.com/juliaaudio/portaudio.jl/issues""") - end - end -end - ################################## # PortAudioSink & PortAudioSource ################################## @@ -322,15 +230,13 @@ for (TypeName, Super) in ((:PortAudioSink, :SampleSink), name::String stream::PortAudioStream{T} chunkbuf::Array{T, 2} - ringbuf::RingBuffer{T} nchannels::Int - function $TypeName{T}(name, stream, channels, ringbufsize) where {T} + 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, CHUNKSIZE) - ringbuf = RingBuffer{T}(channels, ringbufsize) - new(name, stream, chunkbuf, ringbuf, channels) + new(name, stream, chunkbuf, channels) end end end @@ -339,30 +245,36 @@ SampledSignals.nchannels(s::Union{PortAudioSink, PortAudioSource}) = s.nchannels SampledSignals.samplerate(s::Union{PortAudioSink, PortAudioSource}) = samplerate(s.stream) SampledSignals.blocksize(s::Union{PortAudioSink, PortAudioSource}) = s.stream.blocksize eltype(::Union{PortAudioSink{T}, PortAudioSource{T}}) where {T} = T -close(s::Union{PortAudioSink, PortAudioSource}) = close(s.ringbuf) -isopen(s::Union{PortAudioSink, PortAudioSource}) = isopen(s.ringbuf) -RingBuffers.notifyhandle(s::Union{PortAudioSink, PortAudioSource}) = notifyhandle(s.ringbuf) -bufpointer(s::Union{PortAudioSink, PortAudioSource}) = pointer(s.ringbuf) +function close(s::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::Union{PortAudioSink, PortAudioSource}) = s.name -function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} - println(io, T, "(\"", stream.name, "\")") - print(io, nchannels(stream), " channels") +function show(io::IO, ::Type{PortAudioSink{T}}) where T + print(io, "PortAudioSink{$T}") end -flush(sink::PortAudioSink) = flush(sink.ringbuf) +function show(io::IO, ::Type{PortAudioSource{T}}) where T + print(io, "PortAudioSource{$T}") +end + +function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSource}} + print(io, nchannels(stream), "-channel ", T, "(\"", stream.name, "\")") +end function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount) nwritten = 0 while nwritten < framecount - towrite = min(framecount-nwritten, CHUNKSIZE) + n = min(framecount-nwritten, CHUNKSIZE) # make a buffer of interleaved samples - transpose!(view(sink.chunkbuf, :, 1:towrite), - view(buf, (1:towrite) .+ nwritten .+ frameoffset, :)) - n = write(sink.ringbuf, sink.chunkbuf, towrite) + transpose!(view(sink.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 + Pa_WriteStream(sink.stream.stream, sink.chunkbuf, n, sink.stream.warn_xruns) nwritten += n - # break early if the stream is closed - n < towrite && break end nwritten @@ -371,82 +283,36 @@ end function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameoffset, framecount) nread = 0 while nread < framecount - toread = min(framecount-nread, CHUNKSIZE) - n = read!(source.ringbuf, source.chunkbuf, toread) + n = min(framecount-nread, CHUNKSIZE) + # TODO: if the stream is closed we just want to return a + # shorter-than-requested frame count instead of throwing an error + Pa_ReadStream(source.stream.stream, source.chunkbuf, n, source.stream.warn_xruns) # de-interleave the samples - transpose!(view(buf, (1:toread) .+ nread .+ frameoffset, :), - view(source.chunkbuf, :, 1:toread)) + transpose!(view(buf, (1:n) .+ nread .+ frameoffset, :), + view(source.chunkbuf, :, 1:n)) - nread += toread - # break early if the stream is closed - n < toread && break + nread += n end nread end - -""" - PortAudio.shimhash() - -Return the sha256 hash(as a string) of the source file used to build the shim. -We may use this sometime to verify that the distributed binary stays in sync -with the rest of the package. -""" -shimhash() = unsafe_string(ccall((:pa_shim_getsourcehash, libpa_shim), Cstring, ())) - - -# this is called by the shim process callback to notify that there is new data. -# it's run in the audio context so don't do anything besides wake up the -# AsyncCondition handle associated with that ring buffer -notifycb(handle) = ccall(:uv_async_send, Cint, (Ref{Cvoid},), handle) - -global shim_processcb_c, notifycb_c - -function set_global_callbacks() - shim_dlib = Libdl.dlopen(libpa_shim) - - # pointer to the shim's process callback - global shim_processcb_c = Libdl.dlsym(shim_dlib, :pa_shim_processcb) - if shim_processcb_c == C_NULL - error("Got NULL pointer loading `pa_shim_processcb`") - end - - global notifycb_c = @cfunction notifycb Cint (Ptr{Cvoid},) -end - function suppress_err(dofunc::Function) nullfile = @static Sys.iswindows() ? "nul" : "/dev/null" open(nullfile, "w") do io - redirect_stdout(dofunc, io) + redirect_stderr(dofunc, io) end end -# this ref has to be set during __init__ to register itself properly with libuv -const global_cond = Ref{Base.AsyncCondition}() function __init__() - # currently libuv has issues when you try to notify more than one condition - # (see https://github.com/libuv/libuv/issues/1951). So as a workaround we use - # a global AsyncCondition that gets notified from the audio callback, and it - # handles notifying the individual RingBuffer AsyncConditions. - global_cond[] = Base.AsyncCondition() - set_global_callbacks() - + ENV["ALSA_CONFIG_DIR"] = "/usr/share/alsa" # initialize PortAudio on module load - suppress_err() do + # suppress_err() do Pa_Initialize() - end - pa_inited[] = true - notifier = @async notify_active_streams() + # end atexit() do - for str in active_streams - close(str) - end Pa_Terminate() - pa_inited[] = false - notify(global_cond[].cond) - fetch(notifier) end end diff --git a/src/libportaudio.jl b/src/libportaudio.jl index 3da627d..5037a54 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -43,6 +43,28 @@ const paContinue = PaStreamCallbackResult(0) const paComplete = PaStreamCallbackResult(1) const paAbort = PaStreamCallbackResult(2) +""" +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). + +This will wait for the threaded call to complete even if an exception is thrown. +""" +macro tcall(ex) + quote + t = Base.Threads.@spawn $(esc(ex)) + try + fetch(t) + catch + # even if we got an exception (like an interrupt exception) make sure + # we wait for the spawned call to complete so we don't clean up + # any of its resources + wait(t) + rethrow() + end + end +end + function Pa_Initialize() err = ccall((:Pa_Initialize, libportaudio), PaError, ()) handle_status(err) @@ -170,8 +192,9 @@ function Pa_OpenStream(inParams, outParams, # matter because userdata should be GC-rooted anyways Ptr{Cvoid}), streamPtr, inParams, outParams, - sampleRate, framesPerBuffer, flags, callback, - pointer_from_objref(userdata)) + float(sampleRate), framesPerBuffer, flags, + callback === nothing ? C_NULL : callback, + userdata === nothing ? C_NULL : pointer_from_objref(userdata)) handle_status(err) streamPtr[] end @@ -211,9 +234,9 @@ end function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf), show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") - err = ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ref{Cvoid}, Culong), - stream, buf, frames) + err = @tcall ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, buf, frames) handle_status(err, show_warnings) buf end @@ -221,9 +244,9 @@ end function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf), show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") - err = ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ref{Cvoid}, Culong), - stream, buf, frames) + err = @tcall ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, buf, frames) handle_status(err, show_warnings) nothing end @@ -241,11 +264,11 @@ function handle_status(err::PaError, show_warnings::Bool=true) if show_warnings msg = ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) - warn("libportaudio: " * unsafe_string(msg)) + @warn("libportaudio: " * unsafe_string(msg)) end elseif err != PA_NO_ERROR msg = ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) - error("libportaudio: " * unsafe_string(msg)) + throw(ErrorException("libportaudio: " * unsafe_string(msg))) end end diff --git a/src/pa_shim.jl b/src/pa_shim.jl deleted file mode 100644 index 1e7c916..0000000 --- a/src/pa_shim.jl +++ /dev/null @@ -1,18 +0,0 @@ -const pa_shim_errmsg_t = Cint -const PA_SHIM_ERRMSG_OVERFLOW = Cint(0) # input overflow -const PA_SHIM_ERRMSG_UNDERFLOW = Cint(1) # output underflow -const PA_SHIM_ERRMSG_ERR_OVERFLOW = Cint(2) # error buffer overflowed - - -# This struct is shared with pa_shim.c -mutable struct pa_shim_info_t - inputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for input - outputbuf::Ptr{PaUtilRingBuffer} # ringbuffer for output - errorbuf::Ptr{PaUtilRingBuffer} # ringbuffer to send error notifications - sync::Cint # keep input/output ring buffers synchronized (0/1) - notifycb::Ptr{Cvoid} # Julia callback to notify on updates (called from audio thread) - inputhandle::Ptr{Cvoid} # condition to notify on new input data - outputhandle::Ptr{Cvoid} # condition to notify when ready for output - errorhandle::Ptr{Cvoid} # condition to notify on new errors - globalhandle::Ptr{Cvoid} # only needed for libuv workaround -end From 16d0bc48be138f3437cf6b56012056dd4ebefba5 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 2 Jan 2020 13:56:46 -0500 Subject: [PATCH 26/31] adds auto-detection of ALSA config dir --- src/PortAudio.jl | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index bb280d8..34d01a3 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -305,7 +305,32 @@ function suppress_err(dofunc::Function) end function __init__() - ENV["ALSA_CONFIG_DIR"] = "/usr/share/alsa" + envkey = "ALSA_CONFIG_DIR" + if envkey ∉ keys(ENV) + searchdirs = ["/usr/share/alsa", + "/usr/local/share/alsa", + "/etc/alsa"] + confdir_idx = findfirst(searchdirs) do d + isfile(joinpath(d, "alsa.conf")) + end + if confdir_idx === nothing + throw(ErrorException( + """ + Could not find ALSA config directory. Searched: + $(join(searchdirs, "\n")) + + if ALSA is installed, set the "ALSA_CONFIG_DIR" environment + variable. The given directory should have a file "alsa.conf". + + If it would be useful to others, please file an issue at + https://github.com/JuliaAudio/PortAudio.jl/issues + with your alsa config directory so we can add it to the search + paths. + """)) + end + confdir = searchdirs[confdir_idx] + ENV[envkey] = confdir + end # initialize PortAudio on module load # suppress_err() do Pa_Initialize() From f1234782315e43c0a4195f0a16b41790fd207821 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 2 Jan 2020 14:23:41 -0500 Subject: [PATCH 27/31] now using a mutex to protect libportaudio access --- src/libportaudio.jl | 67 ++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/libportaudio.jl b/src/libportaudio.jl index 5037a54..66c75d4 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -47,38 +47,37 @@ const paAbort = PaStreamCallbackResult(2) 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). - -This will wait for the threaded call to complete even if an exception is thrown. """ 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 - t = Base.Threads.@spawn $(esc(ex)) - try - fetch(t) - catch - # even if we got an exception (like an interrupt exception) make sure - # we wait for the spawned call to complete so we don't clean up - # any of its resources - wait(t) - rethrow() + lock(pamutex) do + $(esc(ex)) end end end function Pa_Initialize() - err = ccall((:Pa_Initialize, libportaudio), PaError, ()) + err = @locked ccall((:Pa_Initialize, libportaudio), PaError, ()) handle_status(err) end function Pa_Terminate() - err = ccall((:Pa_Terminate, libportaudio), PaError, ()) + err = @locked ccall((:Pa_Terminate, libportaudio), PaError, ()) handle_status(err) end -Pa_GetVersion() = ccall((:Pa_GetVersion, libportaudio), Cint, ()) +Pa_GetVersion() = @locked ccall((:Pa_GetVersion, libportaudio), Cint, ()) function Pa_GetVersionText() - versionPtr = ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) + versionPtr = @locked ccall((:Pa_GetVersionText, libportaudio), Ptr{Cchar}, ()) unsafe_string(versionPtr) end @@ -117,7 +116,7 @@ mutable struct PaHostApiInfo defaultOutputDevice::PaDeviceIndex end -Pa_GetHostApiInfo(i) = unsafe_load(ccall((:Pa_GetHostApiInfo, libportaudio), +Pa_GetHostApiInfo(i) = unsafe_load(@locked ccall((:Pa_GetHostApiInfo, libportaudio), Ptr{PaHostApiInfo}, (PaHostApiIndex,), i)) # Device Functions @@ -135,15 +134,15 @@ mutable struct PaDeviceInfo default_sample_rate::Cdouble end -Pa_GetDeviceCount() = ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) +Pa_GetDeviceCount() = @locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) -Pa_GetDeviceInfo(i) = unsafe_load(ccall((:Pa_GetDeviceInfo, libportaudio), +Pa_GetDeviceInfo(i) = unsafe_load(@locked ccall((:Pa_GetDeviceInfo, libportaudio), Ptr{PaDeviceInfo}, (PaDeviceIndex,), i)) -Pa_GetDefaultInputDevice() = ccall((:Pa_GetDefaultInputDevice, libportaudio), +Pa_GetDefaultInputDevice() = @locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ()) -Pa_GetDefaultOutputDevice() = ccall((:Pa_GetDefaultOutputDevice, libportaudio), +Pa_GetDefaultOutputDevice() = @locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ()) # Stream Functions @@ -183,7 +182,7 @@ function Pa_OpenStream(inParams, outParams, flags::PaStreamFlags, callback, userdata) streamPtr = Ref{PaStream}(0) - err = ccall((:Pa_OpenStream, libportaudio), PaError, + 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 @@ -200,32 +199,32 @@ function Pa_OpenStream(inParams, outParams, end function Pa_StartStream(stream::PaStream) - err = ccall((:Pa_StartStream, libportaudio), PaError, + err = @locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_StopStream(stream::PaStream) - err = ccall((:Pa_StopStream, libportaudio), PaError, + err = @locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_CloseStream(stream::PaStream) - err = ccall((:Pa_CloseStream, libportaudio), PaError, + err = @locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_GetStreamReadAvailable(stream::PaStream) - avail = ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, + avail = @locked ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, (PaStream,), stream) avail >= 0 || handle_status(avail) avail end function Pa_GetStreamWriteAvailable(stream::PaStream) - avail = ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, + avail = @locked ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, (PaStream,), stream) avail >= 0 || handle_status(avail) avail @@ -234,9 +233,9 @@ end function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer=length(buf), show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") - err = @tcall ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Cvoid}, Culong), - stream, buf, frames) + err = @tcall @locked ccall((:Pa_ReadStream, libportaudio), PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, buf, frames) handle_status(err, show_warnings) buf end @@ -244,9 +243,9 @@ end function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer=length(buf), show_warnings::Bool=true) frames <= length(buf) || error("Need a buffer at least $frames long") - err = @tcall ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Cvoid}, Culong), - stream, buf, frames) + err = @tcall @locked ccall((:Pa_WriteStream, libportaudio), PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, buf, frames) handle_status(err, show_warnings) nothing end @@ -262,12 +261,12 @@ end function handle_status(err::PaError, show_warnings::Bool=true) if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED if show_warnings - msg = ccall((:Pa_GetErrorText, libportaudio), + msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) @warn("libportaudio: " * unsafe_string(msg)) end elseif err != PA_NO_ERROR - msg = ccall((:Pa_GetErrorText, libportaudio), + msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) throw(ErrorException("libportaudio: " * unsafe_string(msg))) end From d069e75a9facd626aafd7ae3c86d9e6ff763e0d2 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 30 Jan 2020 11:28:44 -0500 Subject: [PATCH 28/31] bumps latency for more reliable performance --- src/PortAudio.jl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 34d01a3..b0644e8 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -67,6 +67,15 @@ mutable struct PortAudioStream{T} # this inner constructor is generally called via the top-level outer # constructor below + + # TODO: handle blocksize=0, that should be the default and generally works + # much better than trying to specify + # TODO: expose latency parameter + # 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... + # TODO: write a latency tester app function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, inchans, outchans, sr, blocksize, warn_xruns) where {T} @@ -74,10 +83,10 @@ mutable struct PortAudioStream{T} outchans = outchans == -1 ? outdev.maxoutchans : outchans inparams = (inchans == 0) ? Ptr{Pa_StreamParameters}(0) : - Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.0, C_NULL)) + Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], 0.1, C_NULL)) outparams = (outchans == 0) ? Ptr{Pa_StreamParameters}(0) : - Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.0, C_NULL)) + Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], 0.1, C_NULL)) this = new(sr, blocksize, C_NULL, warn_xruns) # finalizer(close, this) this.sink = PortAudioSink{T}(outdev.name, this, outchans) @@ -199,6 +208,7 @@ end isopen(stream::PortAudioStream) = stream.stream != C_NULL SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate +SampledSignals.blocksize(stream::PortAudioStream) = stream.blocksize eltype(stream::PortAudioStream{T}) where T = T read(stream::PortAudioStream, args...) = read(stream.source, args...) From 9d780e495065ae52c30a5189b41bb8661361723e Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 30 Jan 2020 11:33:50 -0500 Subject: [PATCH 29/31] re-enables suppressing portaudio initialization output --- src/PortAudio.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index b0644e8..4450542 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -341,10 +341,13 @@ function __init__() confdir = searchdirs[confdir_idx] ENV[envkey] = confdir end - # initialize PortAudio on module load - # suppress_err() do + # initialize PortAudio on module load. libportaudio prints a bunch of + # 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 + suppress_err() do Pa_Initialize() - # end + end atexit() do Pa_Terminate() From b18b9bdcae39aaab3ce074a1533f1cb25237cb2f Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 30 Jan 2020 11:54:16 -0500 Subject: [PATCH 30/31] only look for ALSA on linux systems --- src/PortAudio.jl | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 4450542..8eace2c 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -315,31 +315,33 @@ function suppress_err(dofunc::Function) end function __init__() - envkey = "ALSA_CONFIG_DIR" - if envkey ∉ keys(ENV) - searchdirs = ["/usr/share/alsa", - "/usr/local/share/alsa", - "/etc/alsa"] - confdir_idx = findfirst(searchdirs) do d - isfile(joinpath(d, "alsa.conf")) - end - if confdir_idx === nothing - throw(ErrorException( - """ - Could not find ALSA config directory. Searched: - $(join(searchdirs, "\n")) + if Sys.islinux() + envkey = "ALSA_CONFIG_DIR" + if envkey ∉ keys(ENV) + searchdirs = ["/usr/share/alsa", + "/usr/local/share/alsa", + "/etc/alsa"] + confdir_idx = findfirst(searchdirs) do d + isfile(joinpath(d, "alsa.conf")) + end + if confdir_idx === nothing + throw(ErrorException( + """ + Could not find ALSA config directory. Searched: + $(join(searchdirs, "\n")) - if ALSA is installed, set the "ALSA_CONFIG_DIR" environment - variable. The given directory should have a file "alsa.conf". + if ALSA is installed, set the "ALSA_CONFIG_DIR" environment + variable. The given directory should have a file "alsa.conf". - If it would be useful to others, please file an issue at - https://github.com/JuliaAudio/PortAudio.jl/issues - with your alsa config directory so we can add it to the search - paths. - """)) + If it would be useful to others, please file an issue at + https://github.com/JuliaAudio/PortAudio.jl/issues + with your alsa config directory so we can add it to the search + paths. + """)) + end + confdir = searchdirs[confdir_idx] + ENV[envkey] = confdir end - confdir = searchdirs[confdir_idx] - ENV[envkey] = confdir end # initialize PortAudio on module load. libportaudio prints a bunch of # junk to STDOUT on initialization, so we swallow it. From 93916a630de10d17b17adb5d5470d6e4b20b2ba3 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Thu, 30 Jan 2020 15:25:08 -0500 Subject: [PATCH 31/31] Removes crufty files and outdated tests --- Manifest.toml | 206 -------------------------------------- REQUIRE | 7 -- ci_setup.jl | 21 ---- test/runtests.jl | 250 +---------------------------------------------- 4 files changed, 2 insertions(+), 482 deletions(-) delete mode 100644 Manifest.toml delete mode 100644 REQUIRE delete mode 100644 ci_setup.jl diff --git a/Manifest.toml b/Manifest.toml deleted file mode 100644 index 2739782..0000000 --- a/Manifest.toml +++ /dev/null @@ -1,206 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -[[AbstractFFTs]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "051c95d6836228d120f5f4b984dd5aba1624f716" -uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" -version = "0.5.0" - -[[Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[BinaryProvider]] -deps = ["Libdl", "SHA"] -git-tree-sha1 = "5b08ed6036d9d3f0ee6369410b830f8873d4024c" -uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232" -version = "0.5.8" - -[[Compat]] -deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "ed2c4abadf84c53d9e58510b5fc48912c2336fbb" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "2.2.0" - -[[DSP]] -deps = ["FFTW", "LinearAlgebra", "Polynomials", "Random", "Reexport", "SpecialFunctions", "Statistics"] -git-tree-sha1 = "fa9564dc41fb91716c2e3353fe447f268a08f14d" -uuid = "717857b8-e6f2-59f4-9121-6e50c889abd2" -version = "0.6.2" - -[[Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - -[[Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - -[[FFTW]] -deps = ["AbstractFFTs", "FFTW_jll", "IntelOpenMP_jll", "Libdl", "LinearAlgebra", "MKL_jll", "Reexport"] -git-tree-sha1 = "109d82fa4b00429f9afcce873e9f746f11f018d3" -uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" -version = "1.2.0" - -[[FFTW_jll]] -deps = ["Libdl", "Pkg"] -git-tree-sha1 = "05674f209a6e3387dd103a945b0113eeb64b1a58" -uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a" -version = "3.3.9+3" - -[[FixedPointNumbers]] -git-tree-sha1 = "d14a6fa5890ea3a7e5dcab6811114f132fec2b4b" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.6.1" - -[[IntelOpenMP_jll]] -deps = ["Libdl", "Pkg"] -git-tree-sha1 = "fb8e1c7a5594ba56f9011310790e03b5384998d6" -uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0" -version = "2018.0.3+0" - -[[InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[IntervalSets]] -deps = ["Dates", "Statistics"] -git-tree-sha1 = "4214b48a62eb8f2c292b2ee34a508c256c0cdbc9" -uuid = "8197267c-284f-5f27-9208-e0e47529a953" -version = "0.3.2" - -[[LibGit2]] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" - -[[LinearAlgebra]] -deps = ["Libdl"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - -[[Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[MKL_jll]] -deps = ["Libdl", "Pkg"] -git-tree-sha1 = "61069ae718b8ab1e325bbfb4e5268902e7ea08e3" -uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7" -version = "2019.0.117+0" - -[[Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[OpenSpecFun_jll]] -deps = ["Libdl", "Pkg"] -git-tree-sha1 = "65f672edebf3f4e613ddf37db9dcbd7a407e5e90" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.3+1" - -[[Pkg]] -deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" - -[[Polynomials]] -deps = ["LinearAlgebra", "RecipesBase"] -git-tree-sha1 = "ae71c2329790af97b7682b11241b3609e4d48626" -uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" -version = "0.6.0" - -[[Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[Random]] -deps = ["Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[RecipesBase]] -git-tree-sha1 = "7bdce29bc9b2f5660a6e5e64d64d91ec941f6aa2" -uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -version = "0.7.0" - -[[Reexport]] -deps = ["Pkg"] -git-tree-sha1 = "7b1d07f411bc8ddb7977ec7f377b97b158514fe0" -uuid = "189a3867-3050-52da-a836-e630ba90ab69" -version = "0.2.0" - -[[RingBuffers]] -deps = ["BinaryProvider", "Compat"] -git-tree-sha1 = "a22900f141fae3e8c6f2ab9e4d48dfca274fafb8" -uuid = "f6d8bcc6-4e01-5431-93c4-9d6004abab90" -version = "1.2.0" - -[[SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" - -[[SampledSignals]] -deps = ["Compat", "DSP", "FFTW", "FixedPointNumbers", "IntervalSets", "LinearAlgebra", "TreeViews", "Unitful"] -git-tree-sha1 = "ce5705b342baa4a995ba6fa7ca9aa96e6647ac91" -uuid = "bd7594eb-a658-542f-9e75-4c4d8908c167" -version = "2.1.0" - -[[Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" - -[[Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[SparseArrays]] -deps = ["LinearAlgebra", "Random"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[SpecialFunctions]] -deps = ["OpenSpecFun_jll"] -git-tree-sha1 = "268052ee908b2c086cc0011f528694f02f3e2408" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "0.9.0" - -[[Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[[Test]] -deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[TreeViews]] -deps = ["Test"] -git-tree-sha1 = "8d0d7a3fe2f30d6a7f833a5f19f7c7a5b396eae6" -uuid = "a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7" -version = "0.3.0" - -[[UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[Unitful]] -deps = ["LinearAlgebra", "Random"] -git-tree-sha1 = "92bdf0ccfa9612b167d0adaadef832a09971ceb0" -uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" -version = "0.17.0" - -[[libportaudio_jll]] -deps = ["Libdl", "Pkg"] -path = "/Users/sabae/.julia/dev/libportaudio_jll" -uuid = "2d7b7beb-0762-5160-978e-1ab83a1e8a31" -version = "19.6.0+1" diff --git a/REQUIRE b/REQUIRE deleted file mode 100644 index 9379715..0000000 --- a/REQUIRE +++ /dev/null @@ -1,7 +0,0 @@ -julia 0.6 -BinDeps 0.8.8 -SampledSignals 1.1.2 -RingBuffers 1.1.2 -Compat 0.66.0 -@osx Homebrew -@windows WinRPM diff --git a/ci_setup.jl b/ci_setup.jl deleted file mode 100644 index 0e84770..0000000 --- a/ci_setup.jl +++ /dev/null @@ -1,21 +0,0 @@ -VERSION >= v"0.7.0-" && using InteractiveUtils -versioninfo() - -if VERSION < v"0.7.0-" - Pkg.clone(pwd(), "PortAudio") - Pkg.build("PortAudio") - # for now we need SampledSignals and RingBuffers master - Pkg.checkout("SampledSignals") - Pkg.checkout("RingBuffers") -else - using Pkg - # for now we need to `clone` because there's no way to specify the - # package name for `add` - #Pkg.clone(pwd(), "PortAudio") - Pkg.build("PortAudio") - Pkg.add(PackageSpec(name="SampledSignals", rev="master")) - Pkg.add(PackageSpec(name="RingBuffers", rev="master")) -end - -# add test deps manually because we'll be running test/runtests.jl manually -#Pkg.add("Compat") diff --git a/test/runtests.jl b/test/runtests.jl index 3987f10..777e623 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,197 +1,8 @@ #!/usr/bin/env julia using PortAudio -using SampledSignals -using RingBuffers using Test -# pull in some extra stuff we need to test the callback directly -using PortAudio: notifyhandle, notifycb_c, shim_processcb_c -using PortAudio: pa_shim_errmsg_t, pa_shim_info_t -using PortAudio: PA_SHIM_ERRMSG_ERR_OVERFLOW, PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW - -# only needed for the libuv workaround -const globalcond = Base.AsyncCondition() - -"Setup buffers to test callback behavior" -function setup_callback(inchans, outchans, nframes, synced) - sourcebuf = RingBuffer{Float32}(inchans, nframes*2) # the microphone input should end up here - sinkbuf = RingBuffer{Float32}(outchans, nframes*2) # the callback should copy this to cb_output - errbuf = RingBuffer{pa_shim_errmsg_t}(1, 8) - - # pass NULL for i/o we're not using - info = pa_shim_info_t( - inchans > 0 ? pointer(sourcebuf) : C_NULL, - outchans > 0 ? pointer(sinkbuf) : C_NULL, - pointer(errbuf), - synced, notifycb_c, - inchans > 0 ? notifyhandle(sourcebuf) : C_NULL, - outchans > 0 ? notifyhandle(sinkbuf) : C_NULL, - notifyhandle(errbuf), - globalcond.handle - ) - flags = Culong(0) - - cb_input = rand(Float32, inchans, nframes) # simulate microphone input - cb_output = rand(Float32, outchans, nframes) # this is where the output should go - - function processfunc() - ccall(shim_processcb_c, Cint, - (Ref{Float32}, Ref{Float32}, Culong, Ref{Cvoid}, Culong, Ref{pa_shim_info_t}), - cb_input, cb_output, nframes, C_NULL, flags, info) - end - - (sourcebuf, sinkbuf, errbuf, cb_input, cb_output, processfunc) -end - -# the process callback only has pointers (not refs) to some data, so we need -# to make sure the references are preserved -function wrapprocess(process, sourcebuf, sinkbuf) - @static if VERSION >= v"0.7.0-" - GC.@preserve sourcebuf sinkbuf begin - process() - end - else - process() - end -end - -function test_callback(inchans, outchans, synced) - nframes = 8 - (sourcebuf, sinkbuf, errbuf, - cb_input, cb_output, - process) = setup_callback(inchans, outchans, nframes, synced) - if outchans > 0 - testout = rand(Float32, outchans, nframes) # generate some test data to play - write(sinkbuf, testout) # fill the output ringbuffer - end - @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue - if outchans > 0 - # testout -> sinkbuf -> cb_output - @test cb_output == testout - end - if inchans > 0 - # cb_input -> sourcebuf - @test read(sourcebuf, nframes) == cb_input - end - @test framesreadable(errbuf) == 0 -end - -""" - test_callback_underflow(inchans, outchans; nframes=8, underfill=3, synced=false) - -Test that the callback works on underflow conditions. underfill is the numer of -frames we feed in, which should be less than nframes. -""" -function test_callback_underflow(inchans, outchans, synced) - nframes = 8 - underfill = 3 # must be less than nframes - (sourcebuf, sinkbuf, errbuf, - cb_input, cb_output, - process) = setup_callback(inchans, outchans, nframes, synced) - outchans > 0 || error("Can't test underflow with no output") - testout = rand(Float32, outchans, underfill) - write(sinkbuf, testout) # underfill the output ringbuffer - # call callback (partial underflow) - @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue - @test cb_output[:, 1:underfill] == testout - @test cb_output[:, (underfill+1):nframes] == zeros(Float32, outchans, (nframes-underfill)) - errs = readavailable(errbuf) - if inchans > 0 - received = readavailable(sourcebuf) - if synced - @test size(received, 2) == underfill - @test received == cb_input[:, 1:underfill] - @test length(errs) == 2 - @test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW]) - else - @test size(received, 2) == nframes - @test received == cb_input - @test length(errs) == 1 - @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW - end - else - @test length(errs) == 1 - @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW - end - - # call again (total underflow) - @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue - @test cb_output == zeros(Float32, outchans, nframes) - errs = readavailable(errbuf) - if inchans > 0 - received = readavailable(sourcebuf) - if synced - @test size(received, 2) == 0 - @test length(errs) == 2 - @test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW]) - else - @test size(received, 2) == nframes - @test received == cb_input - @test length(errs) == 1 - @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW - end - else - @test length(errs) == 1 - @test errs[1] == PA_SHIM_ERRMSG_UNDERFLOW - end -end - -function test_callback_overflow(inchans, outchans, synced) - nframes = 8 - (sourcebuf, sinkbuf, errbuf, - cb_input, cb_output, - process) = setup_callback(inchans, outchans, nframes, synced) - inchans > 0 || error("Can't test overflow with no input") - @test frameswritable(sinkbuf) == nframes*2 - - # the first time it should half-fill the input ring buffer - if outchans > 0 - testout = rand(Float32, outchans, nframes) - write(sinkbuf, testout) - end - @test framesreadable(sourcebuf) == 0 - outchans > 0 && @test frameswritable(sinkbuf) == nframes - @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue - @test framesreadable(errbuf) == 0 - @test framesreadable(sourcebuf) == nframes - outchans > 0 && @test frameswritable(sinkbuf) == nframes*2 - - # now run the process func again to completely fill the input ring buffer - outchans > 0 && write(sinkbuf, testout) - @test framesreadable(sourcebuf) == nframes - outchans > 0 && @test frameswritable(sinkbuf) == nframes - @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue - @test framesreadable(errbuf) == 0 - @test framesreadable(sourcebuf) == nframes*2 - outchans > 0 && @test frameswritable(sinkbuf) == nframes*2 - - # now this time the process func should overflow the input buffer - outchans > 0 && write(sinkbuf, testout) - @test framesreadable(sourcebuf) == nframes*2 - outchans > 0 && @test frameswritable(sinkbuf) == nframes - @test wrapprocess(process, sourcebuf, sinkbuf) == PortAudio.paContinue - @test framesreadable(sourcebuf) == nframes*2 - errs = readavailable(errbuf) - if outchans > 0 - if synced - # if input and output are synced, thec callback didn't pull from - # the output ringbuf - @test frameswritable(sinkbuf) == nframes - @test cb_output == zeros(Float32, outchans, nframes) - @test length(errs) == 2 - @test Set(errs) == Set([PA_SHIM_ERRMSG_UNDERFLOW, PA_SHIM_ERRMSG_OVERFLOW]) - else - @test frameswritable(sinkbuf) == nframes*2 - @test length(errs) == 1 - @test errs[1] == PA_SHIM_ERRMSG_OVERFLOW - end - else - @test length(errs) == 1 - @test errs[1] == PA_SHIM_ERRMSG_OVERFLOW - end -end - @testset "PortAudio Tests" begin @testset "Reports version" begin io = IOBuffer() @@ -201,64 +12,7 @@ end @test startswith(result[1], "PortAudio V19") end - @testset "using correct shim version" begin - @test PortAudio.shimhash() == "7570f145b3c1b5a6f0733378a45807c5fd47e30f04771e4eaa2c380d80dcd15d" - end - - @testset "Basic callback functionality" begin - @testset "basic duplex (no sync)" begin - test_callback(2, 3, false) - end - @testset "basic input-only (no sync)" begin - test_callback(2, 0, false) - end - @testset "basic output-only (no sync)" begin - test_callback(0, 2, false) - end - @testset "basic no input or output (no sync)" begin - test_callback(0, 0, false) - end - @testset "basic duplex (sync)" begin - test_callback(2, 3, true) - end - @testset "basic input-only (sync)" begin - test_callback(2, 0, true) - end - @testset "basic output-only (sync)" begin - test_callback(0, 2, true) - end - @testset "basic no input or output (sync)" begin - test_callback(0, 0, true) - end - end - - @testset "Ouput underflow" begin - @testset "underflow duplex (nosync)" begin - test_callback_underflow(2, 3, false) - end - @testset "underflow output-only (nosync)" begin - test_callback_underflow(0, 3, false) - end - @testset "underflow duplex (sync)" begin - test_callback_underflow(2, 3, true) - end - @testset "underflow output-only (sync)" begin - test_callback_underflow(0, 3, true) - end - end - - @testset "Input overflow" begin - @testset "overflow duplex (nosync)" begin - test_callback_overflow(2, 3, false) - end - @testset "overflow input-only (nosync)" begin - test_callback_overflow(2, 0, false) - end - @testset "overflow duplex (sync)" begin - test_callback_overflow(2, 3, true) - end - @testset "overflow input-only (sync)" begin - test_callback_overflow(2, 0, true) - end + @testset "Can list devices without crashing" begin + PortAudio.devices() end end