From 5d2e56b0ec9e1c69a805dd780a5eeea01cdf0805 Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Wed, 21 Sep 2011 13:55:41 +0200 Subject: [PATCH 1/6] The sleep is only needed if we output data --- host/ledbar.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/host/ledbar.c b/host/ledbar.c index c8acaf1..0767460 100644 --- a/host/ledbar.c +++ b/host/ledbar.c @@ -286,14 +286,13 @@ int main(int argc, char* argv[]) } if (fp) { - struct termios t; - tcgetattr(fileno(fp), &t); - cfsetspeed(&t, B38400); - tcsetattr(fileno(fp), TCSADRAIN, &t); + struct termios t; + tcgetattr(fileno(fp), &t); + cfsetspeed(&t, B38400); + tcsetattr(fileno(fp), TCSADRAIN, &t); + sleep(2); } - sleep(2); - if (SDL_Init(SDL_INIT_VIDEO) < 0) return 1; if (!(screen = SDL_SetVideoMode(RESX, RESY, BPP, SDL_HWSURFACE))) { SDL_Quit(); From bc08a0cb8c0cfc1921dfceafc56f5b070fc9bb9f Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Wed, 21 Sep 2011 16:42:27 +0200 Subject: [PATCH 2/6] Added script which sends its stdin to serial * It groups its input into batches of 3*(# of boxes) bytes --- host_python/send_to_serial.py | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100755 host_python/send_to_serial.py diff --git a/host_python/send_to_serial.py b/host_python/send_to_serial.py new file mode 100755 index 0000000..f9b4d3b --- /dev/null +++ b/host_python/send_to_serial.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# vim:et:sw=4:ts=4:sts=4 + +import sys +import serial +import getopt + +def print_usage(): + print '''\ +USAGE: + %s [-h | [-n number] [-b speed] serial] +OPTIONS: + serial write output to serial device + -b speed speed of the serial device + -n number number of controlled boxes + -h --help show this help +''' % sys.argv[0] + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 's:b:n:h', ['help']) + except getopt.GetOptError: + print_usage() + return 1 + speed = 38400 + number = 10 + show_help = False + for k, v in opts: + if k == '-n': + if not v.isdigit(): + print_usage() + return 1 + number = int(v) + elif k == '-b': + if not v.isdigit(): + print_usage() + return 1 + speed = int(v) + elif k == '-h' or k == '--help': show_help = True + if show_help: + print_usage() + return 0 + if len(args) != 1: + print_usage() + return 1 + + try: + output_stream = serial.Serial(args[0], speed) + except serial.serialutil.SerialException: + print 'Could not open the serial device' + return 1 + + try: + while True: + data = '' + to_read = number*3 + while to_read > 0: + read = sys.stdin.read(to_read) + if len(read) == 0: break + to_read -= len(read) + data += read + if len(read) == 0: break + output_stream.write(data) + output_stream.flush() + except IOError: + pass + + return 0 + +sys.exit(main()) From 1af1b8093ac4776ebd789661cb75eefcb15abf42 Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Wed, 21 Sep 2011 19:51:15 +0200 Subject: [PATCH 3/6] Added script which shows simulation of ledbar * depends on pygame, which is something like wrapper around SDL * it gets its input from stdin, displays it and sends it to stdout, so some other script can work with it further --- host_python/demo.py | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100755 host_python/demo.py diff --git a/host_python/demo.py b/host_python/demo.py new file mode 100755 index 0000000..929b445 --- /dev/null +++ b/host_python/demo.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# vim:et:sw=4:ts=4:sts=4 + +import sys +import getopt +import pygame + +def print_usage(): + print '''\ +USAGE: + %s [-n number] [-h] +OPTIONS: + -n number number of controlled boxes + -h --help show this help +''' % sys.argv[0] + +def read_byte(): + r = sys.stdin.read(1) + if len(r) == 0: raise EOFError + return ord(r) + +def write_byte(b): + sys.stdout.write(chr(b)) + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'n:h', ['help']) + except getopt.GetOptError: + print_usage() + return 1 + if len(args): + print_usage() + return 1 + number = 10 + show_help = False + for k, v in opts: + if k == '-n': + if not v.isdigit(): + print_usage() + return 1 + number = int(v) + elif k == '-h' or k == '--help': show_help = True + if show_help: + print_usage() + return 0 + + pygame.init() + screen_size = [800, 600] + screen = pygame.display.set_mode(screen_size) + pygame.display.set_caption("ledbar demo viewer") + offset = 5 + pixel_width = (screen_size[0]-offset) / number + try: + exit = False + while not exit: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit = True + continue + screen.fill([0, 0, 0]) + for i in xrange(number): + r = read_byte() + g = read_byte() + b = read_byte() + pygame.draw.rect(screen, [r, g, b], [pixel_width*i, 0, pixel_width-offset, pixel_width-offset]) + write_byte(r); write_byte(g); write_byte(b) + pygame.display.flip() + except EOFError: + pass + finally: + pygame.quit() + + return 0 + +sys.exit(main()) From dace593f3b5e35f2021d8e09265020e3b8f008d0 Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Wed, 21 Sep 2011 23:38:20 +0200 Subject: [PATCH 4/6] Added class simplifiing ledbar output * also added rainbow.py, which uses this class to create rainbow effect --- host_python/README | 26 ++++++++++++++++++++++ host_python/ledbar.py | 50 ++++++++++++++++++++++++++++++++++++++++++ host_python/rainbow.py | 33 ++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 host_python/README create mode 100644 host_python/ledbar.py create mode 100755 host_python/rainbow.py diff --git a/host_python/README b/host_python/README new file mode 100644 index 0000000..ff3639b --- /dev/null +++ b/host_python/README @@ -0,0 +1,26 @@ +INTRODUCTION: + +The scripts in this directory try to work according to the KISS principle. It +contains a script which redirects its stdin to serial device +(send_to_serial.py), one that simulates the ledbar and sends the data unaltered +from its stdin to its stdout, a module that helps programmers to program +various graphical effects and some example effect generators using this module. + + +EXAMPLES: + +./rainbow.py | ./demo.py >/dev/null + - only simulate the rainbow effect, do not send it anywhere + +./rainbow.py | ./send_to_serial.py + - only send the data to the serial device, do not show the simulation + +./rainbow.py | ./demo.py | ./send_to_serial.py + - combine both options, show a simulation and send the data to the serial + device + + +DEPENDENCIES: + +demo.py requires PyGame, Python wrapper around SDL +send_to_serial.py requires PySerial, Python library for work with serial ports diff --git a/host_python/ledbar.py b/host_python/ledbar.py new file mode 100644 index 0000000..72fed7d --- /dev/null +++ b/host_python/ledbar.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +# vim:et:sw=4:ts=4:sts=4 + +import sys +import time + +class Ledbar: + + def __init__(self, boxes=10, secs_per_frame=0.025): + self.boxes = boxes + self.secs_per_frame = secs_per_frame + self.last_update = time.time() + self.pixels = [] + for i in xrange(boxes): + self.pixels.append([0, 0, 0]) + + def set_pixel(self, pixel, red, green, blue): + self.set_red(pixel, red) + self.set_green(pixel, green) + self.set_blue(pixel, blue) + + def set_red(self, pixel, red): + if red < 0.0 or red > 1.0: raise ValueError('red has to be between 0.0 and 1.0') + self.pixels[pixel][0] = int(red*255.99) + + def set_green(self, pixel, green): + if green < 0.0 or green > 1.0: raise ValueError('green has to be between 0.0 and 1.0') + self.pixels[pixel][1] = int(green*255.99) + + def set_blue(self, pixel, blue): + if blue < 0.0 or blue > 1.0: raise ValueError('blue has to be between 0.0 and 1.0') + self.pixels[pixel][2] = int(blue*255.99) + + def echo(self, s, no_newline=False): + sys.stderr.write(str(s) + ('' if no_newline else '\n')) + + def update(self): + now = time.time() + delta = now - self.last_update + if delta < self.secs_per_frame: + time.sleep(self.secs_per_frame - delta) + try: + for p in self.pixels: + for c in p: + sys.stdout.write(chr(c)) + sys.stdout.flush() + except IOError: + return False + self.last_update += self.secs_per_frame + return True diff --git a/host_python/rainbow.py b/host_python/rainbow.py new file mode 100755 index 0000000..36aa39c --- /dev/null +++ b/host_python/rainbow.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# vim:et:sw=4:ts=4:sts=4 + +import sys + +from ledbar import Ledbar + +PIXELS = 10 + +def update(t, i): + offset = float(i)/PIXELS + time = 0.005*t + phi = 6*offset+time + phase = int(phi%6) + part = phi % 1.0 + inc = part + dec = 1-part + if phase == 0: return ( 1, inc, 0) + elif phase == 1: return (dec, 1, 0) + elif phase == 2: return ( 0, 1, inc) + elif phase == 3: return ( 0, dec, 1) + elif phase == 4: return (inc, 0, 1) + elif phase == 5: return ( 1, 0, dec) + +l = Ledbar(PIXELS) +t = 0 +work = True +while work: + for i in xrange(PIXELS): + c = update(t, i) + l.set_pixel(i, c[0], c[1], c[2]) + work = l.update() + t += 1 From 21d9a3d4a4959dc8c51b451459f7751a31329671 Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Fri, 23 Sep 2011 09:18:55 +0200 Subject: [PATCH 5/6] Added Python equalizer effect generator * It uses input from microphone and displays colors based on the frequencies present in the input --- host_python/README | 1 + host_python/equalizer.py | 84 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100755 host_python/equalizer.py diff --git a/host_python/README b/host_python/README index ff3639b..5b85627 100644 --- a/host_python/README +++ b/host_python/README @@ -24,3 +24,4 @@ DEPENDENCIES: demo.py requires PyGame, Python wrapper around SDL send_to_serial.py requires PySerial, Python library for work with serial ports +equalizer.py requires PyAudio (wrapper around PortAudio) and a microphone diff --git a/host_python/equalizer.py b/host_python/equalizer.py new file mode 100755 index 0000000..e1a816d --- /dev/null +++ b/host_python/equalizer.py @@ -0,0 +1,84 @@ +#!/usr/bin/python +# vim:et:sw=4:ts=4:sts=4 + +import pyaudio +import struct +import math +import numpy as np + +import ledbar + +CHUNK_SIZE = 1024 +FORMAT = pyaudio.paInt16 +CHANNELS = 1 +RATE = 44100 + +PIXELS = 10 + +HISTORY_SIZE = 4 +SAMPLE_SIZE = CHUNK_SIZE*HISTORY_SIZE +MIN_FREQ = 20 +MAX_FREQ = 12000 +FREQ_STEP = float(RATE) / (CHUNK_SIZE * HISTORY_SIZE) +PIXEL_FREQ_RANGE = math.pow(float(MAX_FREQ) / MIN_FREQ, 1.0/PIXELS) + +p = pyaudio.PyAudio() + +stream = p.open(format = FORMAT, + channels = CHANNELS, + rate = RATE, + input = True, + frames_per_buffer = CHUNK_SIZE) + +def get_color(volume): + p = 1-15/volume + if p <= 0: return (0, 0, 0) + p *= p + if p <= 0.4: return (0, 0, p*2.5) + elif p <= 0.7: return (0, (p-0.4)*3.33, 1.0-(p-0.4)*3.33) + elif p <= 0.9: return ((p-0.7)*5.0, 1.0-(p-0.7)*5.0, 0.0) + else: return (1.0, (p-0.9)*10.0, (p-0.9)*10.0) + +l = ledbar.Ledbar(PIXELS) +history = [] +window = np.array([0.5*(1-math.cos(2*math.pi*i/(SAMPLE_SIZE-1))) for i in xrange(SAMPLE_SIZE)]) +work = True + +try: + while work: + try: data = stream.read(CHUNK_SIZE) + except IOError: continue + if len(data) == 0: break + indata = np.array(struct.unpack('%dh'%CHUNK_SIZE,data)) + history.append(indata) + if len(history) > HISTORY_SIZE: history.pop(0) + elif len(history) < HISTORY_SIZE: continue + fft = np.fft.rfft(np.concatenate(history)*window) + freq_limit = MIN_FREQ + freq = 0 + i = 0 + while freq < freq_limit: + i += 1 + freq += FREQ_STEP + freq_limit *= PIXEL_FREQ_RANGE + pixel = 0 + count = 0 + volumes = [] + while pixel < PIXELS: + total = 0.0 + while freq < freq_limit: + total += abs(fft[i])**2 + i += 1; count += 1 + freq += FREQ_STEP + volume = (total/count)**0.5/SAMPLE_SIZE + volumes.append(volume) + freq_limit *= PIXEL_FREQ_RANGE + pixel += 1 + count = 0 + for pixel in xrange(PIXELS): + c = get_color(volumes[pixel]) + l.set_pixel(pixel, c[0], c[1], c[2]) + work = l.update() +finally: + stream.close() + p.terminate() From b734027e053755eb795de66fdb93b6534ec3370a Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Mon, 26 Sep 2011 16:00:00 +0200 Subject: [PATCH 6/6] Fixed synchronization bugs in Python version --- host_python/demo.py | 1 + host_python/send_to_serial.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/host_python/demo.py b/host_python/demo.py index 929b445..6409d11 100755 --- a/host_python/demo.py +++ b/host_python/demo.py @@ -64,6 +64,7 @@ def main(): b = read_byte() pygame.draw.rect(screen, [r, g, b], [pixel_width*i, 0, pixel_width-offset, pixel_width-offset]) write_byte(r); write_byte(g); write_byte(b) + sys.stdout.flush() pygame.display.flip() except EOFError: pass diff --git a/host_python/send_to_serial.py b/host_python/send_to_serial.py index f9b4d3b..380f8ad 100755 --- a/host_python/send_to_serial.py +++ b/host_python/send_to_serial.py @@ -2,6 +2,7 @@ # vim:et:sw=4:ts=4:sts=4 import sys +import time import serial import getopt @@ -49,6 +50,7 @@ def main(): except serial.serialutil.SerialException: print 'Could not open the serial device' return 1 + time.sleep(2) try: while True: