Add many small performance optimizations
Over a dozen small performance optimizations * Memoization for linspace generation * Removed unnecessary copies * Limited the rate at which information is printed. Excessive `print()` output was causing issues for some SSH users
This commit is contained in:
parent
d6bf763c67
commit
35c26ca7bb
@ -11,7 +11,7 @@ import led
|
||||
_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.002, alpha_rise=0.002)
|
||||
_fps = dsp.ExpFilter(val=config.FPS, alpha_decay=0.2, alpha_rise=0.2)
|
||||
"""The low-pass filter used to estimate frames-per-second"""
|
||||
|
||||
|
||||
@ -41,6 +41,27 @@ def frames_per_second():
|
||||
return _fps.update(1000.0 / dt)
|
||||
|
||||
|
||||
def memoize(function):
|
||||
"""Provides a decorator for memoizing functions"""
|
||||
from functools import wraps
|
||||
memo = {}
|
||||
|
||||
@wraps(function)
|
||||
def wrapper(*args):
|
||||
if args in memo:
|
||||
return memo[args]
|
||||
else:
|
||||
rv = function(*args)
|
||||
memo[args] = rv
|
||||
return rv
|
||||
return wrapper
|
||||
|
||||
|
||||
@memoize
|
||||
def _normalized_linspace(size):
|
||||
return np.linspace(0, 1, size)
|
||||
|
||||
|
||||
def interpolate(y, new_length):
|
||||
"""Intelligently resizes the array by linearly interpolating the values
|
||||
|
||||
@ -60,8 +81,8 @@ def interpolate(y, new_length):
|
||||
"""
|
||||
if len(y) == new_length:
|
||||
return y
|
||||
x_old = np.linspace(0, 1, len(y))
|
||||
x_new = np.linspace(0, 1, new_length)
|
||||
x_old = _normalized_linspace(len(y))
|
||||
x_new = _normalized_linspace(new_length)
|
||||
z = np.interp(x_new, x_old, y)
|
||||
return z
|
||||
|
||||
@ -84,15 +105,15 @@ gain = dsp.ExpFilter(np.tile(0.01, config.N_FFT_BINS),
|
||||
def visualize_scroll(y):
|
||||
"""Effect that originates in the center and scrolls outwards"""
|
||||
global p
|
||||
y = np.copy(y)**2.0
|
||||
y = y**2.0
|
||||
gain.update(y)
|
||||
y /= gain.value
|
||||
y *= 255.0
|
||||
r = int(max(y[:len(y) // 3]))
|
||||
g = int(max(y[len(y) // 3: 2 * len(y) // 3]))
|
||||
b = int(max(y[2 * len(y) // 3:]))
|
||||
r = int(np.max(y[:len(y) // 3]))
|
||||
g = int(np.max(y[len(y) // 3: 2 * len(y) // 3]))
|
||||
b = int(np.max(y[2 * len(y) // 3:]))
|
||||
# Scrolling effect window
|
||||
p = np.roll(p, 1, axis=1)
|
||||
p[:, 1:] = p[:, :-1]
|
||||
p *= 0.98
|
||||
p = gaussian_filter1d(p, sigma=0.2)
|
||||
# Create new color originating at the center
|
||||
@ -140,22 +161,18 @@ def visualize_spectrum(y):
|
||||
"""Effect that maps the Mel filterbank frequencies onto the LED strip"""
|
||||
global _prev_spectrum
|
||||
y = np.copy(interpolate(y, config.N_PIXELS // 2))
|
||||
common_mode.update(gaussian_filter1d(y, sigma=2.0))
|
||||
common_mode.update(y)
|
||||
diff = y - _prev_spectrum
|
||||
_prev_spectrum = np.copy(y)
|
||||
r = gaussian_filter1d(y, sigma=0.5) - common_mode.value
|
||||
# g = gaussian_filter1d(y, sigma=0.5) - common_mode.value
|
||||
b = gaussian_filter1d(y, sigma=0.0) - common_mode.value
|
||||
# Update temporal filters
|
||||
r = r_filt.update(r)
|
||||
# g = g_filt.update(g)
|
||||
# Color channel mappings
|
||||
r = r_filt.update(y - common_mode.value)
|
||||
g = np.abs(diff)
|
||||
b = b_filt.update(b)
|
||||
b = b_filt.update(np.copy(y))
|
||||
# Mirror the color channels for symmetric output
|
||||
pixel_r = np.concatenate((r[::-1], r))
|
||||
pixel_g = np.concatenate((g[::-1], g))
|
||||
pixel_b = np.concatenate((b[::-1], b))
|
||||
output = np.array([pixel_r, pixel_g, pixel_b]) * 255.0
|
||||
r = np.concatenate((r[::-1], r))
|
||||
g = np.concatenate((g[::-1], g))
|
||||
b = np.concatenate((b[::-1], b))
|
||||
output = np.array([r, g,b]) * 255
|
||||
return output
|
||||
|
||||
|
||||
@ -168,31 +185,21 @@ mel_smoothing = dsp.ExpFilter(np.tile(1e-1, config.N_FFT_BINS),
|
||||
volume = dsp.ExpFilter(config.MIN_VOLUME_THRESHOLD,
|
||||
alpha_decay=0.02, alpha_rise=0.02)
|
||||
fft_window = np.hamming(int(config.MIC_RATE / config.FPS) * config.N_ROLLING_HISTORY)
|
||||
# Keeps track of the number of buffer overflows
|
||||
# Lots of buffer overflows could mean that FPS is set too high
|
||||
buffer_overflows = 1
|
||||
prev_fps_update = time.time()
|
||||
|
||||
|
||||
def microphone_update(stream):
|
||||
global y_roll, prev_rms, prev_exp
|
||||
# Retrieve and normalize the new audio samples
|
||||
try:
|
||||
y = np.fromstring(stream.read(samples_per_frame), dtype=np.int16)
|
||||
except IOError:
|
||||
y = y_roll[config.N_ROLLING_HISTORY - 1, :]
|
||||
global buffer_overflows
|
||||
print('Buffer overflows: {0}'.format(buffer_overflows))
|
||||
buffer_overflows += 1
|
||||
def microphone_update(audio_samples):
|
||||
global y_roll, prev_rms, prev_exp, prev_fps_update
|
||||
# Normalize samples between 0 and 1
|
||||
y = y / 2.0**15
|
||||
y = audio_samples / 2.0**15
|
||||
# Construct a rolling window of audio samples
|
||||
y_roll[:-1] = y_roll[1:]
|
||||
y_roll[-1, :] = np.copy(y)
|
||||
y_data = np.concatenate(y_roll, axis=0)
|
||||
volume.update(np.nanmean(y_data ** 2))
|
||||
|
||||
if volume.value < config.MIN_VOLUME_THRESHOLD:
|
||||
print('No audio input. Volume below threshold. Volume:', volume.value)
|
||||
y_data = np.concatenate(y_roll, axis=0).astype(np.float32)
|
||||
|
||||
vol = np.max(np.abs(y_data))
|
||||
if vol < config.MIN_VOLUME_THRESHOLD:
|
||||
print('No audio input. Volume below threshold. Volume:', vol)
|
||||
led.pixels = np.tile(0, (3, config.N_PIXELS))
|
||||
led.update()
|
||||
else:
|
||||
@ -204,13 +211,14 @@ def microphone_update(stream):
|
||||
y_padded = np.pad(y_data, (0, N_zeros), mode='constant')
|
||||
YS = np.abs(np.fft.rfft(y_padded)[:N // 2])
|
||||
# Construct a Mel filterbank from the FFT data
|
||||
mel = np.atleast_2d(np.abs(YS)).T * dsp.mel_y.T
|
||||
mel = np.atleast_2d(YS).T * dsp.mel_y.T
|
||||
# Scale data to values more suitable for visualization
|
||||
mel = np.mean(mel, axis=0)
|
||||
# mel = np.sum(mel, axis=0)
|
||||
mel = np.sum(mel, axis=0)
|
||||
mel = mel**2.0
|
||||
# Gain normalization
|
||||
mel_gain.update(np.max(gaussian_filter1d(mel, sigma=1.0)))
|
||||
mel = mel / mel_gain.value
|
||||
mel /= mel_gain.value
|
||||
mel = mel_smoothing.update(mel)
|
||||
# Map filterbank output onto LED strip
|
||||
output = visualization_effect(mel)
|
||||
@ -226,8 +234,12 @@ def microphone_update(stream):
|
||||
b_curve.setData(y=led.pixels[2])
|
||||
if config.USE_GUI:
|
||||
app.processEvents()
|
||||
|
||||
if config.DISPLAY_FPS:
|
||||
print('FPS {:.0f} / {:.0f}'.format(frames_per_second(), config.FPS))
|
||||
fps = frames_per_second()
|
||||
if time.time() - 0.5 > prev_fps_update:
|
||||
prev_fps_update = time.time()
|
||||
print('FPS {:.0f} / {:.0f}'.format(fps, config.FPS))
|
||||
|
||||
|
||||
# Number of audio samples to read every time frame
|
||||
|
Loading…
x
Reference in New Issue
Block a user