Last active
March 12, 2021 21:45
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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