diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..d29f76d --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,9 @@ +always_for_in = true +whitespace_typedefs = true +whitespace_ops_in_indices = true +remove_extra_newlines = true +import_to_using = true +short_to_long_function_def = true +format_docstrings = true +align_pair_arrow = false +conditional_to_if = true \ No newline at end of file diff --git a/examples/audiometer.jl b/examples/audiometer.jl index fa59591..3bac247 100644 --- a/examples/audiometer.jl +++ b/examples/audiometer.jl @@ -1,9 +1,11 @@ using PortAudio -"""Continuously read from the default audio input and plot an -ASCII level/peak meter""" +""" +Continuously read from the default audio input and plot an +ASCII level/peak meter +""" function micmeter(metersize) - mic = PortAudioStream(1, 0; latency=0.1) + mic = PortAudioStream(1, 0; latency = 0.1) signalmax = zero(eltype(mic)) println("Press Ctrl-C to quit") @@ -16,28 +18,32 @@ function micmeter(metersize) end end -"""Print an ASCII level meter of the given size. Signal and peak -levels are assumed to be scaled from 0.0-1.0, with peak >= signal""" +""" +Print an ASCII level meter of the given size. Signal and peak +levels are assumed to be scaled from 0.0-1.0, with peak >= signal +""" function printmeter(metersize, signal, peak) # calculate the positions in terms of characters peakpos = clamp(round(Int, peak * metersize), 0, metersize) - meterchars = clamp(round(Int, signal * metersize), 0, peakpos-1) - blankchars = max(0, peakpos-meterchars-1) + meterchars = clamp(round(Int, signal * metersize), 0, peakpos - 1) + blankchars = max(0, peakpos - meterchars - 1) for position in 1:meterchars - printstyled(">", color=barcolor(metersize, position)) + printstyled(">", color = barcolor(metersize, position)) end - print(" " ^ blankchars) - printstyled("|", color=barcolor(metersize, peakpos)) - print(" " ^ (metersize - peakpos)) + print(" "^blankchars) + printstyled("|", color = barcolor(metersize, peakpos)) + print(" "^(metersize - peakpos)) end -"""Compute the proper color for a given position in the bar graph. The first +""" +Compute the proper color for a given position in the bar graph. The first half of the bar should be green, then the remainder is yellow except the final -character, which is red.""" +character, which is red. +""" function barcolor(metersize, position) - if position/metersize <= 0.5 + if position / metersize <= 0.5 :green elseif position == metersize :red diff --git a/examples/lilyplay.jl b/examples/lilyplay.jl index 9117de3..a9904f6 100644 --- a/examples/lilyplay.jl +++ b/examples/lilyplay.jl @@ -15,30 +15,33 @@ function paudio() 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)) +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} +struct Note{S <: Real, T <: Real} pitch::S duration::T sustained::Bool end -function play(ostream, - A::Note, - samplingfreq::Real=44100, - shape::Function=t->0.6sin(t)+0.2sin(2t)+.05*sin(8t)) +function play( + ostream, + A::Note, + samplingfreq::Real = 44100, + shape::Function = t -> 0.6sin(t) + 0.2sin(2t) + 0.05 * sin(8t), +) timesamples = 0:(1 / samplingfreq):(A.duration * (A.sustained ? 0.98 : 0.9)) v = Float64[shape(2π * A.pitch * t) for t in timesamples] 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)] = + v[(end - decay_length):(end - 1)] .* LinRange(1, 0, decay_length) end play(ostream, v) sleep(A.duration) end -function parsevoice(melody::String; tempo=132, beatunit=4, lyrics=nothing) +function parsevoice(melody::String; tempo = 132, beatunit = 4, lyrics = nothing) ostream = paudio() # initialize audio for output lyrics_syllables = lyrics == nothing ? nothing : split(lyrics) lyrics_syllables != nothing && (lyrics_syllables[end] *= "\n") @@ -46,7 +49,7 @@ function parsevoice(melody::String; tempo=132, beatunit=4, lyrics=nothing) oldduration = 4 for line in split(melody, '\n') percent_idx = findfirst('%', line) # Trim comment - percent_idx == nothing || (line = line[1:percent_idx-1]) + percent_idx == nothing || (line = line[1:(percent_idx - 1)]) for token in split(line) pitch, duration, dotted, sustained = parsetoken(token) duration == nothing && (duration = oldduration) @@ -55,7 +58,7 @@ function parsevoice(melody::String; tempo=132, beatunit=4, lyrics=nothing) if lyrics_syllables != nothing && 1 <= note_idx <= length(lyrics_syllables) # Print the lyrics, omitting hyphens if lyrics_syllables[note_idx][end] == '-' - print(join(split(lyrics_syllables[note_idx][:], "")[1:end-1]), "") + print(join(split(lyrics_syllables[note_idx][:], "")[1:(end - 1)]), "") else print(lyrics_syllables[note_idx], ' ') end @@ -66,18 +69,19 @@ function parsevoice(melody::String; tempo=132, beatunit=4, lyrics=nothing) end end -function parsetoken(token, Atuning::Real=220) +function parsetoken(token, Atuning::Real = 220) state = :findpitch pitch = 0.0 sustain = dotted = false lengthbuf = Char[] for char in token if state == :findpitch - scale_idx = something(findfirst(char, String(collect('a':'g'))), 0) + + scale_idx = + something(findfirst(char, String(collect('a':'g'))), 0) + something(findfirst(char, String(collect('A':'G'))), 0) if scale_idx != 0 halfsteps = [12, 14, 3, 5, 7, 8, 10] - pitch = Atuning * 2 ^ (halfsteps[scale_idx] / 12) + pitch = Atuning * 2^(halfsteps[scale_idx] / 12) state = :findlength elseif char == 'r' pitch, state = 0, :findlength @@ -85,22 +89,28 @@ function parsetoken(token, Atuning::Real=220) error("unknown pitch: $char") end elseif state == :findlength - if char == '#' ; pitch *= 2^(1 / 12) # sharp - elseif char == 'b' ; pitch /= 2^(1 / 12) # flat - elseif char == '\''; pitch *= 2 # higher octave - elseif char == ',' ; pitch /= 2 # lower octave - elseif char == '.' ; dotted = true # dotted note - elseif char == '~' ; sustain = true # tied note + if char == '#' + pitch *= 2^(1 / 12) # sharp + elseif char == 'b' + pitch /= 2^(1 / 12) # flat + elseif char == '\'' + pitch *= 2 # higher octave + elseif char == ',' + pitch /= 2 # lower octave + elseif char == '.' + dotted = true # dotted note + elseif char == '~' + sustain = true # tied note else push!(lengthbuf, char) # Check for "is" and "es" suffixes for sharps and flats if length(lengthbuf) >= 2 - if lengthbuf[end-1:end] == "is" + if lengthbuf[(end - 1):end] == "is" pitch *= 2^(1 / 12) - lengthbuf = lengthbuf[1:end-2] - elseif lengthbuf[end-1:end] == "es" + lengthbuf = lengthbuf[1:(end - 2)] + elseif lengthbuf[(end - 1):end] == "es" pitch /= 2^(1 / 12) - lengthbuf = lengthbuf[1:end-2] + lengthbuf = lengthbuf[1:(end - 2)] end end end @@ -112,23 +122,27 @@ function parsetoken(token, Atuning::Real=220) return (pitch, duration, sustain, dotted) end -parsevoice(""" +parsevoice( + """ f# f# g a a g f# e d d e f# f#~ f#8 e e2 f#4 f# g a a g f# e d d e f# e~ e8 d d2 e4 e f# d e f#8~ g8 f#4 d e f#8~ g f#4 e d e a, f#2 f#4 g a a g f# e d d e f# e~ e8 d8 d2""", -lyrics=""" -Freu- de, schö- ner Göt- ter- fun- ken, Toch- ter aus E- li- - si- um! -Wir be- tre- ten feu- er- trun- ken, Himm- li- sche, dein Hei- - lig- thum! -Dei- ne Zau- ber bin den - wie- der, was die - Mo- de streng ge- theilt, -al- le mensch- en wer- den Brü- der wo dein sanf- ter Flü- - gel weilt. -""") + lyrics = """ + Freu- de, schö- ner Göt- ter- fun- ken, Toch- ter aus E- li- - si- um! + Wir be- tre- ten feu- er- trun- ken, Himm- li- sche, dein Hei- - lig- thum! + Dei- ne Zau- ber bin den - wie- der, was die - Mo- de streng ge- theilt, + al- le mensch- en wer- den Brü- der wo dein sanf- ter Flü- - gel weilt. + """, +) # And now with harmony! -soprano = @spawn parsevoice(""" +soprano = @spawn parsevoice( + """ f'#. f'#. g'. a'. a'. g'. f'#. e'~ e'8 d.'4 d.' e.' f#'. f#'.~ f#' e'8 e'4~ e'2 -""", lyrics="Freu- de, schö- ner Göt- ter- fun- ken, Toch- ter aus E- li- - si- um!" +""", + lyrics = "Freu- de, schö- ner Göt- ter- fun- ken, Toch- ter aus E- li- - si- um!", ) alto = @spawn parsevoice(""" a. a. a. a. a. a. a. a~ g8 f#.4 a. a. a. a.~ a a8 a4~ a2 @@ -144,9 +158,12 @@ wait(alto) wait(tenor) wait(bass) -soprano = @spawn parsevoice(""" +soprano = @spawn parsevoice( + """ f'#.4 f'#. g'. a'. a'. g'. f'#. e'. d'. d'. e'. f'#. e'.~ e' d'8 d'4~ d'2 -""", lyrics="Wir be- tre- ten feu- er- trun- ken, Himm- li- sche, dein Hei- - lig- thum!") +""", + lyrics = "Wir be- tre- ten feu- er- trun- ken, Himm- li- sche, dein Hei- - lig- thum!", +) alto = @spawn parsevoice(""" a.4 a. b. c'. c'. b. a. g. f#. f#. g. f#. g.~ g4 f#8 f#~ f#2 """) diff --git a/examples/measure_latency.jl b/examples/measure_latency.jl index 6f78ae3..da091ff 100644 --- a/examples/measure_latency.jl +++ b/examples/measure_latency.jl @@ -4,15 +4,14 @@ using DSP function create_measure_signal() signal = zeros(Float32, 20000) for i in 1:3 - signal = vcat(signal, rand(Float32, 100), zeros(Float32, i*10000)) + signal = vcat(signal, rand(Float32, 100), zeros(Float32, i * 10000)) end return signal end -function measure_latency(in_latency = 0.1, out_latency=0.1; is_warmup = false) - - in_stream = PortAudioStream(1,0; latency=in_latency) - out_stream = PortAudioStream(0,1; latency=out_latency) +function measure_latency(in_latency = 0.1, out_latency = 0.1; is_warmup = false) + in_stream = PortAudioStream(1, 0; latency = in_latency) + out_stream = PortAudioStream(0, 1; latency = out_latency) cond = Base.Event() @@ -27,9 +26,9 @@ function measure_latency(in_latency = 0.1, out_latency=0.1; is_warmup = false) signal = create_measure_signal() writer = Threads.@spawn begin - wait(cond) - reader_start_time = time_ns() |> Int64 - write(out_stream, signal) + wait(cond) + reader_start_time = time_ns() |> Int64 + write(out_stream, signal) end notify(cond) @@ -37,8 +36,8 @@ function measure_latency(in_latency = 0.1, out_latency=0.1; is_warmup = false) wait(reader) wait(writer) - recorded = collect(reader.result)[:,1] - + recorded = collect(reader.result)[:, 1] + close(in_stream) close(out_stream) diff --git a/examples/spectrum.jl b/examples/spectrum.jl index 262f33c..2423fbf 100644 --- a/examples/spectrum.jl +++ b/examples/spectrum.jl @@ -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 diff --git a/examples/waterfall_heatmap.jl b/examples/waterfall_heatmap.jl index 0a7b994..624c6ee 100644 --- a/examples/waterfall_heatmap.jl +++ b/examples/waterfall_heatmap.jl @@ -8,8 +8,8 @@ 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] + for col in size(buf, 2):-1:2 + @. buf[:, col] = buf[:, col - 1] end end @@ -20,7 +20,7 @@ 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)) + @. dispbuf[end:-1:1, 1] = log(clamp(abs(fftbuf[1:D]), 0.0001, Inf)) end function processblock!(src, buf, win, dispbufs, fftbuf, fftplan) @@ -31,17 +31,17 @@ function processblock!(src, buf, win, dispbufs, fftbuf, fftplan) end N = 1024 # size of audio read -N2 = N÷2+1 # size of rfft output +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) buf = Array{Float32}(N) # buffer for reading -fftplan = plan_rfft(buf; flags=FFTW.EXHAUSTIVE) +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)) +scene = Scene(resolution = (1000, 1000)) #pre-fill the display buffer so we can do a reasonable colormap for _ in 1:M @@ -53,7 +53,7 @@ heatmaps = map(enumerate(IndexCartesian(), dispbufs)) do ibuf 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))) + heatmap(buf, offset = (i[2] * size(buf, 2), i[1] * size(buf, 1))) end center!(scene) diff --git a/examples/waterfall_lines.jl b/examples/waterfall_lines.jl index 61a6b07..d4578d2 100644 --- a/examples/waterfall_lines.jl +++ b/examples/waterfall_lines.jl @@ -2,7 +2,7 @@ using Makie, GeometryTypes using PortAudio N = 1024 # size of audio read -N2 = N÷2+1 # size of rfft output +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 @@ -10,19 +10,24 @@ src = PortAudioStream(1, 2) buf = Array{Float32}(N) fftbuf = Array{Complex{Float32}}(N2) magbuf = Array{Float32}(N2) -fftplan = plan_rfft(buf; flags=FFTW.EXHAUSTIVE) +fftplan = plan_rfft(buf; flags = FFTW.EXHAUSTIVE) -scene = Scene(resolution=(500,500)) +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) + 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)) + l = lines( + linspace(0, 1, D), + 0.0f0, + zeros(Float32, D), + offset = offset, + color = (:black, 0.1), + ) (yoffset, l) end @@ -31,7 +36,7 @@ while isopen(scene[:screen]) isopen(scene[:screen]) || break read!(src, buf) A_mul_B!(fftbuf, fftplan, buf) - @. magbuf = log(clamp(abs(fftbuf), 0.0001, Inf))/10+0.5 + @. magbuf = log(clamp(abs(fftbuf), 0.0001, Inf)) / 10 + 0.5 line[:z] = magbuf[1:D] push!(yoffset, to_value(scene[:time])) end diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 0c8c400..7fb980c 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -9,7 +9,7 @@ import Base: eltype, show import Base: close, isopen import Base: read, read!, write -import LinearAlgebra +using LinearAlgebra: LinearAlgebra import LinearAlgebra: transpose! export PortAudioStream @@ -29,9 +29,9 @@ end # data is passed to and from portaudio in chunks with this many frames, because # we need to interleave the samples -const CHUNKFRAMES=128 +const CHUNKFRAMES = 128 -function versioninfo(io::IO=stdout) +function versioninfo(io::IO = stdout) println(io, Pa_GetVersionText()) println(io, "Version: ", Pa_GetVersion()) end @@ -49,7 +49,8 @@ mutable struct PortAudioDevice highoutputlatency::Float64 end -PortAudioDevice(info::PaDeviceInfo, idx) = PortAudioDevice( +function PortAudioDevice(info::PaDeviceInfo, idx) + PortAudioDevice( unsafe_string(info.name), unsafe_string(Pa_GetHostApiInfo(info.host_api).name), info.max_input_channels, @@ -59,12 +60,14 @@ PortAudioDevice(info::PaDeviceInfo, idx) = PortAudioDevice( info.default_low_input_latency, info.default_low_output_latency, info.default_high_input_latency, - info.default_high_output_latency) + info.default_high_output_latency, + ) +end function devices() ndevices = Pa_GetDeviceCount() infos = PaDeviceInfo[Pa_GetDeviceInfo(i) for i in 0:(ndevices - 1)] - PortAudioDevice[PortAudioDevice(info, idx-1) for (idx, info) in enumerate(infos)] + PortAudioDevice[PortAudioDevice(info, idx - 1) for (idx, info) in enumerate(infos)] end # not for external use, used in error message printing @@ -80,8 +83,8 @@ mutable struct PortAudioStream{T} stream::PaStream warn_xruns::Bool recover_xruns::Bool - sink # untyped because of circular type definition - source # untyped because of circular type definition + sink::Any # untyped because of circular type definition + source::Any # untyped because of circular type definition # this inner constructor is generally called via the top-level outer # constructor below @@ -90,23 +93,41 @@ mutable struct PortAudioStream{T} # TODO: recover from xruns - currently with low latencies (e.g. 0.01) it # will run fine for a while and then fail with the first xrun. # TODO: figure out whether we can get deterministic latency... - function PortAudioStream{T}(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans, outchans, sr, - latency, warn_xruns, recover_xruns) where {T} + function PortAudioStream{T}( + indev::PortAudioDevice, + outdev::PortAudioDevice, + inchans, + outchans, + sr, + latency, + warn_xruns, + recover_xruns, + ) where {T} inchans = inchans == -1 ? indev.maxinchans : inchans outchans = outchans == -1 ? outdev.maxoutchans : outchans - inparams = (inchans == 0) ? - Ptr{Pa_StreamParameters}(0) : + inparams = if (inchans == 0) + Ptr{Pa_StreamParameters}(0) + else Ref(Pa_StreamParameters(indev.idx, inchans, type_to_fmt[T], latency, C_NULL)) - outparams = (outchans == 0) ? - Ptr{Pa_StreamParameters}(0) : + end + outparams = if (outchans == 0) + Ptr{Pa_StreamParameters}(0) + else Ref(Pa_StreamParameters(outdev.idx, outchans, type_to_fmt[T], latency, C_NULL)) + end this = new(sr, latency, C_NULL, warn_xruns, recover_xruns) # finalizer(close, this) this.sink = PortAudioSink{T}(outdev.name, this, outchans) this.source = PortAudioSource{T}(indev.name, this, inchans) - this.stream = @stderr_as_debug Pa_OpenStream(inparams, outparams, sr, 0, paNoFlag, - nothing, nothing) + this.stream = @stderr_as_debug Pa_OpenStream( + inparams, + outparams, + sr, + 0, + paNoFlag, + nothing, + nothing, + ) Pa_StartStream(this.stream) # pre-fill the output stream so we're less likely to underrun @@ -116,7 +137,6 @@ mutable struct PortAudioStream{T} end end - function recover_xrun(stream::PortAudioStream) playback = nchannels(stream.sink) > 0 capture = nchannels(stream.source) > 0 @@ -133,15 +153,18 @@ function recover_xrun(stream::PortAudioStream) end end -defaultlatency(devices...) = maximum(d -> max(d.highoutputlatency, d.highinputlatency), devices) +function defaultlatency(devices...) + maximum(d -> max(d.highoutputlatency, d.highinputlatency), devices) +end function combine_default_sample_rates(inchans, sampleratein, outchans, samplerateout) if inchans > 0 && outchans > 0 && sampleratein != samplerateout - error(""" - Can't open duplex stream with mismatched samplerates (in: $sampleratein, out: $samplerateout). - Try changing your sample rate in your driver settings or open separate input and output - streams. + error( """ + Can't open duplex stream with mismatched samplerates (in: $sampleratein, out: $samplerateout). + Try changing your sample rate in your driver settings or open separate input and output + streams. + """, ) elseif inchans > 0 sampleratein @@ -164,33 +187,56 @@ used. Options: -* `eltype`: Sample type of the audio stream (defaults to Float32) -* `samplerate`: Sample rate (defaults to device sample rate) -* `latency`: Requested latency. Stream could underrun when too low, consider - using provided device defaults -* `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. -* `recover_xruns`: Attempt to recover from overruns and underruns by emptying and - filling the input and output buffers, respectively. Should result in - fewer xruns but could make each xrun more audible. True by default. - Only effects duplex streams. + - `eltype`: Sample type of the audio stream (defaults to Float32) + - `samplerate`: Sample rate (defaults to device sample rate) + - `latency`: Requested latency. Stream could underrun when too low, consider + using provided device defaults + - `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. + - `recover_xruns`: Attempt to recover from overruns and underruns by emptying and + filling the input and output buffers, respectively. Should result in + fewer xruns but could make each xrun more audible. True by default. + Only effects duplex streams. """ -function PortAudioStream(indev::PortAudioDevice, outdev::PortAudioDevice, - inchans=2, outchans=2; eltype=Float32, samplerate=-1, - latency=defaultlatency(indev, outdev), warn_xruns=false, recover_xruns=true) +function PortAudioStream( + indev::PortAudioDevice, + outdev::PortAudioDevice, + inchans = 2, + outchans = 2; + eltype = Float32, + samplerate = -1, + latency = defaultlatency(indev, outdev), + warn_xruns = false, + recover_xruns = true, +) if samplerate == -1 samplerate = combine_default_sample_rates( - inchans, indev.defaultsamplerate, - outchans, outdev.defaultsamplerate - ) + inchans, + indev.defaultsamplerate, + outchans, + outdev.defaultsamplerate, + ) end - PortAudioStream{eltype}(indev, outdev, inchans, outchans, samplerate, - latency, warn_xruns, recover_xruns) + PortAudioStream{eltype}( + indev, + outdev, + inchans, + outchans, + samplerate, + latency, + warn_xruns, + recover_xruns, + ) end # handle device names given as streams -function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, args...; kwargs...) +function PortAudioStream( + indevname::AbstractString, + outdevname::AbstractString, + args...; + kwargs..., +) indev = nothing outdev = nothing for d in devices() @@ -205,7 +251,9 @@ function PortAudioStream(indevname::AbstractString, outdevname::AbstractString, error("No device matching \"$indevname\" found.\nAvailable Devices:\n$(devnames())") end if outdev == nothing - error("No device matching \"$outdevname\" found.\nAvailable Devices:\n$(devnames())") + error( + "No device matching \"$outdevname\" found.\nAvailable Devices:\n$(devnames())", + ) end PortAudioStream(indev, outdev, args...; kwargs...) @@ -213,12 +261,17 @@ end # if one device is given, use it for input and output, but set inchans=0 so we # end up with an output-only stream -function PortAudioStream(device::Union{PortAudioDevice, AbstractString}, inchans=2, outchans=2; kwargs...) +function PortAudioStream( + device::Union{PortAudioDevice, AbstractString}, + inchans = 2, + outchans = 2; + kwargs..., +) PortAudioStream(device, device, inchans, outchans; kwargs...) end # use the default input and output devices -function PortAudioStream(inchans=2, outchans=2; kwargs...) +function PortAudioStream(inchans = 2, outchans = 2; kwargs...) inidx = Pa_GetDefaultInputDevice() indevice = PortAudioDevice(Pa_GetDeviceInfo(inidx), inidx) outidx = Pa_GetDefaultOutputDevice() @@ -249,21 +302,37 @@ end isopen(stream::PortAudioStream) = stream.stream != C_NULL SampledSignals.samplerate(stream::PortAudioStream) = stream.samplerate -eltype(stream::PortAudioStream{T}) where 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...) write(stream::PortAudioStream, args...) = write(stream.sink, args...) -write(sink::PortAudioStream, source::PortAudioStream, args...) = write(sink.sink, source.source, args...) +function write(sink::PortAudioStream, source::PortAudioStream, args...) + write(sink.sink, source.source, args...) +end function show(io::IO, stream::PortAudioStream) println(io, typeof(stream)) println(io, " Samplerate: ", samplerate(stream), "Hz") if nchannels(stream.sink) > 0 - print(io, "\n ", nchannels(stream.sink), " channel sink: \"", name(stream.sink), "\"") + print( + io, + "\n ", + nchannels(stream.sink), + " channel sink: \"", + name(stream.sink), + "\"", + ) end if nchannels(stream.source) > 0 - print(io, "\n ", nchannels(stream.source), " channel source: \"", name(stream.source), "\"") + print( + io, + "\n ", + nchannels(stream.source), + " channel source: \"", + name(stream.source), + "\"", + ) end end @@ -272,8 +341,7 @@ end # # Define our source and sink types -for (TypeName, Super) in ((:PortAudioSink, :SampleSink), - (:PortAudioSource, :SampleSource)) +for (TypeName, Super) in ((:PortAudioSink, :SampleSink), (:PortAudioSource, :SampleSource)) @eval mutable struct $TypeName{T} <: $Super name::String stream::PortAudioStream{T} @@ -296,17 +364,16 @@ 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, ::Type{PortAudioSink{T}}) where T +function show(io::IO, ::Type{PortAudioSink{T}}) where {T} print(io, "PortAudioSink{$T}") end -function show(io::IO, ::Type{PortAudioSource{T}}) where T +function show(io::IO, ::Type{PortAudioSource{T}}) where {T} print(io, "PortAudioSource{$T}") end @@ -314,17 +381,23 @@ function show(io::IO, stream::T) where {T <: Union{PortAudioSink, PortAudioSourc print(io, nchannels(stream), "-channel ", T, "(\"", stream.name, "\")") end -function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffset, framecount) +function SampledSignals.unsafe_write( + sink::PortAudioSink, + buf::Array, + frameoffset, + framecount, +) nwritten = 0 while nwritten < framecount - n = min(framecount-nwritten, CHUNKFRAMES) + n = min(framecount - nwritten, CHUNKFRAMES) # make a buffer of interleaved samples - transpose!(view(sink.chunkbuf, :, 1:n), - view(buf, (1:n) .+ nwritten .+ frameoffset, :)) + 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 - err = Pa_WriteStream(sink.stream.stream, sink.chunkbuf, n, - sink.stream.warn_xruns) + err = Pa_WriteStream(sink.stream.stream, sink.chunkbuf, n, sink.stream.warn_xruns) if err ∈ (PA_OUTPUT_UNDERFLOWED, PA_INPUT_OVERFLOWED) && sink.stream.recover_xruns recover_xrun(sink.stream) end @@ -334,20 +407,31 @@ function SampledSignals.unsafe_write(sink::PortAudioSink, buf::Array, frameoffse nwritten end -function SampledSignals.unsafe_read!(source::PortAudioSource, buf::Array, frameoffset, framecount) +function SampledSignals.unsafe_read!( + source::PortAudioSource, + buf::Array, + frameoffset, + framecount, +) nread = 0 while nread < framecount - n = min(framecount-nread, CHUNKFRAMES) + n = min(framecount - nread, CHUNKFRAMES) # TODO: if the stream is closed we just want to return a # shorter-than-requested frame count instead of throwing an error - err = Pa_ReadStream(source.stream.stream, source.chunkbuf, n, - source.stream.warn_xruns) + err = Pa_ReadStream( + source.stream.stream, + source.chunkbuf, + n, + source.stream.warn_xruns, + ) if err ∈ (PA_OUTPUT_UNDERFLOWED, PA_INPUT_OVERFLOWED) && source.stream.recover_xruns recover_xrun(source.stream) end # de-interleave the samples - transpose!(view(buf, (1:n) .+ nread .+ frameoffset, :), - view(source.chunkbuf, :, 1:n)) + transpose!( + view(buf, (1:n) .+ nread .+ frameoffset, :), + view(source.chunkbuf, :, 1:n), + ) nread += n end @@ -391,20 +475,20 @@ function seek_alsa_conf(searchdirs) isfile(joinpath(d, "alsa.conf")) end if confdir_idx === nothing - throw(ErrorException( - """ - Could not find ALSA config directory. Searched: - $(join(searchdirs, "\n")) + 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 searchdirs[confdir_idx] end @@ -413,11 +497,8 @@ function __init__() if Sys.islinux() envkey = "ALSA_CONFIG_DIR" if envkey ∉ keys(ENV) - ENV[envkey] = seek_alsa_conf([ - "/usr/share/alsa", - "/usr/local/share/alsa", - "/etc/alsa" - ]) + ENV[envkey] = + seek_alsa_conf(["/usr/share/alsa", "/usr/local/share/alsa", "/etc/alsa"]) end plugin_key = "ALSA_PLUGIN_DIR" diff --git a/src/libportaudio.jl b/src/libportaudio.jl index 70aa331..fea1634 100644 --- a/src/libportaudio.jl +++ b/src/libportaudio.jl @@ -21,20 +21,20 @@ const PA_OUTPUT_UNDERFLOWED = -10000 + 20 # sample format types const paFloat32 = PaSampleFormat(0x01) -const paInt32 = PaSampleFormat(0x02) -const paInt24 = PaSampleFormat(0x04) -const paInt16 = PaSampleFormat(0x08) -const paInt8 = PaSampleFormat(0x10) -const paUInt8 = PaSampleFormat(0x20) +const paInt32 = PaSampleFormat(0x02) +const paInt24 = PaSampleFormat(0x04) +const paInt16 = PaSampleFormat(0x08) +const paInt8 = PaSampleFormat(0x10) +const paUInt8 = PaSampleFormat(0x20) const paNonInterleaved = PaSampleFormat(0x80000000) const type_to_fmt = Dict{Type, PaSampleFormat}( Float32 => 1, - Int32 => 2, + Int32 => 2, # Int24 => 4, - Int16 => 8, - Int8 => 16, - UInt8 => 3 + Int16 => 8, + Int8 => 16, + UInt8 => 3, ) const PaStreamCallbackResult = Cint @@ -104,7 +104,7 @@ const pa_host_api_names = Dict{PaHostApiTypeId, String}( 11 => "WDMKS", 12 => "Jack", 13 => "WASAPI", - 14 => "AudioScience HPI" + 14 => "AudioScience HPI", ) mutable struct PaHostApiInfo @@ -117,8 +117,12 @@ mutable struct PaHostApiInfo end function Pa_GetHostApiInfo(i) - result = @locked ccall((:Pa_GetHostApiInfo, libportaudio), - Ptr{PaHostApiInfo}, (PaHostApiIndex,), i) + result = @locked ccall( + (:Pa_GetHostApiInfo, libportaudio), + Ptr{PaHostApiInfo}, + (PaHostApiIndex,), + i, + ) if result == C_NULL throw(BoundsError(Pa_GetHostApiInfo, i)) end @@ -143,19 +147,25 @@ end Pa_GetDeviceCount() = @locked ccall((:Pa_GetDeviceCount, libportaudio), PaDeviceIndex, ()) function Pa_GetDeviceInfo(i) - result = @locked ccall((:Pa_GetDeviceInfo, libportaudio), - Ptr{PaDeviceInfo}, (PaDeviceIndex,), i) + result = @locked ccall( + (:Pa_GetDeviceInfo, libportaudio), + Ptr{PaDeviceInfo}, + (PaDeviceIndex,), + i, + ) if result == C_NULL throw(BoundsError(Pa_GetDeviceInfo, i)) end unsafe_load(result) end -Pa_GetDefaultInputDevice() = @locked ccall((:Pa_GetDefaultInputDevice, libportaudio), - PaDeviceIndex, ()) +function Pa_GetDefaultInputDevice() + @locked ccall((:Pa_GetDefaultInputDevice, libportaudio), PaDeviceIndex, ()) +end -Pa_GetDefaultOutputDevice() = @locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), - PaDeviceIndex, ()) +function Pa_GetDefaultOutputDevice() + @locked ccall((:Pa_GetDefaultOutputDevice, libportaudio), PaDeviceIndex, ()) +end # Stream Functions @@ -189,81 +199,113 @@ end # streamPtr[] # end # -function Pa_OpenStream(inParams, outParams, - sampleRate, framesPerBuffer, - flags::PaStreamFlags, - callback, userdata) +function Pa_OpenStream( + inParams, + outParams, + sampleRate, + framesPerBuffer, + flags::PaStreamFlags, + callback, + userdata, +) streamPtr = Ref{PaStream}(0) - err = @locked ccall((:Pa_OpenStream, libportaudio), PaError, - (Ref{PaStream}, Ref{Pa_StreamParameters}, Ref{Pa_StreamParameters}, - Cdouble, Culong, PaStreamFlags, Ref{Cvoid}, - # it seems like we should be able to use Ref{T} here, with - # userdata::T above, and avoid the `pointer_from_objref` below. - # that's not working on 0.6 though, and it shouldn't really - # matter because userdata should be GC-rooted anyways - Ptr{Cvoid}), - streamPtr, inParams, outParams, - float(sampleRate), framesPerBuffer, flags, - callback === nothing ? C_NULL : callback, - userdata === nothing ? C_NULL : pointer_from_objref(userdata)) + err = @locked ccall( + (:Pa_OpenStream, libportaudio), + PaError, + ( + Ref{PaStream}, + Ref{Pa_StreamParameters}, + Ref{Pa_StreamParameters}, + Cdouble, + Culong, + PaStreamFlags, + Ref{Cvoid}, + # it seems like we should be able to use Ref{T} here, with + # userdata::T above, and avoid the `pointer_from_objref` below. + # that's not working on 0.6 though, and it shouldn't really + # matter because userdata should be GC-rooted anyways + Ptr{Cvoid}, + ), + streamPtr, + inParams, + outParams, + float(sampleRate), + framesPerBuffer, + flags, + callback === nothing ? C_NULL : callback, + userdata === nothing ? C_NULL : pointer_from_objref(userdata), + ) handle_status(err) streamPtr[] end function Pa_StartStream(stream::PaStream) - err = @locked ccall((:Pa_StartStream, libportaudio), PaError, - (PaStream,), stream) + err = @locked ccall((:Pa_StartStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_StopStream(stream::PaStream) - err = @locked ccall((:Pa_StopStream, libportaudio), PaError, - (PaStream,), stream) + err = @locked ccall((:Pa_StopStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_CloseStream(stream::PaStream) - err = @locked ccall((:Pa_CloseStream, libportaudio), PaError, - (PaStream,), stream) + err = @locked ccall((:Pa_CloseStream, libportaudio), PaError, (PaStream,), stream) handle_status(err) end function Pa_GetStreamReadAvailable(stream::PaStream) - avail = @locked ccall((:Pa_GetStreamReadAvailable, libportaudio), Clong, - (PaStream,), stream) + avail = @locked ccall( + (:Pa_GetStreamReadAvailable, libportaudio), + Clong, + (PaStream,), + stream, + ) avail >= 0 || handle_status(avail) avail end function Pa_GetStreamWriteAvailable(stream::PaStream) - avail = @locked ccall((:Pa_GetStreamWriteAvailable, libportaudio), Clong, - (PaStream,), stream) + avail = @locked ccall( + (:Pa_GetStreamWriteAvailable, libportaudio), + Clong, + (PaStream,), + stream, + ) avail >= 0 || handle_status(avail) avail end -function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer, - show_warnings=true) +function Pa_ReadStream(stream::PaStream, buf::Array, frames::Integer, show_warnings = true) # without disable_sigint I get a segfault with the error: # "error thrown and no exception handler available." # if the user tries to ctrl-C. Note I've still had some crash problems with # ctrl-C within `pasuspend`, so for now I think either don't use `pasuspend` or # don't use ctrl-C. err = disable_sigint() do - @tcall @locked ccall((:Pa_ReadStream, libportaudio), PaError, - (PaStream, Ptr{Cvoid}, Culong), - stream, buf, frames) + @tcall @locked ccall( + (:Pa_ReadStream, libportaudio), + PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, + buf, + frames, + ) end handle_status(err, show_warnings) err end -function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer, - show_warnings=true) +function Pa_WriteStream(stream::PaStream, buf::Array, frames::Integer, show_warnings = true) err = disable_sigint() do - @tcall @locked ccall((:Pa_WriteStream, libportaudio), PaError, - (PaStream, Ptr{Cvoid}, Culong), - stream, buf, frames) + @tcall @locked ccall( + (:Pa_WriteStream, libportaudio), + PaError, + (PaStream, Ptr{Cvoid}, Culong), + stream, + buf, + frames, + ) end handle_status(err, show_warnings) err @@ -279,16 +321,15 @@ end # end # # General utility function to handle the status from the Pa_* functions -function handle_status(err::Integer, show_warnings::Bool=true) +function handle_status(err::Integer, show_warnings::Bool = true) if err == PA_OUTPUT_UNDERFLOWED || err == PA_INPUT_OVERFLOWED if show_warnings - msg = @locked ccall((:Pa_GetErrorText, libportaudio), - Ptr{Cchar}, (PaError,), err) + msg = + @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) @warn("libportaudio: " * unsafe_string(msg)) end elseif err != PA_NO_ERROR - msg = @locked ccall((:Pa_GetErrorText, libportaudio), - Ptr{Cchar}, (PaError,), err) + msg = @locked ccall((:Pa_GetErrorText, libportaudio), Ptr{Cchar}, (PaError,), err) throw(ErrorException("libportaudio: " * unsafe_string(msg))) end end diff --git a/test/runtests.jl b/test/runtests.jl index dd2dcbb..39a5b88 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,13 +2,13 @@ using Logging: Debug using PortAudio -using PortAudio: +using PortAudio: combine_default_sample_rates, handle_status, - Pa_GetDefaultInputDevice, - Pa_GetDefaultOutputDevice, - Pa_GetDeviceInfo, - Pa_GetHostApiInfo, + Pa_GetDefaultInputDevice, + Pa_GetDefaultOutputDevice, + Pa_GetDeviceInfo, + Pa_GetHostApiInfo, Pa_Initialize, PA_OUTPUT_UNDERFLOWED, Pa_Terminate, @@ -62,7 +62,8 @@ if !isempty(PortAudio.devices()) stream = PortAudioStream(2, 0) buf = read(stream, 5s) close(stream) - @test size(buf) == (round(Int, 5 * samplerate(stream)), nchannels(stream.source)) + @test size(buf) == + (round(Int, 5 * samplerate(stream)), nchannels(stream.source)) println("Playing back recording...") PortAudioStream(0, 2) do stream write(stream, buf) @@ -73,20 +74,20 @@ if !isempty(PortAudio.devices()) source = stream.source @test sprint(show, typeof(sink)) == "PortAudioSink{Float32}" @test sprint(show, typeof(source)) == "PortAudioSource{Float32}" - @test sprint(show, sink) == "2-channel PortAudioSink{Float32}($(repr(default_indev)))" - @test sprint(show, source) == "2-channel PortAudioSource{Float32}($(repr(default_outdev)))" + @test sprint(show, sink) == + "2-channel PortAudioSink{Float32}($(repr(default_indev)))" + @test sprint(show, source) == + "2-channel PortAudioSource{Float32}($(repr(default_outdev)))" write(stream, stream, 5s) recover_xrun(stream) @test_throws ErrorException(""" Attempted to close PortAudioSink or PortAudioSource. Close the containing PortAudioStream instead - """ - ) close(sink) + """) close(sink) @test_throws ErrorException(""" Attempted to close PortAudioSink or PortAudioSource. Close the containing PortAudioStream instead - """ - ) close(source) + """) close(source) close(stream) @test !isopen(stream) @test !isopen(sink) @@ -95,55 +96,74 @@ if !isempty(PortAudio.devices()) end @testset "Samplerate-converting writing" begin stream = PortAudioStream(0, 2) - write(stream, SinSource(eltype(stream), samplerate(stream)*0.8, [220, 330]), 3s) - write(stream, SinSource(eltype(stream), samplerate(stream)*1.2, [220, 330]), 3s) + write( + stream, + SinSource(eltype(stream), samplerate(stream) * 0.8, [220, 330]), + 3s, + ) + write( + stream, + SinSource(eltype(stream), samplerate(stream) * 1.2, [220, 330]), + 3s, + ) close(stream) end @testset "Open Device by name" begin stream = PortAudioStream(default_indev, default_outdev) buf = read(stream, 0.001s) - @test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) + @test size(buf) == + (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) write(stream, buf) io = IOBuffer() show(io, stream) - @test occursin(""" - PortAudioStream{Float32} - Samplerate: 44100.0Hz - - 2 channel sink: "$default_outdev" - 2 channel source: "$default_indev\"""", String(take!(io))) + @test occursin( + """ + PortAudioStream{Float32} + Samplerate: 44100.0Hz + + 2 channel sink: "$default_outdev" + 2 channel source: "$default_indev\"""", + String(take!(io)), + ) close(stream) end @testset "Error handling" begin @test_throws ErrorException PortAudioStream("foobarbaz") @test_throws ErrorException PortAudioStream(default_indev, "foobarbaz") - @test_logs (:warn, "libportaudio: Output underflowed") handle_status(PA_OUTPUT_UNDERFLOWED) - @test_throws ErrorException("libportaudio: PortAudio not initialized") handle_status(-10000) + @test_logs (:warn, "libportaudio: Output underflowed") handle_status( + PA_OUTPUT_UNDERFLOWED, + ) + @test_throws ErrorException("libportaudio: PortAudio not initialized") handle_status( + -10000, + ) @test_throws ErrorException(""" Could not find ALSA config directory. Searched: - + 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. + """) seek_alsa_conf([]) + @test_throws ErrorException( """ - ) seek_alsa_conf([]) - @test_throws ErrorException(""" - Can't open duplex stream with mismatched samplerates (in: 0, out: 1). - Try changing your sample rate in your driver settings or open separate input and output - streams. - """ +Can't open duplex stream with mismatched samplerates (in: 0, out: 1). +Try changing your sample rate in your driver settings or open separate input and output +streams. +""", ) combine_default_sample_rates(1, 0, 1, 1) end # no way to check that the right data is actually getting read or written here, # but at least it's not crashing. @testset "Queued Writing" begin stream = PortAudioStream(0, 2) - buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream)) + buf = SampleBuf( + rand(eltype(stream), 48000, nchannels(stream.sink)) * 0.1, + samplerate(stream), + ) t1 = @async write(stream, buf) t2 = @async write(stream, buf) @test fetch(t1) == 48000 @@ -152,7 +172,10 @@ if !isempty(PortAudio.devices()) end @testset "Queued Reading" begin stream = PortAudioStream(2, 0) - buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, samplerate(stream)) + buf = SampleBuf( + rand(eltype(stream), 48000, nchannels(stream.source)) * 0.1, + samplerate(stream), + ) t1 = @async read!(stream, buf) t2 = @async read!(stream, buf) @test fetch(t1) == 48000 diff --git a/test/runtests_local.jl b/test/runtests_local.jl index 44cc42a..f98afb1 100644 --- a/test/runtests_local.jl +++ b/test/runtests_local.jl @@ -38,24 +38,28 @@ end end @testset "Samplerate-converting writing" begin stream = PortAudioStream(0, 2) - write(stream, SinSource(eltype(stream), samplerate(stream)*0.8, [220, 330]), 3s) - write(stream, SinSource(eltype(stream), samplerate(stream)*1.2, [220, 330]), 3s) + write(stream, SinSource(eltype(stream), samplerate(stream) * 0.8, [220, 330]), 3s) + write(stream, SinSource(eltype(stream), samplerate(stream) * 1.2, [220, 330]), 3s) flush(stream) close(stream) end @testset "Open Device by name" begin stream = PortAudioStream(default_indev, default_outdev) buf = read(stream, 0.001s) - @test size(buf) == (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) + @test size(buf) == + (round(Int, 0.001 * samplerate(stream)), nchannels(stream.source)) write(stream, buf) io = IOBuffer() show(io, stream) - @test occursin(""" - PortAudioStream{Float32} - Samplerate: 44100.0Hz - Buffer Size: 4096 frames - 2 channel sink: "$default_outdev" - 2 channel source: "$default_indev\"""", String(take!(io))) + @test occursin( + """ +PortAudioStream{Float32} + Samplerate: 44100.0Hz + Buffer Size: 4096 frames + 2 channel sink: "$default_outdev" + 2 channel source: "$default_indev\"""", + String(take!(io)), + ) close(stream) end @testset "Error on wrong name" begin @@ -65,7 +69,10 @@ end # but at least it's not crashing. @testset "Queued Writing" begin stream = PortAudioStream(0, 2) - buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.sink))*0.1, samplerate(stream)) + buf = SampleBuf( + rand(eltype(stream), 48000, nchannels(stream.sink)) * 0.1, + samplerate(stream), + ) t1 = @async write(stream, buf) t2 = @async write(stream, buf) @test fetch(t1) == 48000 @@ -75,7 +82,10 @@ end end @testset "Queued Reading" begin stream = PortAudioStream(2, 0) - buf = SampleBuf(rand(eltype(stream), 48000, nchannels(stream.source))*0.1, samplerate(stream)) + buf = SampleBuf( + rand(eltype(stream), 48000, nchannels(stream.source)) * 0.1, + samplerate(stream), + ) t1 = @async read!(stream, buf) t2 = @async read!(stream, buf) @test fetch(t1) == 48000