run JuliaFormatter (#77)
This commit is contained in:
parent
89020cafc7
commit
b3cddf5669
11 changed files with 453 additions and 262 deletions
9
.JuliaFormatter.toml
Normal file
9
.JuliaFormatter.toml
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
""")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
251
src/PortAudio.jl
251
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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue