Refactored all of the led_update_X functions

Led update functions no longer return a colorized array. Colorizing is
now done in led_visualization. It makes more sense for the led_update_X
functions to return 1D brightness arrays and then to apply color
afterwards. This should also improve performance somewhat. Also added a
new function leak_saturated_pixels() which allows saturated colors (>255
value) to leak into the adjacent color channels
This commit is contained in:
Scott Lawson 2016-10-23 16:46:52 -07:00
parent 9e615e0f35
commit a38a8e1680

View File

@ -136,6 +136,24 @@ def interpolate(y, new_length):
return z 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.ExponentialFilter(np.tile(1e-4, config.N_PIXELS), 0.01, 0.25) _EA_norm = dsp.ExponentialFilter(np.tile(1e-4, config.N_PIXELS), 0.01, 0.25)
"""Onset energy per-bin normalization constants """Onset energy per-bin normalization constants
@ -170,16 +188,10 @@ def update_leds_6(y):
Array containing the onset energies that should be visualized. Array containing the onset energies that should be visualized.
""" """
# Scale y to emphasize large spikes and attenuate small changes
# Exponents < 1.0 emphasize small changes and penalize large spikes
# Exponents > 1.0 emphasize large spikes and penalize small changes
y = np.copy(y)**1.25 y = np.copy(y)**1.25
# Use automatic gain control to normalize bin values
# Update normalization constants and then normalize each bin # Update normalization constants and then normalize each bin
_EA_norm.update(y) _EA_norm.update(y)
y /= _EA_norm.value y /= _EA_norm.value
"""Force saturated pixels to leak brighness into neighbouring pixels""" """Force saturated pixels to leak brighness into neighbouring pixels"""
def smooth(): def smooth():
for n in range(1, len(y) - 1): for n in range(1, len(y) - 1):
@ -188,58 +200,26 @@ def update_leds_6(y):
y[n] = 1.0 y[n] = 1.0
y[n - 1] += excess / 2.0 y[n - 1] += excess / 2.0
y[n + 1] += excess / 2.0 y[n + 1] += excess / 2.0
# Several iterations because the adjacent pixels could also be saturated # Several iterations because the adjacent pixels could also be saturated
for i in range(6): for i in range(6):
smooth() smooth()
# Update the onset energy low-pass filter and discard value too dim # Update the onset energy low-pass filter and discard value too dim
_EA_smooth.update(y) _EA_smooth.update(y)
_EA_smooth.value[_EA_smooth.value < .1] = 0.0 _EA_smooth.value[_EA_smooth.value < .1] = 0.0
# Return the pixels
# If some pixels are too bright, allow saturated pixels to become white pixels = np.copy(_EA_smooth.value)**1.5
color = rainbow(config.N_PIXELS) * 255.0
# Create the pixel array
pixels = np.tile(0, (y.shape[0], 3))
for i in range(config.N_PIXELS):
# Update LED strip pixel
pixels[i, :] = np.round(color[i, :] * _EA_smooth.value[i]**1.5)
# Leak excess red
excess_red = max(pixels[i, 0] - 255, 0)
pixels[i, 1] += excess_red
pixels[i, 2] += excess_red
# Leak excess green
excess_green = max(pixels[i, 1] - 255, 0)
pixels[i, 0] += excess_green
pixels[i, 2] += excess_green
# Leak excess blue
excess_blue = max(pixels[i, 2] - 255, 0)
pixels[i, 0] += excess_blue
pixels[i, 1] += excess_blue
return pixels return pixels
_EF_norm = dsp.ExponentialFilter(np.tile(1.0, config.N_PIXELS), 0.05, 0.9) _EF_norm = dsp.ExponentialFilter(np.tile(1.0, config.N_PIXELS), 0.05, 0.9)
_EF_smooth = dsp.ExponentialFilter(np.tile(1.0, config.N_PIXELS), 0.08, 0.9) _EF_smooth = dsp.ExponentialFilter(np.tile(1.0, config.N_PIXELS), 0.08, 0.9)
_prev_energy = 0.0 _prev_energy = 0.0
# Individually normalized energy flux # Individually normalized energy flux
def update_leds_5(y): def update_leds_5(y):
global _prev_energy global _prev_energy
# Scale y
y = np.copy(y) y = np.copy(y)
y = y ** 1.0
# Calculate raw energy flux
# Update previous energy
# Rectify energy flux
# Update the normalization constants
# Normalize the individual energy flux values
# Smooth the result using another smoothing filter
EF = y - _prev_energy EF = y - _prev_energy
_prev_energy = np.copy(y) _prev_energy = np.copy(y)
EF[EF < 0] = 0.0 EF[EF < 0] = 0.0
@ -248,38 +228,18 @@ def update_leds_5(y):
_EF_smooth.update(EF) _EF_smooth.update(EF)
# Cutoff values below 0.1 # Cutoff values below 0.1
_EF_smooth.value[_EF_smooth.value < 0.1] = 0.0 _EF_smooth.value[_EF_smooth.value < 0.1] = 0.0
pixels = np.copy(_EF_smooth.value)
color = rainbow(config.N_PIXELS) * 255.0
pixels = np.tile(0, (y.shape[0], 3))
for i in range(config.N_PIXELS):
pixels[i, :] = np.round(color[i, :] * _EF_smooth.value[i])
# Share excess red
excess_red = max(pixels[i, 0] - 255, 0)
pixels[i, 1] += excess_red
pixels[i, 2] += excess_red
# Share excess green
excess_green = max(pixels[i, 1] - 255, 0)
pixels[i, 0] += excess_green
pixels[i, 2] += excess_green
# Share excess blue
excess_blue = max(pixels[i, 2] - 255, 0)
pixels[i, 0] += excess_blue
pixels[i, 1] += excess_blue
return pixels return pixels
_energy_flux = dsp.ExponentialFilter(1.0, alpha_decay=0.025, alpha_rise=0.9) _energy_flux = dsp.ExponentialFilter(1.0, alpha_decay=0.025, alpha_rise=0.9)
# Modulate brightness of the entire strip with no individual addressing # Modulate brightness of the entire strip with no individual addressing
def update_leds_4(y): def update_leds_4(y):
y = np.copy(y)**1.0 y = np.copy(y)
energy = np.sum(y) energy = np.sum(y)
_energy_flux.update(energy) _energy_flux.update(energy)
energy /= _energy_flux.value pixels = energy / _energy_flux.value
color = rainbow(config.N_PIXELS) * 255.0
pixels = np.round((color * energy)).astype(int)
return pixels return pixels
@ -293,85 +253,29 @@ def update_leds_3(y):
_prev_energy = energy _prev_energy = energy
# Normalize energy flux # Normalize energy flux
_energy_flux.update(energy_flux) _energy_flux.update(energy_flux)
# Update pixels # Update and return pixels
pixels = np.roll(pixels, 1) pixels = np.roll(pixels, 1)
color = np.roll(color, 1, axis=0)
pixels *= 0.99
pixels[0] = energy_flux pixels[0] = energy_flux
return np.copy(pixels)
new_pixels = np.copy(np.round((color.T * pixels).T).astype(int))
for i in range(config.N_PIXELS):
# Share excess red
excess_red = max(new_pixels[i, 0] - 255, 0)
new_pixels[i, 1] += excess_red
new_pixels[i, 2] += excess_red
# Share excess green
excess_green = max(new_pixels[i, 1] - 255, 0)
new_pixels[i, 0] += excess_green
new_pixels[i, 2] += excess_green
# Share excess blue
excess_blue = max(new_pixels[i, 2] - 255, 0)
new_pixels[i, 0] += excess_blue
new_pixels[i, 1] += excess_blue
# Update LEDs
return new_pixels
# Energy based motion across the LED strip # Energy based motion across the LED strip
def update_leds_2(y): def update_leds_2(y):
global pixels, color global pixels
y = np.copy(y) y = np.copy(y)
# Calculate energy # Calculate energy
energy = np.sum(y**2.0) energy = np.sum(y**1.5)
onset_energy.update(energy) onset_energy.update(energy)
energy /= onset_energy.value energy /= onset_energy.value
# Update pixels # Update and return pixels
pixels = np.roll(pixels, 1) pixels = np.roll(pixels, 1)
color = np.roll(color, 1, axis=0)
pixels *= 0.99
pixels[0] = energy pixels[0] = energy
pixels -= 0.005 return np.copy(pixels)
pixels[pixels < 0.0] = 0.0
new_pixels = np.copy(np.round((color.T * pixels).T).astype(int))
for i in range(config.N_PIXELS):
# Share excess red
excess_red = max(new_pixels[i, 0] - 255, 0)
new_pixels[i, 1] += excess_red
new_pixels[i, 2] += excess_red
# Share excess green
excess_green = max(new_pixels[i, 1] - 255, 0)
new_pixels[i, 0] += excess_green
new_pixels[i, 2] += excess_green
# Share excess blue
excess_blue = max(new_pixels[i, 2] - 255, 0)
new_pixels[i, 0] += excess_blue
new_pixels[i, 1] += excess_blue
# Update LEDs
return new_pixels
def update_leds_1(y): def update_leds_1(y):
"""Display the raw onset spectrum on the LED strip""" """Display the raw onset spectrum on the LED strip"""
y = np.copy(y) return np.copy(y)**0.5
y = y ** 0.5
color = rainbow(y.shape[0]) * 255.0
pixels = np.copy(np.round((color.T * y).T).astype(int))
for i in range(y.shape[0]):
# Share excess red
excess_red = max(pixels[i, 0] - 255, 0)
pixels[i, 1] += excess_red
pixels[i, 2] += excess_red
# Share excess green
excess_green = max(pixels[i, 1] - 255, 0)
pixels[i, 0] += excess_green
pixels[i, 2] += excess_green
# Share excess blue
excess_blue = max(pixels[i, 2] - 255, 0)
pixels[i, 0] += excess_blue
pixels[i, 1] += excess_blue
return pixels
def microphone_update(stream): def microphone_update(stream):
@ -432,7 +336,7 @@ pixels = np.tile(0.0, config.N_PIXELS)
color = rainbow(config.N_PIXELS) * 255.0 color = rainbow(config.N_PIXELS) * 255.0
# Tracks average onset spectral energy # Tracks average onset spectral energy
onset_energy = dsp.ExponentialFilter(1.0, alpha_decay=0.1, alpha_rise=0.99) onset_energy = dsp.ExponentialFilter(1.0, alpha_decay=0.01, alpha_rise=0.65)
# Tracks the location of the spectral median # Tracks the location of the spectral median
median = dsp.ExponentialFilter(val=config.N_SUBBANDS / 2.0, median = dsp.ExponentialFilter(val=config.N_SUBBANDS / 2.0,
@ -462,18 +366,22 @@ y_roll = np.random.rand(config.N_ROLLING_HISTORY, samples_per_frame) / 100.0
# Low pass filter for the LEDs being output to the strip # Low pass filter for the LEDs being output to the strip
pixels_filt = dsp.ExponentialFilter(np.tile(0., (config.N_PIXELS, 3)), .2, .9) pixels_filt = dsp.ExponentialFilter(np.tile(0., (config.N_PIXELS, 3)), .35, .9)
# This is the function responsible for updating LED values # This is the function responsible for updating LED values
# Edit this function to change the visualization # Edit this function to change the visualization
def led_visualization(onset_values): def led_visualization(onset_values):
# Visualizations that we want to use (normalized to [0, 1]) # Visualizations that we want to use (normalized to ~[0, 1])
pixels_A = update_leds_6(onset_values).astype(float) / 255.0 pixels_A = update_leds_6(onset_values)
pixels_B = update_leds_4(onset_values).astype(float) / 255.0 pixels_B = update_leds_4(onset_values)
# Take the product of the visualizations and scale by 255 # Take the product of the visualizations and scale by 255
pixels = pixels_A * pixels_B pixels = pixels_A * pixels_B
pixels *= 255.0 # Combine pixels with color map
color = rainbow(onset_values.shape[0]) * 255.0
pixels = (pixels * color.T).T
pixels = leak_saturated_pixels(pixels)
pixels = np.clip(pixels, 0.0, 255.0)
# Apply low-pass filter to the output # Apply low-pass filter to the output
pixels_filt.update(np.copy(pixels)) pixels_filt.update(np.copy(pixels))
# Display values on the LED strip # Display values on the LED strip