led-server/old/audiovis.py
2019-04-27 15:58:14 +02:00

293 lines
7.6 KiB
Python

#
# Audio Visualizer RGB lights, v1.0
# Author: Jared Sanson
# Created: 11/01/2013
#
# LED strip will be a blue colour when the room is calm,
# and progressively turn to red as the room gets noisy.
#
# The settings below should work fine,
# try adjusting the microphone volume/gain before you touch them!
#
# Connects to an arduino, which controls the RGB led strip.
#
# Required libraries:
# Python 2.7
# SciPy : http://www.scipy.org/
# Console : http://effbot.org/downloads/#console
# pySerial : http://pyserial.sourceforge.net/
# pyAudio : http://people.csail.mit.edu/hubert/pyaudio/
#
########## LIBRARIES ###########################################################
#import console as Console
import scipy.fftpack
import pyaudio
import time
import struct
import scipy
import math
from scipy import pi, signal
from scipy.fftpack import fft,rfft,rfftfreq,fftfreq
import serial
import struct
import time
########## VISUALIZATION SETTINGS ##############################################
# Loudness detect:
CHANNEL = 1 # frequency channel of the FFT to use (see console output to decide)
GAIN = 1.5 # audio gain (multiplier)
THRESHOLD = 0.15 # audio trigger threshold
#
ATTACK = 0.004 # amount of rowdz increase with loudness
DECAY = 0.003 # amount of rowdz decay
# Brightness:
MODULATION = 0.0 # amount of loudness flickering modulation
MIN_BRIGHTNESS = 0.5 # minimum brightness
# Hue mapping:
MIN_HUE = 200 # Aqua
MAX_HUE = 0 # Red
# Note that the hue mapping is actually a power function,
# so it will spend more time towards the MIN_HUE, and only a short time towards the MAX_HUE.
########## APPLICATION SETTINGS ################################################
#COM_PORT = 'COM3' # COM port to use, or None to run without an arudino
COM_PORT = None
# Audio capture settings
SAMPLE_RATE = 44100
BUFFER_SIZE = 2**11 # Changing this will change the frequency response of the algorithm
#CUTOFF_FREQ = 20000 # LPF freq (Hz)
########## SUPPORT FUNCTIONS ###################################################
def tobyte(i):
"""
Clip values that fall outside an unsigned byte
"""
i = int(i)
if i < 0: i = 0
if i > 255: i = 255
return i
def limit(val, vmin, vmax):
"""
Clip values that fall outside vmin & vmax
"""
if val < vmin: return vmin
if val > vmax: return vmax
return val
def mapval(val, minin, maxin, minout, maxout):
"""
Linear value mapping between in and out
"""
norm = (val-minin)/(maxin-minin)
return norm*(maxout-minout) + minout
def thresh(val, threshold):
"""
A bit hard to describe, but this will return 0
when val is below the threshold, and will
linearly map val to anything higher than threshold.
The effect being that above the threshold, louder
signals will have more of an effect.
"""
val -= threshold
if val < 0: val = 0
val *= (1.0/threshold)
return val
def hsv2rgb(h, s, v):
"""
Convert H,S,V to R,G,B
H: 0.0 to 360.0, S&V: 0.0 to 1.0
R,G,B: 0 to 255
"""
h = float(h)
s = float(s)
v = float(v)
h60 = h / 60.0
h60f = math.floor(h60)
hi = int(h60f) % 6
f = h60 - h60f
p = v * (1 - s)
q = v * (1 - f * s)
t = v * (1 - (1 - f) * s)
r, g, b = 0, 0, 0
if hi == 0: r, g, b = v, t, p
elif hi == 1: r, g, b = q, v, p
elif hi == 2: r, g, b = p, v, t
elif hi == 3: r, g, b = p, q, v
elif hi == 4: r, g, b = t, p, v
elif hi == 5: r, g, b = v, p, q
r, g, b = int(r * 255), int(g * 255), int(b * 255)
return r, g, b
def get_fft(data):
"""
Run the sample through a FFT, and normalize
"""
FFT = fft(data)
freqs = fftfreq(BUFFER_SIZE*2, 1.0/SAMPLE_RATE)
y = 20*scipy.log10(abs(FFT))/ 100
y = abs(FFT[0:len(FFT)/2])/1000
y = scipy.log(y) - 2
return (freqs,y)
ffts=[]
def smoothMemory(ffty,degree=3):
"""
Average samples. Taken from Python FFT tutorial
"""
global ffts
ffts = ffts+[ffty]
if len(ffts) <=degree: return ffty
ffts=ffts[1:]
return scipy.average(scipy.array(ffts),0)
bar_len = 70
def update_bars(x,y):
"""
Display a bar graph in the console
"""
for i,_ in enumerate(y):
a = int(min(max(y[i],0),1)*bar_len)
label = str(x[i])[:5]
label = ' '*(5-len(label)) + label
text = label +'[' + ('#'*a) + (' '*(bar_len-a)) + ']' + str(i)
print(0, i+3, text)
########## SUPPORT CLASSES #####################################################
class RGBController(object):
"""
Communicates with the Arduino
"""
def __init__(self, port):
self.ser = serial.Serial(port)
time.sleep(1)
def update(self, color):
#print "Update color to (R:%d G:%d B:%d)" % (color[0], color[1], color[2])
r = tobyte(color[0])
g = tobyte(color[1])
b = tobyte(color[2])
packet = struct.pack('cBBB', 'X', r,g,b)
#print packet
self.ser.write(packet)
########## CODE ################################################################
TITLE = "Audio Visualizer"
INK = 0x1f
# Set up console
#console = Console.getconsole(0)
#console.title(TITLE)
#console.text(0, 0, chr(0xfe) + ' ' + TITLE + ' ' + chr(0xfe))
#console.text(0, 1, chr(0xcd) * 80)
# Create LPF filter
# norm_pass = 2*math.pi*CUTOFF_FREQ/SAMPLE_RATE
# norm_stop = 1.5*norm_pass
# (N, Wn) = signal.buttord(wp=norm_pass, ws=norm_stop, gpass=2, gstop=30, analog=0)
# (b, a) = signal.butter(N, Wn, btype='low', analog=0, output='ba')
# b *= 1e3
# Open Audio stream (uses default audio adapter)
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16,channels=1,rate=SAMPLE_RATE,input=True,output=False,frames_per_buffer=BUFFER_SIZE)
# Open comms port to Arduino
if COM_PORT:
RGB = RGBController(COM_PORT)
########## GLOBAL VARIABLES ####################################################
red = 0
green = 0
blue = 0
noisiness = 0 # Noisiness level
########## VISUALIZATION LOOP ##################################################
while True:
## Part 1: Sample Audio ##
# Get audio sample
buf = stream.read(BUFFER_SIZE)
data = scipy.array(struct.unpack("%dh"%(BUFFER_SIZE),buf))
## Part 2: Perform FFT and Filtering ##
# Filter incoming data
#data = signal.lfilter(b,a,data)
# Generate FFT
freqs,y = get_fft(data)
# Average the samples
#y=smoothMemory(y,3)
# Normalize
y = y / 5
# Average into chunks of N
N = 25
yy = [scipy.average(y[n:n+N]) for n in range(0, len(y), N)]
yy = yy[:len(yy)/2] # Discard half of the samples, as they are mirrored
## Part 3: Algorithm ##
# Loudness detection
loudness = thresh(yy[CHANNEL] * GAIN, THRESHOLD)
# Noisiness meter
noisiness -= DECAY
noisiness += loudness * ATTACK
noisiness = limit(noisiness, 0.0, 1.0)
# Brightness modulation
modulation = MODULATION * limit(noisiness, 0.0, 1.0)
brightness = limit(MIN_BRIGHTNESS + (loudness * modulation), 0.0, 1.0)
# Hue modulation (power relationship)
mapping = (10 ** limit(noisiness, 0.0, 1.0)) / 10.0
mapping = mapping * 1.1 - 0.11
hue = mapval(mapping, 0.0, 1.0, MIN_HUE, MAX_HUE)
# Display colour
red,green,blue = hsv2rgb(hue,1.0,brightness)
if COM_PORT:
RGB.update([int(red),int(green),int(blue)])
# Debug information
labels = list(yy)
bars = list(yy)
labels.extend( ['-', 'loud','noise','map', 'brght', '-', 'hue','red','grn','blue'])
bars.extend( [0, loudness, noisiness, mapping, brightness, 0, hue/360.0, red/255.0,green/255.0,blue/255.0])
update_bars(labels,bars)
########## #####################################################################