audio-reactive-led-strip/python/visualize.py

126 lines
4.5 KiB
Python

from __future__ import print_function
import time
import numpy as np
from scipy.ndimage.filters import gaussian_filter1d
import config
import dsp
import led
import microphone as mic
class Beat:
def __init__(self, pixels, speed):
self.pixels = pixels
self.speed = float(speed)
self.iteration = 0
def update_pixels(self):
self.iteration += 1
# Roll the pixel values to the right
# Temporal dithering is used to support fractional speed values
roll = int(self.speed)
roll += 1 if np.random.random() < self.speed - roll else 0
self.pixels = np.roll(self.pixels, roll, axis=0)
self.pixels[:roll] *= 0.0
# Apply Gaussian blur to create a dispersion effect
# Dispersion increases in strength over time
sigma = (2. * .14 * self.iteration / (config.N_PIXELS * self.speed))**4.
self.pixels = gaussian_filter1d(self.pixels, sigma, mode='constant')
# Exponentially decay the brightness over time
# The decay helps to direct viewer's focus to newer and brighter beats
self.pixels *= np.exp(2. * np.log(.1) / (self.speed * config.N_PIXELS))
self.pixels = np.round(self.pixels, decimals=2)
self.pixels = np.clip(self.pixels, 0, 255)
self.speed *= np.exp(2. * np.log(.8) / config.N_PIXELS)
def finished(self):
return np.array_equal(self.pixels, self.pixels * 0.0)
def rainbow(speed=10.0 / 5.0):
# Note: assumes array is N_PIXELS / 2 long
dt = np.pi / config.N_PIXELS
t = time.time() * speed
def r(t): return (np.sin(t + 0.0) + 1.0) * 1.0 / 2.0
def g(t): return (np.sin(t + (2.0 / 3.0) * np.pi) + 1.0) * 1.0 / 2.0
def b(t): return (np.sin(t + (4.0 / 3.0) * np.pi) + 1.0) * 1.0 / 2.0
x = np.tile(0.0, (config.N_PIXELS, 3))
for i in range(config.N_PIXELS):
x[i][0] = r(i * dt + t)
x[i][1] = g(i * dt + t)
x[i][2] = b(i * dt + t)
return x
def radiate(beats, energy, beat_speed=1.0, max_length=7, min_beats=1):
N_beats = len(beats[beats == True])
if N_beats > 0 and N_beats >= min_beats:
index_to_color = rainbow()
# Beat properties
beat_power = float(N_beats) / config.N_SUBBANDS
# energy = np.copy(energy)
# energy -= np.min(energy)
# energy /= (np.max(energy) - np.min(energy))
beat_brightness = np.round(256.0 / config.N_SUBBANDS)
beat_brightness *= np.sqrt(config.N_SUBBANDS / N_beats)
beat_brightness *= 1.3
beat_length = int(np.sqrt(beat_power) * max_length)
beat_length = max(beat_length, 2)
beat_pixels = np.tile(0.0, (config.N_PIXELS / 2, 3))
for i in range(len(beats)):
if beats[i]:
beat_color = np.round(index_to_color[i] * beat_brightness * energy[i] / 2.0)
beat_pixels[:beat_length] += beat_color
beat_pixels = np.clip(beat_pixels, 0.0, 255.0)
beat = Beat(beat_pixels, beat_speed)
radiate.beats = np.append(radiate.beats, beat)
# Pixels that will be displayed on the LED strip
pixels = np.zeros((config.N_PIXELS / 2, 3))
if len(radiate.beats):
pixels += sum([b.pixels for b in radiate.beats])
for b in radiate.beats:
b.update_pixels()
radiate.beats = [b for b in radiate.beats if not b.finished()]
pixels = np.append(pixels[::-1], pixels, axis=0)
pixels = np.clip(pixels, 0.0, 255.0)
led.pixels = np.round(pixels).astype(int)
led.update()
# Number of audio samples to read every time frame
samples_per_frame = int(config.MIC_RATE / config.FPS)
# Array containing the rolling audio sample window
y_roll = np.random.rand(config.N_ROLLING_HISTORY, samples_per_frame) / 100.0
def microphone_update(stream):
global y_roll
# Read new audio data
y = np.fromstring(stream.read(samples_per_frame), dtype=np.int16)
y = y / 2.0**15
# Construct rolling window of audio data
y_roll = np.roll(y_roll, -1, axis=0)
y_roll[-1, :] = np.copy(y)
y_data = np.concatenate(y_roll, axis=0)
# Take the real FFT with logarithmic bin spacing
xs, ys = dsp.rfft_log_partition(y_data,
subbands=config.N_SUBBANDS,
window=np.hamming,
fmin=1,
fmax=14000)
# Visualize the result
beats, energy, variance = dsp.beat_detect(ys)
radiate(beats, energy)
# Initial values for the radiate effect
radiate.beats = np.array([])
if __name__ == "__main__":
mic.start_stream(microphone_update)