Scroll pHat HD Spectrum Analyzer

I recently ran across the Build a spectrum analyser with Scroll pHAT and pHAT DAC tutorial and since I had the component parts I thought I would put one together. The one difference was that I had a Scroll pHat HD and so I had a lot more LEDs to play with. So I wanted to share, in case anyone is interested, my updated code for the Scroll pHat HD. I had to do some experimenting with the weighting and I had to guess about how to break up the power_index ranges for the additional LEDs. I really don’t know much about Fast Fourier Transforms or audio so if folks have any thoughts on how I might break things up differently that would be awesome.

Mostly I just like the blinky lights

import sys
import wave
from struct import unpack

import alsaaudio as aa
import numpy as np
import scrollphathd

wavfile = wave.open(sys.argv[1], 'r')

sample_rate = wavfile.getframerate()
no_channels = wavfile.getnchannels()
chunk = 4096

output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
output.setchannels(no_channels)
output.setrate(sample_rate)
output.setformat(aa.PCM_FORMAT_S16_LE)
output.setperiodsize(chunk)

matrix = [0] * scrollphathd.DISPLAY_WIDTH
power = []
weighting = [1, 1, 2, 4, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16]
#
# weighting using a Fibonacci sequence. Makes the right end very active
# weighting = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
#
scrollphathd.set_brightness(0.75)
scrollphathd.rotate(degrees=180)


def power_index(val):
    return int(2 * chunk * val / sample_rate)


def compute_fft(data, chunk, sample_rate):
    global matrix
    data = unpack("%dh" % (len(data) / 2), data)
    data = np.array(data, dtype='h')

    fourier = np.fft.rfft(data)
    fourier = np.delete(fourier, len(fourier) - 1)

    power = np.abs(fourier)
    matrix[0] = int(np.mean(power[power_index(0):power_index(156):1]))
    matrix[1] = int(np.mean(power[power_index(156):power_index(313):1]))
    matrix[2] = int(np.mean(power[power_index(313):power_index(625):1]))
    matrix[3] = int(np.mean(power[power_index(625):power_index(1000):1]))
    matrix[4] = int(np.mean(power[power_index(1000):power_index(1500):1]))
    matrix[5] = int(np.mean(power[power_index(1500):power_index(2000):1]))
    matrix[6] = int(np.mean(power[power_index(2000):power_index(2500):1]))
    matrix[7] = int(np.mean(power[power_index(2500):power_index(3000):1]))
    matrix[8] = int(np.mean(power[power_index(3000):power_index(3500):1]))
    matrix[9] = int(np.mean(power[power_index(3500):power_index(4000):1]))
    matrix[10] = int(np.mean(power[power_index(4000):power_index(4500):1]))
    matrix[11] = int(np.mean(power[power_index(4500):power_index(5000):1]))
    matrix[12] = int(np.mean(power[power_index(5000):power_index(5500):1]))
    matrix[13] = int(np.mean(power[power_index(5500):power_index(6000):1]))
    matrix[14] = int(np.mean(power[power_index(6000):power_index(6500):1]))
    matrix[15] = int(np.mean(power[power_index(6500):power_index(7000):1]))
    matrix[16] = int(np.mean(power[power_index(7000):power_index(8000):1]))

    matrix = np.divide(np.multiply(matrix, weighting), 1000000)
    matrix = matrix.clip(0, scrollphathd.DISPLAY_HEIGHT)
    matrix = [float(m) for m in matrix]

    return matrix


data = wavfile.readframes(chunk)

while data != '':
    output.write(data)
    matrix = compute_fft(data, chunk, sample_rate)
    scrollphathd.set_graph(matrix, 0, scrollphathd.DISPLAY_HEIGHT)
    scrollphathd.show()
    data = wavfile.readframes(chunk)