Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.3'
- '1.4'
- '1.5'
- '1.6'
- '1'
- 'nightly'
os:
Expand Down
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ Suppressor = "0.2"

[extras]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DSP = "717857b8-e6f2-59f4-9121-6e50c889abd2"
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
LibSndFile = "b13ce0c6-77b0-50c6-a2db-140568b8d1a5"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Documenter", "LibSndFile", "Test"]
test = ["DSP", "Documenter", "FFTW", "GR", "LibSndFile", "Test"]
29 changes: 17 additions & 12 deletions examples/audiometer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ using PortAudio
Continuously read from the default audio input and plot an
ASCII level/peak meter
"""
function micmeter(metersize)
mic = PortAudioStream(1, 0; latency = 0.1)

signalmax = zero(eltype(mic))
println("Press Ctrl-C to quit")
while true
block = read(mic, 512)
blockmax = maximum(abs.(block)) # find the maximum value in the block
signalmax = max(signalmax, blockmax) # keep the maximum value ever
print("\r") # reset the cursor to the beginning of the line
printmeter(metersize, blockmax, signalmax)
function micmeter(seconds; metersize = 80)
PortAudioStream(1, 0; latency = 0.1) do mic
done = false
signalmax = zero(eltype(mic))
@sync begin
@async while !done
block = read(mic, 512)
blockmax = maximum(abs.(block)) # find the maximum value in the block
signalmax = max(signalmax, blockmax) # keep the maximum value ever
print("\r") # reset the cursor to the beginning of the line
printmeter(metersize, blockmax, signalmax)
end
sleep(seconds)
done = true
end
end
nothing
end

"""
Expand Down Expand Up @@ -52,4 +57,4 @@ function barcolor(metersize, position)
end
end

micmeter(80)
micmeter(5)
25 changes: 5 additions & 20 deletions examples/lilyplay.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
using Distributed, PortAudio
using PortAudio
using Base.Threads: @spawn

# Modified from Jiahao Chen's example in the obsolete AudioIO module.
# Will use first output device found in system's listing or DEFAULTDEVICE if set below
const DEFAULTDEVICE = -1

function paudio()
devs = PortAudio.devices()
if DEFAULTDEVICE < 0
devnum = findfirst(x -> x.maxoutchans > 0, devs)
(devnum == nothing) && error("No output device for audio found")
else
devnum = DEFAULTDEVICE + 1
end
return ostream = PortAudioStream(devs[devnum].name, 0, 2)
end

play(ostream, sample::Array{Float64, 1}) = write(ostream, sample)
play(ostr, sample::Array{Int64, 1}) = play(ostr, Float64.(sample))

struct Note{S <: Real, T <: Real}
pitch::S
Expand All @@ -35,14 +21,13 @@ function play(
if !A.sustained
decay_length = div(length(timesamples), 5)
v[(end - decay_length):(end - 1)] =
v[(end - decay_length):(end - 1)] .* LinRange(1, 0, decay_length)
v[(end - decay_length):(end - 1)] .* LinRange(1, 0, decay_length)
end
play(ostream, v)
sleep(A.duration)
write(ostream, v)
end

function parsevoice(melody::String; tempo = 132, beatunit = 4, lyrics = nothing)
ostream = paudio() # initialize audio for output
ostream = PortAudioStream(0, 2; warn_xruns = false) # initialize audio for output
lyrics_syllables = lyrics == nothing ? nothing : split(lyrics)
lyrics_syllables != nothing && (lyrics_syllables[end] *= "\n")
note_idx = 1
Expand Down
32 changes: 19 additions & 13 deletions examples/spectrum.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# plot a real-time spectrogram. This example is adapted from the GR example
# at http://gr-framework.org/examples/audio_ex.html

module SpectrumExample

using GR, PortAudio, SampledSignals, FFTW

const N = 1024
const stream = PortAudioStream(1, 0)
const buf = read(stream, N)
const fmin = 0Hz
const fmax = 10000Hz
const fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])]

while true
read!(stream, buf)
plot(fs, abs.(fft(buf)[fmin..fmax]), xlim = (fs[1], fs[end]), ylim = (0, 100))
function plot_spectrogram(seconds;
N = 1024,
fmin = 0Hz,
fmax = 10000Hz
)
PortAudioStream(1, 0) do stream
done = false
buf = read(stream, N)
fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])]
@sync begin
@async while !done
read!(stream, buf)
plot(fs, abs.(fft(buf)[fmin..fmax]), xlim = (fs[1], fs[end]), ylim = (0, 100))
end
sleep(seconds)
done = true
end
end
end

end
plot_spectrogram(5)
76 changes: 45 additions & 31 deletions examples/waterfall_heatmap.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Makie
using PortAudio
using DSP
using LinearAlgebra
using FFTW

"""
Slide the values in the given matrix to the right by 1.
Expand All @@ -16,52 +18,64 @@ end
"""
takes a block of audio, FFT it, and write it to the beginning of the buffer
"""
function processbuf!(readbuf, win, dispbuf, fftbuf, fftplan)
function processbuf!(readbuf, win, dispbuf, fftbuf, fftplan; D = 200)
readbuf .*= win
A_mul_B!(fftbuf, fftplan, readbuf)
mul!(fftbuf, fftplan, readbuf)
shift1!(dispbuf)
@. dispbuf[end:-1:1, 1] = log(clamp(abs(fftbuf[1:D]), 0.0001, Inf))
end

function processblock!(src, buf, win, dispbufs, fftbuf, fftplan)
function processblock!(src, buf, win, dispbufs, fftbuf, fftplan; D = 200)
read!(src, buf)
for dispbuf in dispbufs
processbuf!(buf, win, dispbuf, fftbuf, fftplan)
processbuf!(buf, win, dispbuf, fftbuf, fftplan; D = D)
end
end

N = 1024 # size of audio read
N2 = N ÷ 2 + 1 # size of rfft output
D = 200 # number of bins to display
M = 200 # amount of history to keep
src = PortAudioStream(1, 2)
buf = Array{Float32}(N) # buffer for reading
fftplan = plan_rfft(buf; flags = FFTW.EXHAUSTIVE)
fftbuf = Array{Complex{Float32}}(N2) # destination buf for FFT
dispbufs = [zeros(Float32, D, M) for i in 1:5, j in 1:5] # STFT bufs
win = gaussian(N, 0.125)
function waterfall_heatmap(seconds;
N = 1024, # size of audio read
D = 200, # number of bins to display
M = 200, # amount of history to keep
)
N2 = N ÷ 2 + 1 # size of rfft output
PortAudioStream(1, 2) do src
buf = Array{Float32}(undef, N) # buffer for reading
fftplan = plan_rfft(buf; flags = FFTW.EXHAUSTIVE)
fftbuf = Array{Complex{Float32}}(undef, 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
processblock!(src, buf, win, dispbufs, fftbuf, fftplan)
end
#pre-fill the display buffer so we can do a reasonable colormap
for _ in 1:M
processblock!(src, buf, win, dispbufs, fftbuf, fftplan; D = D)
end

heatmaps = map(enumerate(IndexCartesian(), dispbufs)) do ibuf
i = ibuf[1]
buf = ibuf[2]
heatmaps = map(zip(CartesianIndices(dispbufs), dispbufs)) do ibuf
i = ibuf[1]
buf = ibuf[2]

# some function of the 2D index and the value
heatmap(buf, offset = (i[2] * size(buf, 2), i[1] * size(buf, 1)))
end
# some function of the 2D index and the value
heatmap(buf, offset = (i[2] * size(buf, 2), i[1] * size(buf, 1)))
end

center!(scene)
center!(scene)

while isopen(scene[:screen])
processblock!(src, buf, dispbufs, fftbuf, fftplan)
for (hm, db) in zip(heatmaps, dispbufs)
hm[:heatmap] = db
done = false

@sync begin
@async while !done
processblock!(src, buf, win, dispbufs, fftbuf, fftplan)
for (hm, db) in zip(heatmaps, dispbufs)
hm[:heatmap] = db
end
render_frame(scene)
end
sleep(seconds)
done = true
end
end
render_frame(scene)
end

waterfall_heatmap(5)
84 changes: 49 additions & 35 deletions examples/waterfall_lines.jl
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
using Makie, GeometryTypes
using PortAudio
using FFTW
using CairoMakie

N = 1024 # size of audio read
N2 = N ÷ 2 + 1 # size of rfft output
D = 200 # number of bins to display
M = 100 # number of lines to draw
S = 0.5 # motion speed of lines
src = PortAudioStream(1, 2)
buf = Array{Float32}(N)
fftbuf = Array{Complex{Float32}}(N2)
magbuf = Array{Float32}(N2)
fftplan = plan_rfft(buf; flags = FFTW.EXHAUSTIVE)
function waterfall_lines(seconds;
N = 1024, # size of audio read
D = 200, # number of bins to display
M = 100, # number of lines to draw
S = 0.5, # motion speed of lines
)
PortAudioStream(1, 2) do src
N2 = N ÷ 2 + 1 # size of rfft output
buf = Array{Float32}(undef, N)
fftbuf = Array{Complex{Float32}}(undef, N2)
magbuf = Array{Float32}(undef, N2)
fftplan = plan_rfft(buf; flags = FFTW.EXHAUSTIVE)

scene = Scene(resolution = (500, 500))
ax = axis(0:0.1:1, 0:0.1:1, 0:0.1:0.5)
center!(scene)
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)
ls = map(1:M) do _
yoffset = to_node(to_value(scene[:time]))
offset = lift_node(scene[:time], yoffset) do t, yoff
Point3f0(0.0f0, (t - yoff) * S, 0.0f0)
end
l = lines(
linspace(0, 1, D),
0.0f0,
zeros(Float32, D),
offset = offset,
color = (:black, 0.1),
)
(yoffset, l)
end
done = false
@sync begin
@async begin
while !done
for (yoffset, line) in ls
isopen(scene[:screen]) || break
read!(src, buf)
A_mul_B!(fftbuf, fftplan, buf)
@. magbuf = log(clamp(abs(fftbuf), 0.0001, Inf)) / 10 + 0.5
line[:z] = magbuf[1:D]
push!(yoffset, to_value(scene[:time]))
end
end
end
sleep(seconds)
done = true
end
end
l = lines(
linspace(0, 1, D),
0.0f0,
zeros(Float32, D),
offset = offset,
color = (:black, 0.1),
)
(yoffset, l)
end

while isopen(scene[:screen])
for (yoffset, line) in ls
isopen(scene[:screen]) || break
read!(src, buf)
A_mul_B!(fftbuf, fftplan, buf)
@. magbuf = log(clamp(abs(fftbuf), 0.0001, Inf)) / 10 + 0.5
line[:z] = magbuf[1:D]
push!(yoffset, to_value(scene[:time]))
end
end
waterfall_lines(5)
Loading