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