diff --git a/Project.toml b/Project.toml index cf1b455..e6621d6 100644 --- a/Project.toml +++ b/Project.toml @@ -19,8 +19,11 @@ Suppressor = "0.2" [extras] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2" +FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" LibSndFile = "b13ce0c6-77b0-50c6-a2db-140568b8d1a5" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Documenter", "LibSndFile", "Test"] +test = ["DSP", "Documenter", "FFTW", "GR", "LibSndFile", "Test"] diff --git a/examples/audiometer.jl b/examples/audiometer.jl index 3bac247..9800cfc 100644 --- a/examples/audiometer.jl +++ b/examples/audiometer.jl @@ -4,18 +4,23 @@ using PortAudio Continuously read from the default audio input and plot an ASCII level/peak meter """ -function micmeter(metersize) - mic = PortAudioStream(1, 0; latency = 0.1) - - signalmax = zero(eltype(mic)) - println("Press Ctrl-C to quit") - while true - block = read(mic, 512) - 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) +function micmeter(seconds; metersize = 80) + PortAudioStream(1, 0; latency = 0.1) do mic + done = false + signalmax = zero(eltype(mic)) + @sync begin + @async while !done + block = read(mic, 512) + 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) + end + sleep(seconds) + done = true + end end + nothing end """ @@ -52,4 +57,4 @@ function barcolor(metersize, position) end end -micmeter(80) +micmeter(5) diff --git a/examples/lilyplay.jl b/examples/lilyplay.jl index a9904f6..8dbc19e 100644 --- a/examples/lilyplay.jl +++ b/examples/lilyplay.jl @@ -1,22 +1,8 @@ -using Distributed, PortAudio +using PortAudio +using Base.Threads: @spawn # Modified from Jiahao Chen's example in the obsolete AudioIO module. # Will use first output device found in system's listing or DEFAULTDEVICE if set below -const DEFAULTDEVICE = -1 - -function paudio() - devs = PortAudio.devices() - if DEFAULTDEVICE < 0 - devnum = findfirst(x -> x.maxoutchans > 0, devs) - (devnum == nothing) && error("No output device for audio found") - else - devnum = DEFAULTDEVICE + 1 - end - return ostream = PortAudioStream(devs[devnum].name, 0, 2) -end - -play(ostream, sample::Array{Float64, 1}) = write(ostream, sample) -play(ostr, sample::Array{Int64, 1}) = play(ostr, Float64.(sample)) struct Note{S <: Real, T <: Real} pitch::S @@ -35,14 +21,13 @@ function play( if !A.sustained decay_length = div(length(timesamples), 5) v[(end - decay_length):(end - 1)] = - v[(end - decay_length):(end - 1)] .* LinRange(1, 0, decay_length) + v[(end - decay_length):(end - 1)] .* LinRange(1, 0, decay_length) end - play(ostream, v) - sleep(A.duration) + write(ostream, v) end function parsevoice(melody::String; tempo = 132, beatunit = 4, lyrics = nothing) - ostream = paudio() # initialize audio for output + ostream = PortAudioStream(0, 2; warn_xruns = false) # initialize audio for output lyrics_syllables = lyrics == nothing ? nothing : split(lyrics) lyrics_syllables != nothing && (lyrics_syllables[end] *= "\n") note_idx = 1 diff --git a/examples/spectrum.jl b/examples/spectrum.jl index 2423fbf..badd769 100644 --- a/examples/spectrum.jl +++ b/examples/spectrum.jl @@ -1,20 +1,26 @@ # plot a real-time spectrogram. This example is adapted from the GR example # at http://gr-framework.org/examples/audio_ex.html -module SpectrumExample - using GR, PortAudio, SampledSignals, FFTW -const N = 1024 -const stream = PortAudioStream(1, 0) -const buf = read(stream, N) -const fmin = 0Hz -const fmax = 10000Hz -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)) +function plot_spectrogram(seconds; + N = 1024, + fmin = 0Hz, + fmax = 10000Hz +) + PortAudioStream(1, 0) do stream + done = false + buf = read(stream, N) + fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])] + @sync begin + @async while !done + read!(stream, buf) + plot(fs, abs.(fft(buf)[fmin..fmax]), xlim = (fs[1], fs[end]), ylim = (0, 100)) + end + sleep(seconds) + done = true + end + end end -end +plot_spectrogram(5) diff --git a/src/PortAudio.jl b/src/PortAudio.jl index d8cd916..abdbf0d 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1005,6 +1005,9 @@ function exchange(messenger, arguments...) take!(messenger.output_channel) end +as_matrix(matrix::Matrix) = matrix +as_matrix(vector::Vector) = reshape(vector, length(vector), 1) + # these will only work with sampledsignals scribes function unsafe_write( sink::PortAudioSink{<:Messenger{<:Any, <:SampledSignalsWriter}}, @@ -1012,7 +1015,7 @@ function unsafe_write( already, frame_count, ) - exchange(sink.stream.sink_messanger, julia_buffer, already, frame_count) + exchange(sink.stream.sink_messanger, as_matrix(julia_buffer), already, frame_count) end function unsafe_read!( @@ -1021,7 +1024,7 @@ function unsafe_read!( already, frame_count, ) - exchange(source.stream.source_messanger, julia_buffer, already, frame_count) + exchange(source.stream.source_messanger, as_matrix(julia_buffer), already, frame_count) end end # module PortAudio diff --git a/test/runtests.jl b/test/runtests.jl index ee2e5ac..b1ea345 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -247,6 +247,14 @@ if !isempty(devices()) handle_status(Pa_SetStreamFinishedCallback(pointer_to, C_NULL)), ) == paNoError end + @testset "Make sure examples run" begin + include(joinpath(pkgdir(PortAudio), "examples", "audiometer.jl")) + include(joinpath(pkgdir(PortAudio), "examples", "lilyplay.jl")) + include(joinpath(pkgdir(PortAudio), "examples", "measure_latency.jl")) + include(joinpath(pkgdir(PortAudio), "examples", "spectrum.jl")) + # include("waterfall_heatmap.jl") + # include("waterfall_lines.jl") + end end doctest(PortAudio) end