From 859d1b20ea46aa4a81f6d38a5974fa27ba0a0e8a Mon Sep 17 00:00:00 2001 From: eBrnd Date: Thu, 24 Sep 2015 18:31:49 +0200 Subject: [PATCH 1/4] use twinklclient python wrapper --- animations/spectrum.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/animations/spectrum.py b/animations/spectrum.py index b418c7a..6a85ec6 100644 --- a/animations/spectrum.py +++ b/animations/spectrum.py @@ -6,6 +6,8 @@ import random import time import sys +from twinklclient import TwinklSocket, TwinklMessage + WIDTH = 6 HEIGHT = 8 @@ -32,6 +34,8 @@ COLORS = [ channels = {} +msg = TwinklMessage() + def set_box(x,y,r,g,b): if x >= 0 and y >= 0 and x < WIDTH and y < HEIGHT: base_address = BOX_MAP[y][x] @@ -50,13 +54,6 @@ def get_box(x, y): return res -def output_channels(): - for channel, value in channels.items(): - print("%d : %d" % (channel, value)) - print("") - sys.stdout.flush() - - class Background: """clear the light wall to a pseudorandomly changing/fading solid color""" @@ -96,7 +93,7 @@ class Background: for i in range(3): # fade into BG by adding just a little of the current BG color, # add color from pixel below for a "upward flowing" effect - color[i] = 0.744 * color[i] + 0.056 * self._current_bg_color[i] + 0.2 * color_below[i] + color[i] = int(0.744 * color[i] + 0.056 * self._current_bg_color[i] + 0.2 * color_below[i]) set_box(x, y, color[0], color[1], color[2]) @@ -126,11 +123,12 @@ class Fft_output: the aggregated result""" - def __init__(self, width, height, windowsize): + def __init__(self, width, height, windowsize, twinklsocket): self._background = Background() self._width = width self._height = height self._windowsize = windowsize + self._twinklsocket = twinklsocket self._count = 0 self._data = [] @@ -168,7 +166,10 @@ class Fft_output: color = COLORS[normalized-1] for row in range(self._height - normalized, self._height): set_box(col, row, color[0], color[1], color[2]) - output_channels() + + for _, val in enumerate(channels): + msg[val] = channels[val] + self._twinklsocket.send(msg) def init_audio(rate): @@ -181,8 +182,15 @@ def init_audio(rate): def main(): + if len(sys.argv) != 3: + print "Usage: %s host priority" % sys.argv[0] + sys.exit(1) + + socket = TwinklSocket(sys.argv[1], "1337") + msg.set_priority(int(sys.argv[2])) + ain = init_audio(AUDIO_RATE) - fft_out = Fft_output(WIDTH, HEIGHT, WINDOW_SIZE) + fft_out = Fft_output(WIDTH, HEIGHT, WINDOW_SIZE, socket) while True: data = ain.read(); From 8942de16ab5981dfdaa1e16bcd514c4338131496 Mon Sep 17 00:00:00 2001 From: eBrnd Date: Thu, 24 Sep 2015 19:20:13 +0200 Subject: [PATCH 2/4] add wrapper for twinkl output --- animations/spectrum.py | 91 +++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 37 deletions(-) diff --git a/animations/spectrum.py b/animations/spectrum.py index 6a85ec6..1bd4ace 100644 --- a/animations/spectrum.py +++ b/animations/spectrum.py @@ -31,36 +31,14 @@ COLORS = [ ] -channels = {} - - -msg = TwinklMessage() - -def set_box(x,y,r,g,b): - if x >= 0 and y >= 0 and x < WIDTH and y < HEIGHT: - base_address = BOX_MAP[y][x] - channels[base_address] = r - channels[base_address + 1] = g - channels[base_address + 2] = b - - -def get_box(x, y): - base_address = BOX_MAP[y][x] - try: - res = [ channels[base_address], channels[base_address + 1], channels[base_address + 2] ] - except KeyError, e: - # If the array is still uninitialized, just return [0,0,0] - res = [ 0, 0, 0 ] - return res - - class Background: """clear the light wall to a pseudorandomly changing/fading solid color""" - def __init__(self): + def __init__(self, twinkl_output): self._current_bg_color = [ 0, 0, 0 ] self._target_bg_color = [ 128, 128, 128 ] self._bg_time = 0 + self._out = twinkl_output def clear(self): @@ -83,10 +61,10 @@ class Background: for x in range(WIDTH): for y in range(HEIGHT): - color = get_box(x, y) + color = self._out.get_box(x, y) if y != HEIGHT - 1: - color_below = get_box(x, y + 1) + color_below = self._out.get_box(x, y + 1) else: color_below = self._current_bg_color @@ -95,7 +73,7 @@ class Background: # add color from pixel below for a "upward flowing" effect color[i] = int(0.744 * color[i] + 0.056 * self._current_bg_color[i] + 0.2 * color_below[i]) - set_box(x, y, color[0], color[1], color[2]) + self._out.set_box(x, y, color[0], color[1], color[2]) def audio_from_raw(raw): @@ -118,17 +96,59 @@ def audio_from_raw(raw): return out +class Twinkl_output: + """Wrapper class for TwinklSocket/TwinklMessage that keeps a frame buffer which allows write and + read access and takes care of mapping x/y coordinates to boxes""" + + + def __init__(self, hostname, port, priority): + self._socket = TwinklSocket(hostname, port) + self._msg = TwinklMessage() + + self._msg.set_priority(int(priority)) + + self._channels = {} + + + def set_box(self, x,y,r,g,b): + if x >= 0 and y >= 0 and x < WIDTH and y < HEIGHT: + base_address = BOX_MAP[y][x] + self._channels[base_address] = r + self._channels[base_address + 1] = g + self._channels[base_address + 2] = b + + + def get_box(self, x, y): + base_address = BOX_MAP[y][x] + try: + res = [ + self._channels[base_address], + self._channels[base_address + 1], + self._channels[base_address + 2] + ] + except KeyError, e: + # If the array is still uninitialized, just return [0,0,0] + res = [ 0, 0, 0 ] + return res + + + def send(self): + for _, i in enumerate(self._channels): + self._msg[i] = self._channels[i] + self._socket.send(self._msg) + + class Fft_output: """aggregate several fft'ed samples, does postprocessing to make it look nice and outputs the aggregated result""" - def __init__(self, width, height, windowsize, twinklsocket): - self._background = Background() + def __init__(self, width, height, windowsize, twinkl_output): + self._background = Background(twinkl_output) self._width = width self._height = height self._windowsize = windowsize - self._twinklsocket = twinklsocket + self._out = twinkl_output self._count = 0 self._data = [] @@ -165,11 +185,9 @@ class Fft_output: normalized = min(int(abss[col] / (self._height * self._windowsize * 1000)), self._height) color = COLORS[normalized-1] for row in range(self._height - normalized, self._height): - set_box(col, row, color[0], color[1], color[2]) + self._out.set_box(col, row, color[0], color[1], color[2]) - for _, val in enumerate(channels): - msg[val] = channels[val] - self._twinklsocket.send(msg) + self._out.send() def init_audio(rate): @@ -186,11 +204,10 @@ def main(): print "Usage: %s host priority" % sys.argv[0] sys.exit(1) - socket = TwinklSocket(sys.argv[1], "1337") - msg.set_priority(int(sys.argv[2])) + output = Twinkl_output(sys.argv[1], "1337", sys.argv[2]) ain = init_audio(AUDIO_RATE) - fft_out = Fft_output(WIDTH, HEIGHT, WINDOW_SIZE, socket) + fft_out = Fft_output(WIDTH, HEIGHT, WINDOW_SIZE, output) while True: data = ain.read(); From ae3bfd264dfe80ee6ba874c9c05bbc63ae6c07d5 Mon Sep 17 00:00:00 2001 From: eBrnd Date: Thu, 24 Sep 2015 19:59:41 +0200 Subject: [PATCH 3/4] only use one stereo channel --- animations/spectrum.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/animations/spectrum.py b/animations/spectrum.py index 1bd4ace..f2a3f16 100644 --- a/animations/spectrum.py +++ b/animations/spectrum.py @@ -77,22 +77,27 @@ class Background: def audio_from_raw(raw): - """convert bytewise signed 16bit little endian to int list""" + """convert bytewise signed 16bit little endian to int list, only take one stereo channel""" out = [] - high = False + sample_byte = 0 current = 0 + for value in raw: value = ord(value[0]) - if high: + if sample_byte == 0: + current = value + sample_byte = 1 + elif sample_byte == 1: sign = value & 0x80 current = current + 256 * (value & 0x7F) if sign: current = -((~current & 0x7FFF) + 1) out.append(current) - high = False + sample_byte = 2 + elif sample_byte == 2: # skip other stereo channel + sample_byte = 3 else: - current = value - high = True + sample_byte = 0 return out @@ -182,7 +187,7 @@ class Fft_output: self._background.clear() for col in range(self._width): - normalized = min(int(abss[col] / (self._height * self._windowsize * 1000)), self._height) + normalized = min(int(abss[col] / (self._height * self._windowsize * 300)), self._height) color = COLORS[normalized-1] for row in range(self._height - normalized, self._height): self._out.set_box(col, row, color[0], color[1], color[2]) From 3bc690d470443a855d122c2c2f346d80e5db78f4 Mon Sep 17 00:00:00 2001 From: eBrnd Date: Thu, 24 Sep 2015 21:39:24 +0200 Subject: [PATCH 4/4] use 16kHz sampling and average over 2 fft points for broader spectrum --- animations/spectrum.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/animations/spectrum.py b/animations/spectrum.py index f2a3f16..ede36d6 100644 --- a/animations/spectrum.py +++ b/animations/spectrum.py @@ -11,8 +11,8 @@ from twinklclient import TwinklSocket, TwinklMessage WIDTH = 6 HEIGHT = 8 -AUDIO_RATE = 4000 # sampling rate in Hz -WINDOW_SIZE = 8 # average fft over WINDOW_SIZE audio frames +AUDIO_RATE = 16000 # sampling rate in Hz +WINDOW_SIZE = 14 # average fft over WINDOW_SIZE audio frames BOX_MAP = [ [357, 18, 369, 186, 249, 228, 51], @@ -156,18 +156,24 @@ class Fft_output: self._out = twinkl_output self._count = 0 - self._data = [] - for _ in range(width): - self._data.append(0) + self._data = [0] * width def add(self, data): """add a set of fft data to the internal store, output the result if enough data is available""" - abss = numpy.absolute(data[1:self._width+1]) + + binsize = 2 + + abss = numpy.absolute(data[0:binsize * self._width]) + + # sum fft values into bins + absum = [0] * self._width + for i in range(1, self._width * binsize): + absum[i / binsize] = absum[i / binsize] + abss[i] for i in range(self._width): - self._data[i] = self._data[i] + abss[i] + self._data[i] = self._data[i] + absum[i] self._count = self._count + 1 if (self._count == self._windowsize): @@ -180,14 +186,13 @@ class Fft_output: def output_twinkl(self): """output graph to twinkl client""" # correct for disproportionately large first and second column - self._data[0] = self._data[0] / 2.5 - self._data[1] = self._data[1] / 1.5 - - abss = numpy.absolute(self._data) + self._data[0] = self._data[0] / 2 + self._data[1] = self._data[1] / 2.5 + self._data[2] = self._data[2] / 1.5 self._background.clear() for col in range(self._width): - normalized = min(int(abss[col] / (self._height * self._windowsize * 300)), self._height) + normalized = min(int(self._data[col] / (self._height * self._windowsize * 700)), self._height) color = COLORS[normalized-1] for row in range(self._height - normalized, self._height): self._out.set_box(col, row, color[0], color[1], color[2]) @@ -200,6 +205,7 @@ def init_audio(rate): ain = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL) ain.setformat(alsaaudio.PCM_FORMAT_S16_LE) ain.setrate(rate) + ain.setperiodsize(64) return ain