diff --git a/host/ledbar.c b/host/ledbar.c index f109e45..4a1c617 100644 --- a/host/ledbar.c +++ b/host/ledbar.c @@ -345,14 +345,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(); diff --git a/host_python/README b/host_python/README new file mode 100644 index 0000000..5b85627 --- /dev/null +++ b/host_python/README @@ -0,0 +1,27 @@ +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 +equalizer.py requires PyAudio (wrapper around PortAudio) and a microphone diff --git a/host_python/demo.py b/host_python/demo.py new file mode 100755 index 0000000..6409d11 --- /dev/null +++ b/host_python/demo.py @@ -0,0 +1,76 @@ +#!/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) + sys.stdout.flush() + pygame.display.flip() + except EOFError: + pass + finally: + pygame.quit() + + return 0 + +sys.exit(main()) 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() 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 diff --git a/host_python/send_to_serial.py b/host_python/send_to_serial.py new file mode 100755 index 0000000..380f8ad --- /dev/null +++ b/host_python/send_to_serial.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# vim:et:sw=4:ts=4:sts=4 + +import sys +import time +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 + time.sleep(2) + + 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())