PortAudio.jl/examples/waterfall_heatmap.jl
2021-06-01 13:44:23 -04:00

67 lines
1.8 KiB
Julia

using Makie
using PortAudio
using DSP
"""
Slide the values in the given matrix to the right by 1.
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]
end
end
"""
takes a block of audio, FFT it, and write it to the beginning of the buffer
"""
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))
end
function processblock!(src, buf, win, dispbufs, fftbuf, fftplan)
read!(src, buf)
for dispbuf in dispbufs
processbuf!(buf, win, dispbuf, fftbuf, fftplan)
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)
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
heatmaps = map(enumerate(IndexCartesian(), 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
center!(scene)
while isopen(scene[:screen])
processblock!(src, buf, dispbufs, fftbuf, fftplan)
for (hm, db) in zip(heatmaps, dispbufs)
hm[:heatmap] = db
end
render_frame(scene)
end