Skip to content

Instantly share code, notes, and snippets.

@daniel-j
Last active March 12, 2021 21:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save daniel-j/401d3e7bb481bea55b789c97828fd0d2 to your computer and use it in GitHub Desktop.
Save daniel-j/401d3e7bb481bea55b789c97828fd0d2 to your computer and use it in GitHub Desktop.
Ardunio FFT audio visualizer, using a python script running on host | https://www.youtube.com/watch?v=k5hBVO-IvFo
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 3
#define LEDS 16
#define PACKET_SZ ( (LEDS * 3) + 3 )
// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LEDS, PIN, NEO_GRB + NEO_KHZ800);
unsigned char serial_buffer[PACKET_SZ];
unsigned int head = 0;
unsigned int start;
unsigned int checksum_1;
unsigned int checksum_0;
void setup() {
Serial.begin(115200);
strip.begin();
strip.setPixelColor(0, 0x110000);
strip.show();
}
void loop() {
if ( Serial.available() ) {
serial_buffer[head] = Serial.read();
if ( head >= (PACKET_SZ - 1) ) {
start = 0;
checksum_1 = head;
checksum_0 = head - 1;
head = 0;
}
else {
start = head + 1;
checksum_1 = head;
if ( head == 0 ) {
checksum_0 = PACKET_SZ - 1;
}
else {
checksum_0 = head - 1;
}
head++;
}
if ( serial_buffer[start] == 0xAA ) {
unsigned short sum = 0;
for ( int i = 0; i < checksum_0; i++ ) {
sum += serial_buffer[i];
}
if ( start > 0 ) {
for ( int i = start; i < PACKET_SZ; i++ ) {
sum += serial_buffer[i];
}
}
//Test if valid write packet
if ( ( ( (unsigned short)serial_buffer[checksum_0] << 8 ) | serial_buffer[checksum_1] ) == sum ) {
noInterrupts();
for( int i = 0; i < LEDS; i++ ) {
int idx = start + 1 + ( 3 * i );
if ( idx >= (PACKET_SZ - 1) ) {
idx = idx - PACKET_SZ;
}
strip.setPixelColor(i, strip.Color(serial_buffer[idx], serial_buffer[idx+1], serial_buffer[idx+2]));
}
strip.show();
interrupts();
}
}
}
}
#!/usr/bin/env python
# by djazz, using various bits of code found over the web
# works with both python2 and python3
# requires: python-numpy, python-smbus
# usage:
# this script accepts raw audio in this format: S16LE 44100 kHz Mono
# script-that-outputs-audio | ./visualizer.py
# echo raw.pcm | ./visualizer.py
# see usage suggestions below
# examples:
# ffmpeg -i http://icecast.djazz.se:8000/radio -f s16le -c:a pcm_s16le -ar 44100 -ac 1 - -nostats -loglevel info | python visualizer.py
import sys
from time import sleep
from struct import unpack
import numpy as np
import serial
import colorsys
import threading
num_leds = 16
ser = serial.Serial('/dev/ttyUSB0', 115200)
pixels = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
def writePixels():
serial_buf = bytearray((num_leds * 3) + 3)
serial_buf[0] = 0xAA
idx = 0
while (idx < (num_leds * 3)):
pixel_idx = int(idx / 3)
color = pixels[pixel_idx]
serial_buf[idx + 1] = color[0]
serial_buf[idx + 2] = color[1]
serial_buf[idx + 3] = color[2]
idx += 3
sum = 0
i = 0
while (i < (num_leds * 3) + 1):
sum += serial_buf[i]
i += 1
serial_buf[(num_leds * 3) + 1] = sum >> 8
serial_buf[(num_leds * 3) + 2] = sum & 0x00FF
ser.write(serial_buf)
ser.flush()
sleep(0.001)
def setPixelColor(pos, color):
pixels[pos][0] = color >> 16 & 0xFF
pixels[pos][1] = color >> 8 & 0xFF
pixels[pos][2] = color & 0xFF
# Return power array index corresponding to a particular frequency
def freq2bin(freq):
return int(freq / (sample_rate / (fftsize / 2.0) / 2.0))
# Initialize the arrays
bins = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
smoothbins = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] # for smoothing
weighting = [1, 0.9, 0.9, 0.9, 0.9, 0.9, 1, 1, 1, 1, 1, 1.5, 2, 3, 3, 3] # Change these according to taste
# Precalculate weighting bins
weighting = np.true_divide(weighting, 10000000)
# audio settings
sample_rate = 44100
no_channels = 1 # mono only
fftsize = 1024
chunksize = fftsize * 2 # s16le uses 2 bytes
# stdin acts as a file, read() method
# stdin = getattr(sys.stdin, 'buffer', sys.stdin)
print("Loading...")
data = sys.stdin.buffer.read(chunksize)
print("Visualizing...")
counter = 0
class readData(threading.Thread):
output_lock = threading.Lock()
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global data, bins, smoothbins
while len(data) != 0:
data = sys.stdin.buffer.read(chunksize)
if len(data) != chunksize:
print('weird chunk size', len(data))
continue
npdata = np.array(unpack("%dh" % (len(data) / 2), data), dtype='h')
fourier = np.fft.rfft(npdata)
fourier = np.delete(fourier, len(fourier) - 1)
power = np.abs(fourier)
lower_bound = 0
upper_bound = 100
with self.output_lock:
for i in range(len(bins)):
mean = np.mean(power[freq2bin(lower_bound):freq2bin(upper_bound)])
bins[i] = int(mean) if np.isnan(mean) == False else 0
lower_bound = upper_bound
upper_bound = upper_bound * 1.35
# print(i, freq2bin(lower_bound), freq2bin(upper_bound))
bins = np.multiply(bins, weighting)
def loop():
global counter, smoothbins
a = ''
for i in range(len(bins)):
smoothbins[i] *= 0.98
m = bins[i]
if m > smoothbins[i]:
smoothbins[i] = m
value = smoothbins[i]
if value > 1:
value = 1
brightness = min(140, value * 150) + 0.8
# piglow.ring(i, brightness)
# setPixelColor(i, brightness)
c = colorsys.hls_to_rgb((i / (len(bins) * 1.05)), brightness / 255.0, 1.0)
pixels[i][0] = int(c[0] * 255)
pixels[i][1] = int(c[1] * 255)
pixels[i][2] = int(c[2] * 255)
# c = colorsys.hls_to_rgb(((i + 0.3) / 8), brightness / 255.0, 1.0)
# pixels[i*2+1][0] = int(c[0] * 255)
# pixels[i*2+1][1] = int(c[1] * 255)
# pixels[i*2+1][2] = int(c[2] * 255)
s = "|"
s = s.ljust(int(value*20), "#")[0:10]
s = s.ljust(10)
a += s
a += "|"
# print bars
if counter % 10 == 0:
# print(a)
pass
counter += 1
writePixels()
thr1 = readData()
thr1.start()
while True:
loop()
print("Finished")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment