Deleted sandbox.py and visualize.py

This commit is contained in:
Scott Lawson 2016-11-13 01:09:14 -08:00
parent ff6d7719af
commit 2bad2cd4d4
2 changed files with 0 additions and 655 deletions

View File

@ -1,526 +0,0 @@
from __future__ import print_function
from __future__ import division
import time
import numpy as np
from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
import config
import microphone
import dsp
import led
import melbank
from scipy.ndimage.filters import gaussian_filter1d
from scipy.signal import argrelextrema
def rainbow(length, speed=1.0 / 5.0):
"""Returns a rainbow colored array with desired length
Returns a rainbow colored array with shape (length, 3).
Each row contains the red, green, and blue color values between 0 and 1.
Example format:
[[red0, green0, blue0],
[red1, green1, blue1],
...
[redN, greenN, blueN]]
Parameters
----------
length : int
The length of the rainbow colored array that should be returned
speed : float
Value indicating the speed that the rainbow colors change.
If speed > 0, then successive calls to this function will return
arrays with different colors assigned to the indices.
If speed == 0, then this function will always return the same colors.
Returns
-------
x : numpy.array
np.ndarray with shape (length, 3).
Columns denote the red, green, and blue color values respectively.
Each color is a float between 0 and 1.
"""
dt = 2.0 * np.pi / length
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, (length, 3))
for i in range(length):
x[i][0] = r(i * dt + t)
x[i][1] = g(i * dt + t)
x[i][2] = b(i * dt + t)
return x
def rainbow_gen(length, speed=1./5., center=0.5, width=0.5, f=[1, 1, 1]):
dt = 2.0 * np.pi / length
t = time.time() * speed
phi = 2.0 / 3.0 * np.pi
def r(t): return np.clip(np.sin(f[0] * t + 1. * phi) * width + center, 0., 1.)
def g(t): return np.clip(np.sin(f[1] * t + 2. * phi) * width + center, 0., 1.)
def b(t): return np.clip(np.sin(f[2] * t + 3. * phi) * width + center, 0., 1.)
x = np.tile(0.0, (length, 3))
for i in range(length):
x[i][0] = r(i * dt + t)
x[i][1] = g(i * dt + t)
x[i][2] = b(i * dt + t)
return x
_time_prev = time.time() * 1000.0
"""The previous time that the frames_per_second() function was called"""
_fps = dsp.ExpFilter(val=config.FPS, alpha_decay=0.05, alpha_rise=0.05)
"""The low-pass filter used to estimate frames-per-second"""
def frames_per_second():
"""Return the estimated frames per second
Returns the current estimate for frames-per-second (FPS).
FPS is estimated by measured the amount of time that has elapsed since
this function was previously called. The FPS estimate is low-pass filtered
to reduce noise.
This function is intended to be called one time for every iteration of
the program's main loop.
Returns
-------
fps : float
Estimated frames-per-second. This value is low-pass filtered
to reduce noise.
"""
global _time_prev, _fps
time_now = time.time() * 1000.0
dt = time_now - _time_prev
_time_prev = time_now
if dt == 0.0:
return _fps.value
return _fps.update(1000.0 / dt)
def update_plot_1(x, y):
"""Updates pyqtgraph plot 1
Parameters
----------
x : numpy.array
1D array containing the X-axis values that should be plotted.
There should only be one X-axis array.
y : numpy.ndarray
Array containing each of the Y-axis series that should be plotted.
Each row of y corresponds to a Y-axis series. The columns in each row
are the values that should be plotted.
Returns
-------
None
"""
global curves, p1
colors = rainbow(config.N_CURVES) * 255.0
for i in range(config.N_CURVES):
#curves[i].setPen((colors[i][0], colors[i][1], colors[i][2]))
curves[i].setData(x=x, y=y[i])
p1.autoRange()
p1.setRange(yRange=(0.0, 2.0))
def interpolate(y, new_length):
"""Intelligently resizes the array by linearly interpolating the values
Parameters
----------
y : np.array
Array that should be resized
new_length : int
The length of the new interpolated array
Returns
-------
z : np.array
New array with length of new_length that contains the interpolated
values of y.
"""
x_old = np.linspace(0, 1, len(y))
x_new = np.linspace(0, 1, new_length)
z = np.interp(x_new, x_old, y)
return z
def leak_saturated_pixels(pixels):
pixels = np.copy(pixels)
for i in range(pixels.shape[0]):
excess_red = max(pixels[i, 0] - 255.0, 0.0)
excess_green = max(pixels[i, 1] - 255.0, 0.0)
excess_blue = max(pixels[i, 2] - 255.0, 0.0)
# Share excess red
pixels[i, 1] += excess_red
pixels[i, 2] += excess_red
# Share excess green
pixels[i, 0] += excess_green
pixels[i, 2] += excess_green
# Share excess blue
pixels[i, 0] += excess_blue
pixels[i, 1] += excess_blue
return pixels
_EA_norm = dsp.ExpFilter(np.tile(1e-4, config.N_PIXELS), 0.01, 0.85)
"""Onset energy per-bin normalization constants
This filter is responsible for individually normalizing the onset bin energies.
This is used to obtain per-bin automatic gain control.
"""
_EA_smooth = dsp.ExpFilter(np.tile(1.0, config.N_PIXELS), 0.25, 0.80)
"""Asymmetric exponential low-pass filtered onset energies
This filter is responsible for smoothing the displayed onset energies.
Asymmetric rise and fall constants allow the filter to quickly respond to
increases in onset energy, while slowly responded to decreases.
"""
# Individually normalized energy spike method
# Works well with GAMMA_CORRECTION = True
# This is one of the best visualizations, but doesn't work for everything
def update_leds_6(y):
"""Visualization using per-bin normalized onset energies
Visualizes onset energies by normalizing each frequency bin individually.
The normalized bins are then processed and displayed onto the LED strip.
This function visualizes the onset energies by individually normalizing
each onset energy bin. The normalized onset bins are then scaled and
Parameters
----------
y : numpy.array
Array containing the onset energies that should be visualized.
"""
y = np.abs(y)**1.25
# Update normalization constants and then normalize each bin
_EA_norm.update(y)
y /= _EA_norm.value
# Update the onset energy low-pass filter and discard value too dim
_EA_smooth.update(y)
_EA_smooth.value[_EA_smooth.value < .1] = 0.0
# Return the pixels
pixels = np.copy(_EA_smooth.value)**1.5
return pixels
_EF_norm = dsp.ExpFilter(np.tile(1.0, config.N_PIXELS), 0.05, 0.9)
_EF_smooth = dsp.ExpFilter(np.tile(1.0, config.N_PIXELS), 0.08, 0.9)
_prev_energy = 0.0
# Individually normalized energy flux
def update_leds_5(y):
global _prev_energy
y = np.copy(y)
EF = np.max(y - _prev_energy, 0.0)
_prev_energy = np.copy(y)
_EF_norm.update(EF)
EF /= _EF_norm.value
_EF_smooth.update(EF)
# Cutoff values below 0.1
_EF_smooth.value[_EF_smooth.value < 0.1] = 0.0
pixels = np.copy(_EF_smooth.value)
return pixels
_prev_E = np.tile(.1, config.N_PIXELS)
_EF_N = dsp.ExpFilter(np.tile(.1, config.N_PIXELS), 0.01, 0.9)
def update_leds_5(y):
global _prev_E
y = np.copy(y)
current_E = y**2.0
EF = current_E - _prev_E
_prev_E = np.copy(current_E)
EF[EF < 0.0] = 0.0
_EF_N.update(EF)
EF /= _EF_N.value
EF[current_E < 0.02] = 0.0
return EF
_energy_norm = dsp.ExpFilter(10.0, alpha_decay=.15, alpha_rise=.9)
_energy_smooth = dsp.ExpFilter(10.0, alpha_decay=0.1, alpha_rise=0.8)
# Modulate brightness by relative average rectified onset flux
def update_leds_4(y):
global _prev_energy
energy = np.sum(y**1.0)
EF = max(energy - _prev_energy, 0.0)
_prev_energy = energy
_energy_norm.update(EF)
_energy_smooth.update(min(EF / _energy_norm.value, 1.0))
pixels = np.tile(_energy_smooth.value, y.shape[0])
return pixels
# Energy flux based motion across the LED strip
def update_leds_3(y):
global pixels, _prev_energy
y = np.copy(y)
# Calculate energy flux
energy = np.sum(y)
energy_flux = max(energy - _prev_energy, 0)
_prev_energy = energy
# Normalize energy flux
_energy_norm.update(energy_flux)
# Update and return pixels
pixels = np.roll(pixels, 1)
pixels[0] = energy_flux
return np.copy(pixels)
# Energy based motion across the LED strip
def update_leds_2(y):
global pixels
y = np.copy(y)
# Calculate energy
energy = np.sum(y**1.5)
onset_energy.update(energy)
energy /= onset_energy.value
# Update and return pixels
pixels = np.roll(pixels, 1)
pixels[0] = energy
return np.copy(pixels)
def update_leds_1(y):
"""Display the raw onset spectrum on the LED strip"""
return np.copy(y)**0.75
YS_peak = dsp.ExpFilter(1.0, alpha_decay=0.005, alpha_rise=0.95)
def microphone_update(stream):
global y_roll
# Retrieve new audio samples and construct the rolling window
y = np.fromstring(stream.read(samples_per_frame), dtype=np.int16)
y = y / 2.0**15
y_roll = np.roll(y_roll, -1, axis=0)
y_roll[-1, :] = np.copy(y)
y_data = np.concatenate(y_roll, axis=0)
# # Calculate onset detection functions
# SF, NWPD, RCD = dsp.onset(y_data)
# # Apply Gaussian blur to improve agreement between onset functions
# SF = gaussian_filter1d(SF, 1.0)
# NWPD = gaussian_filter1d(NWPD, 1.0)
# RCD = gaussian_filter1d(RCD, 1.0)
# # Update and normalize peak followers
# SF_peak.update(np.max(SF))
# NWPD_peak.update(np.max(NWPD))
# RCD_peak.update(np.max(RCD))
# SF /= SF_peak.value
# NWPD /= NWPD_peak.value
# RCD /= RCD_peak.value
# # Normalize and update onset spectrum
# onset = SF + NWPD + RCD
# onset_peak.update(np.max(onset))
# onset /= onset_peak.value
# onsets.update(onset)
# # Map the onset values to LED strip pixels
# if len(onsets.value) != config.N_PIXELS:
# onset_values = interpolate(onsets.value, config.N_PIXELS)
# else:
# onset_values = np.copy(onsets.value)
# brightness = led_visualization(onset_values)
XS, YS = dsp.fft(y_data, window=np.hamming)
YS = YS[XS >= 0.0]
XS = XS[XS >= 0.0]
YS = np.atleast_2d(np.abs(YS)).T * dsp.mel_y.T
YS = np.sum(YS, axis=0)**2.0
#YS = gaussian_filter1d(YS, 2.0)
YS = np.diff(YS, n=0)
YS_peak.update(np.max(YS))
YS /= YS_peak.value
if len(YS) != config.N_PIXELS:
YS = interpolate(YS, config.N_PIXELS)
#YS = led_visualization(YS)
YS = led_vis3(YS)
# Plot the onsets
#plot_x = np.array(range(1, len(onsets.value) + 1))
plot_x = np.array(range(1, len(YS) + 1))
#plot_y = [onsets.value**i for i in np.linspace(2.0, 0.25, config.N_CURVES)]
plot_y = [YS]
update_plot_1(plot_x, plot_y)
app.processEvents()
print('FPS {:.0f} / {:.0f}'.format(frames_per_second(), config.FPS))
# Create plot and window
app = QtGui.QApplication([])
win = pg.GraphicsWindow('Audio Visualization')
win.resize(300, 200)
win.setWindowTitle('Audio Visualization')
# Create plot 1 containing config.N_CURVES
p1 = win.addPlot(title='Onset Detection Function')
p1.setLogMode(x=False)
curves = []
colors = rainbow(config.N_CURVES) * 255.0
for i in range(config.N_CURVES):
curve = p1.plot(pen=(colors[i][0], colors[i][1], colors[i][2]))
curves.append(curve)
# Pixel values for each LED
pixels = np.tile(0.0, config.N_PIXELS)
# Used to colorize the LED strip
color = rainbow(config.N_PIXELS) * 255.0
# Tracks average onset spectral energy
onset_energy = dsp.ExpFilter(1.0, alpha_decay=0.01, alpha_rise=0.65)
# Tracks the location of the spectral median
median = dsp.ExpFilter(val=config.N_SUBBANDS / 2.0,
alpha_decay=0.1, alpha_rise=0.1)
# Smooths the decay of the onset detection function
onsets = dsp.ExpFilter(val=np.tile(0.0, (config.N_SUBBANDS)),
alpha_decay=0.15, alpha_rise=0.75)
# Peak followers used for normalization
SF_peak = dsp.ExpFilter(1.0, alpha_decay=0.01, alpha_rise=0.99)
NWPD_peak = dsp.ExpFilter(1.0, alpha_decay=0.01, alpha_rise=0.99)
RCD_peak = dsp.ExpFilter(1.0, alpha_decay=0.01, alpha_rise=0.99)
onset_peak = dsp.ExpFilter(0.1, alpha_decay=0.002, alpha_rise=0.5)
# 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
# Which LED visualization to use
# update_leds_1 = raw onset spectrum without normalization (GAMMA = True)
# update_leds_2 = energy average chase effect (GAMMA = True)
# update_leds_3 = energy flux chase effect (GAMMA = True)
# update_leds_4 = brightness modulation effect (GAMMA = True)
# update_leds_5 = energy flux normalized per-bin spectrum (GAMMA = True)
# update_leds_6 = energy average normalized per-bin spectrum (GAMMA = True)
# Low pass filter for the LEDs being output to the strip
pixels_filt = dsp.ExpFilter(np.tile(0., (config.N_PIXELS, 3)), .14, .9)
def hyperbolic_tan(x):
return 1.0 - 2.0 / (np.exp(2.0 * x) + 1.0)
def bloom_peaks(x, width=3, blur_factor=1.0):
peaks = argrelextrema(x, np.greater)[0]
y = x * 0.0
if len(peaks) == 0:
return y
for peak in peaks:
min_idx = max(peak - width, 0)
max_idx = min(peak + width, len(x) - 1)
for i in range(min_idx, max_idx):
y[i] = x[i]
y = gaussian_filter1d(y, blur_factor)
return y
# This is the function responsible for updating LED values
# Edit this function to change the visualization
def led_visualization(onset_values):
# Visualizations that we want to use (normalized to ~[0, 1])
#pixels_A = update_leds_6(onset_values)
#pixels_B = update_leds_4(onset_values)
# Combine the effects by taking the product
#brightness = pixels_A #* pixels_B
brightness = update_leds_6(onset_values**2.0)
brightness = gaussian_filter1d(brightness, 4.0)
#brightness = hyperbolic_tan(brightness)
brightness = bloom_peaks(brightness)**2.
# Combine pixels with color map
color = rainbow_gen(onset_values.shape[0],
speed=1.,
center=0.5,
width=0.5,
f=[1.1, .5, .2]) * 255.0
color = np.tile(255.0, (config.N_PIXELS, 3))
# color = rainbow(onset_values.shape[0]) * 255.0
pixels = (brightness * color.T).T
pixels = leak_saturated_pixels(pixels)
pixels = np.clip(pixels, 0., 255.)
# Apply low-pass filter to the output
pixels_filt.update(np.copy(pixels))
# Display values on the LED strip
led.pixels = np.round(pixels_filt.value).astype(int)
led.update()
return brightness
mean_energy = dsp.ExpFilter(0.1, alpha_decay=0.05, alpha_rise=0.05)
def led_vis2(x):
energy = np.mean(x**.5)
mean_energy.update(energy)
energy = energy / mean_energy.value - 1.0
edge = np.exp(-10 * np.linspace(0, 1, len(x)))
edge = edge + edge[::-1]
edge *= max(energy, 0)
edge /= 2.0
x = gaussian_filter1d(x, 3.0)
x = update_leds_6(x)
red = bloom_peaks(x**1.0, width=1, blur_factor=1.5)
green = bloom_peaks(x**1.0, width=2, blur_factor=0.5)
blue = bloom_peaks(x**1.0, width=1, blur_factor=0.5)
# Set LEDs
color = np.tile(0.0, (3, config.N_PIXELS))
color[0, :] = 1.0*edge + red*1.0
color[1, :] = 1.2*edge + green*1.0
color[2, :] = 1.5*edge + blue*1.0
color = color.T * 255.0
pixels_filt.update(color)
led.pixels = np.round(pixels_filt.value).astype(int)
led.update()
return (color[:, 0] + color[:, 1] + color[:, 2]) / (3. * 255.0)
N = 60
E = []
for i in range(0, N):
alpha_decay = 0.01 * (float(i + 1) / (N + 1.0))**2.0
alpha_rise = alpha_decay
E.append(dsp.ExpFilter(.1, alpha_decay, alpha_rise))
def led_vis3(x):
energy = np.mean(x**.5)
pixels = np.tile(0.0, config.N_PIXELS)
for i in range(N):
E[i].update(energy)
pixels[i] = hyperbolic_tan(max(energy / E[i].value - 1.0, 0))
color = np.tile(0.0, (3, config.N_PIXELS))
color[0, :] = pixels
color[1, :] = pixels
color[2, :] = pixels
color = color.T * 255.0
pixels_filt.update(color)
led.pixels = np.round(pixels_filt.value).astype(int)
led.update()
return (color[:, 0] + color[:, 1] + color[:, 2]) / (3. * 255.0)
if __name__ == '__main__':
led.update()
microphone.start_stream(microphone_update)

View File

@ -1,129 +0,0 @@
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(.5) / (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(.95) / 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=.6, 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(y_data, window=np.hamming)
ys = ys[(xs >= config.MIN_FREQUENCY) * (xs <= config.MAX_FREQUENCY)]
xs = xs[(xs >= config.MIN_FREQUENCY) * (xs <= config.MAX_FREQUENCY)]
xs, ys = dsp.log_partition(xs, ys, config.N_SUBBANDS)
# 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)