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
