Mercurial > hg > mood-conductor
changeset 37:2db17c224664
added visclient2
author | gyorgyf |
---|---|
date | Fri, 24 Apr 2015 07:09:32 +1000 |
parents | caea7ec0c162 |
children | b17a5b6f74a4 12d6fd69d166 |
files | visualclient2/.DS_Store visualclient2/colors.txt visualclient2/gradients.py visualclient2/gradients.pyc visualclient2/midiclient.py visualclient2/moods.csv visualclient2/visclient.py |
diffstat | 7 files changed, 2601 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/visualclient2/colors.txt Fri Apr 24 07:09:32 2015 +1000 @@ -0,0 +1,257 @@ +x,y,r,g,b +0,1,200,0,0 +0.0625,1,200,9,0 +0.125,1,200,18,0 +0.1875,1,200,28,0 +0.25,1,200,37,0 +0.3125,1,200,46,0 +0.375,1,200,56,0 +0.4375,1,200,65,0 +0.5,1,200,75,0 +0.5625,1,200,84,0 +0.625,1,200,93,0 +0.6875,1,200,103,0 +0.75,1,200,112,0 +0.8125,1,200,121,0 +0.875,1,200,131,0 +0.9375,1,200,140,0 +0,0.9375,187,3,6 +0.0625,0.9375,187,12,5 +0.125,0.9375,188,22,5 +0.1875,0.9375,189,31,5 +0.25,0.9375,190,41,5 +0.3125,0.9375,191,50,5 +0.375,0.9375,191,60,5 +0.4375,0.9375,192,69,5 +0.5,0.9375,193,79,5 +0.5625,0.9375,194,88,5 +0.625,0.9375,195,98,5 +0.6875,0.9375,195,107,5 +0.75,0.9375,196,117,5 +0.8125,0.9375,197,126,5 +0.875,0.9375,198,136,5 +0.9375,0.9375,199,145,5 +0,0.875,175,6,12 +0.0625,0.875,176,15,11 +0.125,0.875,178,25,11 +0.1875,0.875,179,34,11 +0.25,0.875,181,44,11 +0.3125,0.875,182,54,11 +0.375,0.875,184,63,11 +0.4375,0.875,185,73,11 +0.5,0.875,187,83,11 +0.5625,0.875,189,92,10 +0.625,0.875,190,102,10 +0.6875,0.875,192,111,10 +0.75,0.875,193,121,10 +0.8125,0.875,195,131,10 +0.875,0.875,196,140,10 +0.9375,0.875,198,150,10 +0,0.8125,162,9,18 +0.0625,0.8125,164,18,17 +0.125,0.8125,166,28,17 +0.1875,0.8125,169,38,17 +0.25,0.8125,171,48,17 +0.3125,0.8125,173,57,17 +0.375,0.8125,176,67,16 +0.4375,0.8125,178,77,16 +0.5,0.8125,181,87,16 +0.5625,0.8125,183,96,16 +0.625,0.8125,185,106,16 +0.6875,0.8125,188,116,15 +0.75,0.8125,190,126,15 +0.8125,0.8125,192,135,15 +0.875,0.8125,195,145,15 +0.9375,0.8125,197,155,15 +0,0.75,150,12,25 +0.0625,0.75,153,21,24 +0.125,0.75,156,31,24 +0.1875,0.75,159,41,24 +0.25,0.75,162,51,23 +0.3125,0.75,165,61,23 +0.375,0.75,168,71,23 +0.4375,0.75,171,81,22 +0.5,0.75,175,91,22 +0.5625,0.75,178,100,22 +0.625,0.75,181,110,21 +0.6875,0.75,184,120,21 +0.75,0.75,187,130,21 +0.8125,0.75,190,140,20 +0.875,0.75,193,150,20 +0.9375,0.75,196,160,20 +0,0.6875,137,15,31 +0.0625,0.6875,140,25,30 +0.125,0.6875,144,35,30 +0.1875,0.6875,148,45,29 +0.25,0.6875,152,55,29 +0.3125,0.6875,156,65,29 +0.375,0.6875,160,75,28 +0.4375,0.6875,164,85,28 +0.5,0.6875,168,95,28 +0.5625,0.6875,172,105,27 +0.625,0.6875,176,115,27 +0.6875,0.6875,180,125,26 +0.75,0.6875,184,135,26 +0.8125,0.6875,188,145,26 +0.875,0.6875,192,155,25 +0.9375,0.6875,196,165,25 +0,0.625,125,18,37 +0.0625,0.625,129,28,36 +0.125,0.625,134,38,36 +0.1875,0.625,139,48,35 +0.25,0.625,143,58,35 +0.3125,0.625,148,68,34 +0.375,0.625,153,78,34 +0.4375,0.625,157,88,33 +0.5,0.625,162,99,33 +0.5625,0.625,167,109,33 +0.625,0.625,171,119,32 +0.6875,0.625,176,129,32 +0.75,0.625,181,139,31 +0.8125,0.625,185,149,31 +0.875,0.625,190,159,30 +0.9375,0.625,195,169,30 +0,0.5625,112,21,43 +0.0625,0.5625,117,31,42 +0.125,0.5625,123,41,42 +0.1875,0.5625,128,51,41 +0.25,0.5625,134,62,41 +0.3125,0.5625,139,72,40 +0.375,0.5625,145,82,40 +0.4375,0.5625,150,92,39 +0.5,0.5625,156,103,39 +0.5625,0.5625,161,113,38 +0.625,0.5625,167,123,38 +0.6875,0.5625,172,133,37 +0.75,0.5625,178,144,37 +0.8125,0.5625,183,154,36 +0.875,0.5625,189,164,36 +0.9375,0.5625,194,174,35 +0,0.5,100,25,50 +0.0625,0.5,106,35,49 +0.125,0.5,112,45,48 +0.1875,0.5,118,55,48 +0.25,0.5,125,66,47 +0.3125,0.5,131,76,46 +0.375,0.5,137,86,46 +0.4375,0.5,143,97,45 +0.5,0.5,150,107,45 +0.5625,0.5,156,117,44 +0.625,0.5,162,128,43 +0.6875,0.5,168,138,43 +0.75,0.5,175,148,42 +0.8125,0.5,181,159,41 +0.875,0.5,187,169,41 +0.9375,0.5,193,179,40 +0,0.4375,87,28,56 +0.0625,0.4375,94,38,55 +0.125,0.4375,101,48,54 +0.1875,0.4375,108,59,53 +0.25,0.4375,115,69,53 +0.3125,0.4375,122,80,52 +0.375,0.4375,129,90,51 +0.4375,0.4375,136,101,51 +0.5,0.4375,143,111,50 +0.5625,0.4375,150,121,49 +0.625,0.4375,157,132,49 +0.6875,0.4375,164,142,48 +0.75,0.4375,171,153,47 +0.8125,0.4375,178,163,47 +0.875,0.4375,185,174,46 +0.9375,0.4375,192,184,45 +0,0.375,75,31,62 +0.0625,0.375,82,41,61 +0.125,0.375,90,52,60 +0.1875,0.375,98,62,59 +0.25,0.375,106,73,59 +0.3125,0.375,114,83,58 +0.375,0.375,121,94,57 +0.4375,0.375,129,104,56 +0.5,0.375,137,115,56 +0.5625,0.375,145,126,55 +0.625,0.375,153,136,54 +0.6875,0.375,160,147,53 +0.75,0.375,168,157,53 +0.8125,0.375,176,168,52 +0.875,0.375,184,178,51 +0.9375,0.375,192,189,50 +0,0.3125,62,34,68 +0.0625,0.3125,70,44,67 +0.125,0.3125,79,55,66 +0.1875,0.3125,87,66,65 +0.25,0.3125,96,76,64 +0.3125,0.3125,105,87,63 +0.375,0.3125,113,98,63 +0.4375,0.3125,122,108,62 +0.5,0.3125,131,119,61 +0.5625,0.3125,139,130,60 +0.625,0.3125,148,140,59 +0.6875,0.3125,156,151,59 +0.75,0.3125,165,162,58 +0.8125,0.3125,174,172,57 +0.875,0.3125,182,183,56 +0.9375,0.3125,191,194,55 +0,0.25,50,37,75 +0.0625,0.25,59,47,74 +0.125,0.25,68,58,73 +0.1875,0.25,78,69,72 +0.25,0.25,87,80,71 +0.3125,0.25,96,91,70 +0.375,0.25,106,101,69 +0.4375,0.25,115,112,68 +0.5,0.25,125,123,67 +0.5625,0.25,134,134,66 +0.625,0.25,143,145,65 +0.6875,0.25,153,155,64 +0.75,0.25,162,166,63 +0.8125,0.25,171,177,62 +0.875,0.25,181,188,61 +0.9375,0.25,190,199,60 +0,0.1875,37,40,81 +0.0625,0.1875,47,50,80 +0.125,0.1875,57,61,79 +0.1875,0.1875,67,72,78 +0.25,0.1875,77,83,77 +0.3125,0.1875,87,94,76 +0.375,0.1875,98,105,75 +0.4375,0.1875,108,116,74 +0.5,0.1875,118,127,73 +0.5625,0.1875,128,138,72 +0.625,0.1875,138,149,71 +0.6875,0.1875,149,160,70 +0.75,0.1875,159,171,69 +0.8125,0.1875,169,182,68 +0.875,0.1875,179,193,67 +0.9375,0.1875,189,204,66 +0,0.125,25,43,87 +0.0625,0.125,35,54,85 +0.125,0.125,46,65,84 +0.1875,0.125,57,76,83 +0.25,0.125,68,87,82 +0.3125,0.125,79,98,81 +0.375,0.125,90,109,80 +0.4375,0.125,101,120,79 +0.5,0.125,112,131,78 +0.5625,0.125,123,142,77 +0.625,0.125,134,153,76 +0.6875,0.125,145,164,75 +0.75,0.125,156,175,74 +0.8125,0.125,167,186,73 +0.875,0.125,178,197,72 +0.9375,0.125,189,208,71 +0,0.0625,12,46,93 +0.0625,0.0625,23,57,91 +0.125,0.0625,35,68,90 +0.1875,0.0625,47,79,89 +0.25,0.0625,59,90,88 +0.3125,0.0625,70,101,87 +0.375,0.0625,82,113,86 +0.4375,0.0625,94,124,85 +0.5,0.0625,106,135,84 +0.5625,0.0625,117,146,82 +0.625,0.0625,129,157,81 +0.6875,0.0625,141,169,80 +0.75,0.0625,153,180,79 +0.8125,0.0625,164,191,78 +0.875,0.0625,176,202,77 +0.9375,0.0625,188,213,76
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/visualclient2/gradients.py Fri Apr 24 07:09:32 2015 +1000 @@ -0,0 +1,576 @@ +#Copyright 2006 DR0ID <dr0id@bluewin.ch> http://mypage.bluewin.ch/DR0ID +# +# +# +""" +Allow to draw some gradients relatively easy. +""" + +__author__ = "$Author: DR0ID $" +__version__= "$Revision: 109 $" +__date__ = "$Date: 2007-08-09 20:33:32 +0200 (Do, 09 Aug 2007) $" + +import pygame +import math + +BLEND_MODES_AVAILABLE = False +vernum = pygame.vernum +if vernum[0]>=1 and vernum[1]>=8: + BLEND_MODES_AVAILABLE = True + + +class ColorInterpolator(object): + ''' + ColorInterpolator(distance, color1, color2, rfunc, gfunc, bfunc, afunc) + + interpolates a color over the distance using different functions for r,g,b,a + separately (a= alpha). + ''' + def __init__(self, distance, color1, color2, rfunc, gfunc, bfunc, afunc): + object.__init__(self) + + self.rInterpolator = FunctionInterpolator(color1[0], color2[0], distance, rfunc) + self.gInterpolator = FunctionInterpolator(color1[1], color2[1], distance, gfunc) + self.bInterpolator = FunctionInterpolator(color1[2], color2[2], distance, bfunc) + if len(color1)==4 and len(color2)==4: + self.aInterpolator = FunctionInterpolator(color1[3], color2[3], distance, afunc) + else: + self.aInterpolator = FunctionInterpolator(255, 255, distance, afunc) + + def eval(self, x): + ''' + eval(x) -> color + + returns the color at the position 0<=x<=d (actually not bound to this interval). + ''' +## print "colorInterp x", x, self.rInterpolator.eval(x), self.gInterpolator.eval(x), self.bInterpolator.eval(x) + return [self.rInterpolator.eval(x), + self.gInterpolator.eval(x), + self.bInterpolator.eval(x), + self.aInterpolator.eval(x)] + + + +class FunctionInterpolator(object): + ''' + FunctionINterpolator(startvalue, endvalue, trange, func) + + interpolates a function y=f(x) in the range trange with + startvalue = f(0) + endvalue = f(trange) + using the function func + ''' + def __init__(self, startvalue, endvalue, trange, func): + object.__init__(self) + # function + self.func = func + # y-scaling + self.a = endvalue-startvalue + if self.a == 0: + self.a = 1. + # x-scaling + if trange!=0: + self.b = 1./abs(trange) + else: + self.b = 1. + # x-displacement + self.c = 0 + # y-displacement + self.d = min(max(startvalue,0),255) + + def eval(self, x): + ''' + eval(x)->float + + return value at position x + ''' + # make sure that the returned value is in [0,255] +## return int(round(min(max(self.a*self.func(self.b*(x+self.c))+self.d, 0), 255))) + return int(min(max(self.a*self.func(self.b*(x+self.c))+self.d, 0), 255)) + + + +##def gradient(surface, +## startpoint, +## endpoint, +## startcolor, +## endcolor, +## Rfunc = (lambda x:x), +## Gfunc = (lambda x:x), +## Bfunc = (lambda x:x), +## Afunc = (lambda x:1), +## type = "line", +## mode = None ): +## ''' +## surface : surface to draw on +## startpoint: (x,y) point on surface +## endpoint : (x,y) point on surface +## startcolor: (r,g,b,a) color at startpoint +## endcolor : (r,g,b,a) color at endpoint +## Rfunc : function y = f(x) with startcolor =f(0) and endcolor = f(1) where 0 is at startpoint and 1 at endpoint +## Gfunc : --- " --- +## Bfunc : --- " --- +## Afunc : --- " --- +## these functions are evaluated in the range 0 <= x <= 1 and 0<= y=f(x) <= 1 +## type : "line", "circle" or "rect" +## mode : "+", "-", "*", None (how the pixels are drawen) +## +## returns : surface with the color characteristics w,h = (d, 256) and d = length of endpoint-startpoint +## +## ''' +## dx = endpoint[0]-startpoint[0] +## dy = endpoint[1]-startpoint[1] +## d = int(round(math.hypot(dx, dy))) +## angle = math.degrees( math.atan2(dy, dx) ) +## +## color = ColorInterpolator(d, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) +## +## if type=="line": +## h = int(2.*math.hypot(*surface.get_size())) +### bigSurf = pygame.Surface((d, h)).convert_alpha() +## bigSurf = pygame.Surface((d, h), pygame.SRCALPHA)#.convert_alpha() +### bigSurf = pygame.Surface((d, 1), pygame.SRCALPHA)#.convert_alpha() +## bigSurf.lock() +## bigSurf.fill((0,0,0,0)) +## bigSurf.set_colorkey((0,0,0,0)) +## for x in range(d): +## pygame.draw.line(bigSurf, color.eval(x), (x,0), (x,h), 1) +### for x in range(d): +### bigSurf.set_at((x, 0), color.eval(x)) +### bigSurf = pygame.transform.scale(bigSurf, (d, h)) +## +## bigSurf = pygame.transform.rotate(bigSurf, -angle) #rotozoom(bigSurf, -angle, 1) +## bigSurf.set_colorkey((0,0,0, 0)) +## rect = bigSurf.get_rect() +## srect = pygame.Rect(rect) +## dx = d/2. * math.cos(math.radians(angle)) +## dy = d/2. * math.sin(math.radians(angle)) +## rect.center = startpoint +## rect.move_ip(dx, dy) +## bigSurf.unlock() +## +## elif type=="circle": +## bigSurf = pygame.Surface((2*d, 2*d)).convert_alpha() +## bigSurf.fill((0,0,0,0)) +## bigSurf.lock() +## for x in range(d, 0, -1): +## pygame.draw.circle(bigSurf, color.eval(x), (d,d), x) +## bigSurf.unlock() +## rect = bigSurf.get_rect() +## srect = pygame.Rect(rect) +## rect.center = (startpoint[0], startpoint[1]) +## +## elif type=="rect": +## bigSurf = pygame.Surface((2*d, 2*d)).convert_alpha() +## bigSurf.fill((0,0,0,0)) +## c = bigSurf.get_rect().center +## bigSurf.lock() +## for x in range(d,-1,-1): +## r = pygame.Rect(0,0,2*x,2*x) +## r.center = c +## pygame.draw.rect(bigSurf, color.eval(x), r) +## bigSurf.unlock() +## bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1) +## bigSurf.set_colorkey((0,0,0, 0)) +## +## rect = bigSurf.get_rect() +## srect = pygame.Rect(rect) +## rect.center = startpoint +## else: +## raise NameError("type must be one of \"line\",\"circle\" or \"rect\"") +## +## if mode is None: +## surface.blit(bigSurf, rect, srect) +## else: +## if mode=="+": +## cf = pygame.color.add +## elif mode=="*": +## cf = pygame.color.multiply +## elif mode=="-": +## cf = pygame.color.subtract +## else: +## raise NameError("type must be one of \"+\", \"*\", \"-\" or None") +## irect = surface.get_clip().clip(rect) +## surface.lock() +## for x in range(irect.left, irect.left+irect.width): +## for y in range(irect.top, irect.top+irect.height): +## surface.set_at((x,y), cf(surface.get_at((x,y)), bigSurf.get_at((x-rect.left, y-rect.top)) ) ) +## surface.unlock() +## +## del bigSurf +## char = pygame.Surface((d+1, 257)) +### char.fill((0,0,0)) +### ox = 0 +### oldcol = color.eval(0) +### for x in range(d): +### col = color.eval(x) +### pygame.draw.line(char, (255,0,0), (x, 256-col[0]), (ox, 256-oldcol[0])) +### pygame.draw.line(char, (0,255,0), (x, 256-col[1]), (ox, 256-oldcol[1])) +### pygame.draw.line(char, (0,0,255), (x, 256-col[2]), (ox, 256-oldcol[2])) +### pygame.draw.line(char, (255,255,255), (x, 256-col[3]), (ox, 256-oldcol[3])) +### ox = x +### oldcol = col +### +## return char + + + + +def vertical(size, startcolor, endcolor): + """ + Draws a vertical linear gradient filling the entire surface. Returns a + surface filled with the gradient (numeric is only 2-3 times faster). + """ + height = size[1] + bigSurf = pygame.Surface((1,height)).convert_alpha() + dd = 1.0/height + sr, sg, sb, sa = startcolor + er, eg, eb, ea = endcolor + rm = (er-sr)*dd + gm = (eg-sg)*dd + bm = (eb-sb)*dd + am = (ea-sa)*dd + for y in range(height): + bigSurf.set_at((0,y), + (int(sr + rm*y), + int(sg + gm*y), + int(sb + bm*y), + int(sa + am*y)) + ) + return pygame.transform.scale(bigSurf, size) + + +def horizontal(size, startcolor, endcolor): + """ + Draws a horizontal linear gradient filling the entire surface. Returns a + surface filled with the gradient (numeric is only 2-3 times faster). + """ + width = size[0] + bigSurf = pygame.Surface((width, 1)).convert_alpha() + dd = 1.0/width + sr, sg, sb, sa = startcolor + er, eg, eb, ea = endcolor + rm = (er-sr)*dd + gm = (eg-sg)*dd + bm = (eb-sb)*dd + am = (ea-sa)*dd + for y in range(width): + bigSurf.set_at((y,0), + (int(sr + rm*y), + int(sg + gm*y), + int(sb + bm*y), + int(sa + am*y)) + ) + return pygame.transform.scale(bigSurf, size) + + +def radial(radius, startcolor, endcolor): + """ + Draws a linear raidal gradient on a square sized surface and returns + that surface. + """ + bigSurf = pygame.Surface((2*radius, 2*radius)).convert_alpha() + bigSurf.fill((0,0,0,0)) + dd = -1.0/radius + sr, sg, sb, sa = endcolor + er, eg, eb, ea = startcolor + rm = (er-sr)*dd + gm = (eg-sg)*dd + bm = (eb-sb)*dd + am = (ea-sa)*dd + + draw_circle = pygame.draw.circle + for rad in range(radius, 0, -1): + draw_circle(bigSurf, (er + int(rm*rad), + eg + int(gm*rad), + eb + int(bm*rad), + ea + int(am*rad)), (radius, radius), rad) + return bigSurf + +def squared(width, startcolor, endcolor): + """ + Draws a linear sqared gradient on a square sized surface and returns + that surface. + """ + bigSurf = pygame.Surface((width, width)).convert_alpha() + bigSurf.fill((0,0,0,0)) + dd = -1.0/(width/2) + sr, sg, sb, sa = endcolor + er, eg, eb, ea = startcolor + rm = (er-sr)*dd + gm = (eg-sg)*dd + bm = (eb-sb)*dd + am = (ea-sa)*dd + + draw_rect = pygame.draw.rect + for currentw in range((width/2), 0, -1): + pos = (width/2)-currentw + draw_rect(bigSurf, (er + int(rm*currentw), + eg + int(gm*currentw), + eb + int(bm*currentw), + ea + int(am*currentw)), pygame.Rect(pos, pos, 2*currentw, 2*currentw )) + return bigSurf + + +def vertical_func(size, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1)): + """ + Draws a vertical linear gradient filling the entire surface. Returns a + surface filled with the gradient (numeric is only 2x faster). + Rfunc, Gfunc, Bfunc and Afunc are function like y = f(x). They define + how the color changes. + """ + height = size[1] + bigSurf = pygame.Surface((1,height)).convert_alpha() + color = ColorInterpolator(height, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + for y in range(0, height): + bigSurf.set_at((0,y), color.eval(y+0.1)) + return pygame.transform.scale(bigSurf, size) + + +def horizontal_func(size, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1)): + """ + Draws a horizontal linear gradient filling the entire surface. Returns a + surface filled with the gradient (numeric is only 2x faster). + Rfunc, Gfunc, Bfunc and Afunc are function like y = f(x). They define + how the color changes. + """ + width = size[0] + bigSurf = pygame.Surface((width, 1)).convert_alpha() + color = ColorInterpolator(width, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + for y in range(0, width): + bigSurf.set_at((y, 0), color.eval(y+0.1)) + return pygame.transform.scale(bigSurf, size) + +def radial_func(radius, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), colorkey=(0,0,0,0)): + """ + Draws a linear raidal gradient on a square sized surface and returns + that surface. + """ + bigSurf = pygame.Surface((2*radius, 2*radius)).convert_alpha() + if len(colorkey)==3: + colorkey += (0,) + bigSurf.fill(colorkey) + color = ColorInterpolator(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + draw_circle = pygame.draw.circle + for rad in range(radius, 0, -1): + draw_circle(bigSurf, color.eval(rad), (radius, radius), rad) + return bigSurf + +def radial_func_offset(radius, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), colorkey=(0,0,0,0), offset=(0,0)): + """ + Draws a linear raidal gradient on a square sized surface and returns + that surface. + offset is the amount the center of the gradient is displaced of the center of the image. + Unfotunately this function ignores alpha. + """ + bigSurf = pygame.Surface((2*radius, 2*radius))#.convert_alpha() + + mask = pygame.Surface((2*radius, 2*radius), pygame.SRCALPHA)#.convert_alpha() + mask.fill(colorkey) + mask.set_colorkey((255,0,255)) + pygame.draw.circle(mask, (255,0,255), (radius, radius), radius) + + if len(colorkey)==3: + colorkey += (0,) + bigSurf.fill(colorkey) + + color = ColorInterpolator(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + draw_circle = pygame.draw.circle + radi = radius + int(math.hypot(offset[0], offset[1])+1) + for rad in range(radi, 0, -1): + draw_circle(bigSurf, color.eval(rad), (radius+offset[0], radius+offset[1]), rad) + + bigSurf.blit(mask, (0,0)) + bigSurf.set_colorkey(colorkey) + return bigSurf + + +def squared_func(width, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), offset=(0,0)): + """ + Draws a linear sqared gradient on a square sized surface and returns + that surface. + """ + bigSurf = pygame.Surface((width, width)).convert_alpha() + bigSurf.fill((0,0,0,0)) + color = ColorInterpolator(width/2, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + draw_rect = pygame.draw.rect + widthh = width+2*int(max(abs(offset[0]),abs(offset[1]))) + for currentw in range((widthh/2), 0, -1): +## pos = (width/2)-currentw + rect = pygame.Rect(0, 0, 2*currentw, 2*currentw ) + rect.center = (width/2+offset[0], width/2+offset[1]) + draw_rect(bigSurf, color.eval(currentw), rect) + return bigSurf + +def draw_gradient(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0): + """ + Instead of returning an Surface, this function draw it directy onto the + given Surface and returns the rect. + """ + dx = endpoint[0]-startpoint[0] + dy = endpoint[1]-startpoint[1] + d = int(round(math.hypot(dx, dy))) + angle = math.degrees( math.atan2(dy, dx) ) + + h = int(2.*math.hypot(*surface.get_size())) + + bigSurf = horizontal_func((d,h), startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + +## bigSurf = pygame.transform.rotate(bigSurf, -angle) #rotozoom(bigSurf, -angle, 1) + bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1) +## bigSurf.set_colorkey((0,0,0, 0)) + rect = bigSurf.get_rect() + srect = pygame.Rect(rect) + dx = d/2. * math.cos(math.radians(angle)) + dy = d/2. * math.sin(math.radians(angle)) + rect.center = startpoint + rect.move_ip(dx, dy) + if BLEND_MODES_AVAILABLE: + return surface.blit(bigSurf, rect, None, mode) + else: + return surface.blit(bigSurf, rect) + + +def draw_circle(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0): + """ + Instead of returning an Surface, this function draw it directy onto the + given Surface and returns the rect. + """ + dx = endpoint[0]-startpoint[0] + dy = endpoint[1]-startpoint[1] + radius = int(round(math.hypot(dx, dy))) + pos = (startpoint[0]-radius, startpoint[1]-radius) + if BLEND_MODES_AVAILABLE: + return surface.blit(radial_func(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc), pos, None, mode) + else: + return surface.blit(radial_func(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc), pos) + +def draw_squared(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0): + """ + Instead of returning an Surface, this function draw it directy onto the + given Surface and returns the rect. + """ + dx = endpoint[0]-startpoint[0] + dy = endpoint[1]-startpoint[1] + angle = math.degrees( math.atan2(dy, dx) ) + width = 2*int(round(math.hypot(dx, dy))) + + bigSurf = squared_func(width, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + + bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1) +## bigSurf.set_colorkey((0,0,0, 0)) + rect = bigSurf.get_rect() + rect.center = startpoint + if BLEND_MODES_AVAILABLE: + return surface.blit(bigSurf, rect, None, mode) + else: + return surface.blit(bigSurf, rect) + + +def chart(startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), scale=None): + """ + This returns a Surface where the change of the colors over the distance + (the width of the image) is showen as a line. + scale: a float, 1 is not scaling + """ + dx = endpoint[0]-startpoint[0] + dy = endpoint[1]-startpoint[1] + distance = int(round(math.hypot(dx, dy))) + color = ColorInterpolator(distance, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc) + bigSurf = pygame.Surface((distance, 256)) + bigSurf.fill((0,)*3) + oldcol = color.eval(0) + for x in range(distance): + r, g, b, a = color.eval(x) + pygame.draw.line(bigSurf, (255, 0, 0, 128), (x-1, oldcol[0]), (x, r)) + pygame.draw.line(bigSurf, (0, 255, 0, 128), (x-1, oldcol[1]), (x, g)) + pygame.draw.line(bigSurf, (0, 0, 255, 128), (x-1, oldcol[2]), (x, b)) + pygame.draw.line(bigSurf, (255, 255, 255, 128), (x-1, oldcol[3]), (x, a)) + oldcol = (r,g,b,a) + if scale: +## return pygame.transform.scale(bigSurf, size) + return pygame.transform.rotozoom(bigSurf, 0, scale) + return pygame.transform.flip(bigSurf, 0, 1) +#------------------------------------------------------------------------------ + + + + +def genericFxyGradient(surf, clip, color1, color2, func, intx, yint, zint=None): + """ + genericFxyGradient(size, color1, color2,func, intx, yint, zint=None) + + some sort of highfield drawer :-) + + surf : surface to draw + clip : rect on surf to draw in + color1 : start color + color2 : end color + func : function z = func(x,y) + xint : interval in x direction where the function is evaluated + yint : interval in y direction where the function is evaluated + zint : if not none same as yint or xint, if None then the max and min value + of func is taken as z-interval + + color = a*func(b*(x+c), d*(y+e))+f + """ + # make shure that x1<x2 and y1<y2 and z1<z2 + w,h = clip.size + x1 = min(intx) + x2 = max(intx) + y1 = min(yint) + y2 = max(yint) + if zint: # if user give us z intervall, then use it + z1 = min(zint) + z2 = max(zint) + else: # look for extrema of function (not best algorithme) + z1 = func(x1,y1) + z2 = z1 + for i in range(w): + for j in range(h): + r = func(i,j) + z1 = min(z1, r) + z2 = max(z2, r) + + x1 = float(x1) + x2 = float(x2) + y1 = float(y1) + y2 = float(y2) + z1 = float(z1) + z2 = float(z2) + if len(color1)==3: + color1 = list(color1) + color1.append(255) + if len(color2)==3: + color2 = list(color2) + color2.append(255) + + # calculate streching and displacement variables + a = ((color2[0]-color1[0])/(z2-z1), \ + (color2[1]-color1[1])/(z2-z1), \ + (color2[2]-color1[2])/(z2-z1), \ + (color2[3]-color1[3])/(z2-z1) ) # streching in z direction + b = (x2-x1)/float(w) # streching in x direction + d = (y2-y1)/float(h) # streching in y direction + f = ( color1[0]-a[0]*z1, \ + color1[1]-a[1]*z1, \ + color1[2]-a[2]*z1, \ + color1[3]-a[3]*z1 )# z displacement + c = x1/b + e = y1/d + + surff = pygame.surface.Surface((w,h)).convert_alpha() + # generate values + for i in range(h): + for j in range(w): + val = func(b*(j+c), d*(i+e)) + #clip color + color = ( max(min(a[0]*val+f[0],255),0), \ + max(min(a[1]*val+f[1],255),0), \ + max(min(a[2]*val+f[2],255),0), \ + max(min(a[3]*val+f[3],255),0) ) + surff.set_at( (j,i), color ) + surf.blit(surff, clip) + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/visualclient2/midiclient.py Fri Apr 24 07:09:32 2015 +1000 @@ -0,0 +1,690 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +visclient.py + +Created by George Fazekas on 2012-06-17. +Copyright (c) 2012 . All rights reserved. +""" + +import sys,os,math,time,copy +import pygame as pg +from pygame.locals import * +import httplib as ht +from pygame import midi + +import gradients +from gradients import genericFxyGradient + +from threading import Thread +from random import random +import numpy as np + +import colorsys as cs + +NOTE_OFF_CH0 = [[[0xb0,0x7b,0],0]] + +# 1 HONKY TONK 0x1 / 0x4 (5) +# 2 SITAR 0x9 / 0x0 (1) +# 3 Melancholia +# 4 Rock GITAR 0x3 / 0x2C (45) + +IMAP = {1 : (0x1,0x4), 2: (0x9,0x0), 3:(0x8,0xb), 4:(0x3,0x2c) } + + +# from pytagcloud import create_tag_image, make_tags +# from pytagcloud.lang.counter import get_tag_counts + +# YOUR_TEXT = "A tag cloud is a visual representation for text data, typically\ +# used to depict keyword metadata on websites, or to visualize free form text." +# +# tags = make_tags(get_tag_counts(YOUR_TEXT), maxsize=120) +# +# create_tag_image(tags, 'cloud_large.png', size=(900, 600), fontname='Lobster') + +scol = (0,255,0,255) +ecol = (0,0,0,255) + +# X,Y=1140,900 +# X,Y = 600,400 +X,Y = 800,600 + +# Fullscreen resolution: +# XF,YF = 1280,900 +# XF,YF = 1440,900 +XF,YF = 1344,900 +# display calibrated + +# detect display resolution +import subprocess +screenres = subprocess.Popen('xrandr | grep "\*" | cut -d" " -f4',shell=True, stdout=subprocess.PIPE).communicate()[0] +screenres = map(lambda x: int(x.strip()), screenres.split('x')) +XF,YF = screenres +print "Screen resolution: ",XF,YF + +NBLOBS = 18 +BLOBSIZE = 25 +G=110 +FADE = 15 +DIST = 0.15 # blob equivalence tolerance +FRAMERATE = 60 + +# Connection: +# IP = "127.0.0.1:8030" +# IP = "192.168.2.158:8030" +IP = "138.37.95.215" +HTTP_TIMEOUT = 3 +SERVER_UPDATE_INTERVAL = 0.8 + + +class Indicator(object): + + off_color = pg.Color(110,0,0) + on_color = pg.Color(0,120,0) + + def __init__(self,bg,pos): + self.visible = True + self.ison = True + self.x,self.y = pos + self.xs = int(self.x * X) + self.ys = int(Y - (self.y * Y)) + self.c = self.off_color + self.size = 6 + self.bg = bg + + def reinit(self,bg): + self.bg = bg + self.xs = int(self.x * X) + self.ys = int(Y - (self.y * Y)) + + def draw(self): + if self.visible : + pg.draw.circle(self.bg, self.c, (self.xs,self.ys),self.size,0) + + def toggle(self): + if self.ison == True : + self.off() + else : + self.on() + return self + + def on(self): + self.c = self.on_color + self.ison = True + return self + + def off(self): + self.c = self.off_color + self.ison = False + return self + + +class Blob(object): + + def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): + # print x,y + self.x = x + self.y = y + self.quadrant = self.get_quadrant_number(x,y) + self.xs = x * X + self.ys = Y - (y * Y) + self.bg = bg + self.size = BLOBSIZE + self.time = time.time() + self.alpha = 255 + self.c = color + self.count = 1 + self.visible = True + self.FADE = fade + if mood and mood.color : + self.c = mood.color + + def get_quadrant_number(self,x,y): + if x > 0.5 and y > 0.5 : + return 1 + if x > 0.5 and y < 0.5 : + return 2 + if x < 0.5 and y < 0.5 : + return 3 + if x < 0.5 and y > 0.5 : + return 4 + + + def __cmp__(self,other): + d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) + if d < DIST : + return 0 + else : + return -1 + + def draw(self): + if not self.visible : return + d=int(self.size) + self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d)) + self.alpha = 255 - int(self.age()*self.FADE) + if self.alpha < 5 : + self.alpha = 1 + self.visible = False + + def age(self): + return time.time() - self.time + + def increment(self,count): + self.time = time.time() + self.count = count + # self.size = int(BLOBSIZE * int(self.count/1.5)) + self.to = int(BLOBSIZE * int(self.count/1.5)) + self.start_animate() + + def get_distance(self,x,y): + return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) + + def fade(self,fade): + if not fade : self.alpha = 255 + self.FADE = fade + + def reset_time(self): + self.time = time.time() + + def start_animate(self): + self.thread = Thread(target = self.animate) + self.thread.daemon = True + self.thread.start() + + def animate(self): + '''Animate the way bubbles are grown.''' + while self.size < self.to : + self.size += 1 + time_inc = 20.0 / (pow(1.2, self.to-self.size) * 200.0) + time.sleep(0.02+time_inc) + + + + +class Mood(): + def __init__(self,word,x,y): + self.word = word + self.x = float(x) + self.y = float(y) + self.color = [] + + def get_distance(self,x,y): + return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) + + + +class VisualClient(object): + '''Main visualisation client.''' + + def __init__(self): + # self.conn = ht.HTTPConnection("192.168.2.184:8030") + # self.conn = ht.HTTPConnection("138.37.95.215") + self.s_age = 10 + self.s_dist = DIST + self.s_ninp = 18 + + pg.init() + + # fontObj = pg.font.Font("freesansbold.ttf",18) + white = ( 255, 255, 255) + black = ( 0,0,0) + + self.fpsClock = pg.time.Clock() + self.screen = pg.display.set_mode((X, Y)) + pg.display.set_caption('Mood Conductor') + self.bg = pg.Surface(self.screen.get_size()) + self.bg = self.bg.convert() + self.bg.fill((0,0,0)) + pg.display.set_gamma(100.0) + + + self.scol = (0,255,0,255) + self.ecol = (0,0,0,255) + coordstxt = "test" + + self.blobs = [] + self.moods = [] + self.read_mood_data() + + self.FADE = FADE + + self.indicators = { + "conn":Indicator(self.bg,(0.98,0.02)), + "update":Indicator(self.bg,(0.96,0.02)), + "data":Indicator(self.bg,(0.94,0.02)), + "receive":Indicator(self.bg,(0.92,0.02))} + + self.thread = None + self.running = False + self.fullscreen = False + self.init_reconnect = False + + pg.midi.init() + + print pg.midi.get_device_info(4) + self.midi_out = pg.midi.Output(4,0) + self.midi_out.set_instrument(6) + + # self.midi_out.note_on(23,128,1) + # self.midi_out.write([[[0xc0,0,0],20000],[[0x90,60,100],20500]]) + + self.active_quadrant = 1 + self.prev_quadrant = 1 + + pass + + + def read_mood_data(self): + '''Read the mood position and color information form csv file.''' + with open('moods.csv') as mf: + data = mf.readlines()[1:] + for line in data : + l = line.split(',') + mood = Mood(l[0],l[1],l[2]) + self.moods.append(mood) + with open('colors.txt') as ff: + data = ff.readlines()[1:] + data = map(lambda x: x.split(','),data) + for mood in self.moods : + d = cd = sys.float_info.max + for colors in data : + d = mood.get_distance(float(colors[0]),float(colors[1])) + if d < cd : + cd = d + # mood.color = tuple(map(lambda x: int(pow(math.atan((float(x)/7.0)),12.5)),(colors[2],colors[3],colors[4]))) + mood.color = self.set_color(tuple(map(lambda x: int(x),(colors[2],colors[3],colors[4])))) + return True + + def set_color(self,color): + '''Move to HLS colour space and manipulate saturation there.''' + # TODO: ideally, we need a non-linear compressor of the lightness and saturation values + r,g,b = map(lambda x: (1.0*x/255.0), color) + h,l,s = cs.rgb_to_hls(r,g,b) + s = 1.0 #1.0 - (1.0 / pow(50.0,s)) + l = 1.0 - (1.0 / pow(20.0,l)) #0.6 + r,g,b = map(lambda x: int(x*255), cs.hls_to_rgb(h,l,s)) + return r,g,b + + def start_update_thread(self): + '''Start the thread that reads data from the server.''' + self.running = True + self.thread = Thread(target = self.update_thread) + self.thread.daemon = True + self.thread.start() + + def stop_update_thread(self): + '''Stop the thread and allow some time fot the connections to close.''' + self.running = False + try : + self.thread.join(2) + except : + print "No update thread to join." + + def update_thread(self): + '''The server update thread''' + while self.running : + try : + self.update() + # self.indicators["update"].visible = True + except Exception, e: + if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip()) + self.indicators["conn"].off() + # self.indicators["update"].visible = False + time.sleep(SERVER_UPDATE_INTERVAL) + + + def update(self): + '''Update the blob list from the server. This should be in a thread.''' + + # indicate connection health by toggling an indictor + self.indicators["update"].toggle() + + # delete invisibles + for blob in self.blobs : + if not blob.visible : + self.blobs.remove(blob) + + # get new coordinates from the server + self.conn.putrequest("GET","/moodconductor/result", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + data = res.read() + data = eval(data) + if not data : + self.conn.close() + self.indicators["data"].toggle() + return False + for d in data : + # coordstxt = "x:%s y:%s c:%s" %d + x,y,count = d + self.add_blob(x,y,count) + self.indicators["receive"].toggle() + self.conn.close() + self.blobs = self.blobs[:NBLOBS] + self.compute_quadrant_weighting() + self.self_change_instrument() + return True + + def compute_quadrant_weighting(self): + quadrant_dict = {1:[],2:[],3:[],4:[]} + # sort blobs into quadrants + for blob in self.blobs : + quadrant_dict[blob.quadrant].append(blob) + # get weight for each + quadrant_weights = [] + for q,blob_list in quadrant_dict.iteritems() : + quadrant_weights.append(sum(map(lambda x: x.alpha * x.size,blob_list))) + self.active_quadrant = np.argmax(quadrant_weights) + 1 + print self.active_quadrant + return self.active_quadrant + + def self_change_instrument(self): + if self.active_quadrant != self.prev_quadrant : + self.prev_quadrant = self.active_quadrant + args = IMAP[self.active_quadrant] + self.send_midi_patch_change_GR20(*args) + print args + + def add_blob(self,x,y,count=1): + '''Insert a blob to the list of blobs''' + # find mood correxponding to x,y + cmood = None + d = cd = sys.float_info.max + for mood in self.moods : + d = mood.get_distance(x,y) + if d < cd : + cd = d + cmood = mood + # create new blob or increase click count on existing one + new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE) + if not new in self.blobs : + self.blobs.insert(0,new) + # self.send_midi() + elif count > self.blobs[self.blobs.index(new)].count: + self.blobs[self.blobs.index(new)].increment(count) + pass + + def send_midi(self): + # self.midi_out.write([[[0xc0,0,0],20000],[[0x90,60,100],20500]]) + self.midi_out.write([[[0x90,60,100],0],[[0x90,60,100],500]]) + + def send_midi_patch_change_GR20(self,bank,instrument): + '''PIANO = BANK 1, Patch 5.. bank starts from 1, patch starts from 0 so I have to substract one...''' + self.midi_out.write([[[0xB0,0x0,bank],0],[[0xC0,instrument],100]]) + # Control change (B) followed by patch change (C): + # midi_out.write([[[0xB0,0x0,0x9],0],[[0xC0,0x0],100]]) + # midi_out.write([[[0xB0,0x0,int(bank)],0],[[0xC0,int(instrument)-1],100]]) + # 1 HONKY TONK 0x1 / 0x4 (5) + # 2 SITAR 0x9 / 0x0 (1) + # 3 Melancholia + # 4 Rock GITAR 0x3 / 0x2C (45) + + + def draw(self): + self.bg.fill((0,0,0)) + # self.bg.blit(gradients.radial(19, self.scol, self.ecol), (rect_x,rect_y)) + l = copy.copy(self.blobs) + l.reverse() + for blob in l : + blob.draw() + + # axis + pg.draw.line(self.bg, (G,G,G), (int(X/2.0),0),(int(X/2.0),Y), 1) + pg.draw.line(self.bg, (G,G,G), (0,int(Y/2.0)),(X,int(Y/2.0)), 1) + + # indicators + for i in self.indicators.itervalues() : + i.draw() + + def read_keys(self): + '''Read keys''' + for event in pg.event.get() : + # Quit (event) + if event.type == QUIT: + self.midi_out.write(NOTE_OFF_CH0) + self.quit() + elif event.type == KEYDOWN : + # Post Quit: Esc, q + if event.key == K_ESCAPE or event.key == K_q : + pg.event.post(pg.event.Event(QUIT)) + # Reset: Space + elif event.key == K_SPACE : + self.blobs = [] + # Random : r + elif event.key == K_r : + self.add_blob(random(),random(),count=5) + # Connection : c + elif event.key == K_c : + self.init_reconnect = True + self.indicators["conn"].off() + # Fullscreen: f + elif event.key == K_f : + # pg.display.toggle_fullscreen() + self.toggle_screen_mode() + # Toggle fade: s + elif event.key == K_s : + if self.FADE : + print "fade off" + self.indicators["conn"].off() + self.FADE = 0 + for blob in self.blobs : + blob.fade(0) + else: + print "fade on" + self.indicators["conn"].on() + self.FADE = 15 + for blob in self.blobs : + blob.fade(15) + blob.reset_time() + # inc age + elif event.key == K_1 : + self.s_age += 1 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # dec age + elif event.key == K_2 : + self.s_age -= 1 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # inc dist + elif event.key == K_3 : + self.s_dist += 0.025 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # dec dist + elif event.key == K_4 : + self.s_dist -= 0.025 + if self.s_dist < 0.025 : self.s_dist = 0.025 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # inc ninp + elif event.key == K_5 : + self.s_ninp += 1 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # dec ninp + elif event.key == K_6 : + self.s_ninp -= 1 + if self.s_ninp < 2 : self.s_ninp = 2 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + + pass + pass + + def toggle_screen_mode(self): + '''Go back and forth between full screen mode.''' + if self.fullscreen == False: + globals()['_X'] = globals()['X'] + globals()['_Y'] = globals()['Y'] + globals()['X'] = XF + globals()['Y'] = YF + self.screen = pg.display.set_mode((X, Y)) + # self.screen = pg.display.set_mode((0,0),pg.FULLSCREEN) + self.fullscreen = True + self.bg = pg.Surface(self.screen.get_size()) + self.bg = self.bg.convert() + self.bg.fill((0,0,0)) + for i in self.indicators.itervalues() : + i.reinit(self.bg) + i.draw() + else : + globals()['X'] = globals()['_X'] + globals()['Y'] = globals()['_Y'] + self.screen = pg.display.set_mode((X, Y)) + self.fullscreen = False + self.bg = pg.Surface(self.screen.get_size()) + self.bg = self.bg.convert() + self.bg.fill((0,0,0)) + for i in self.indicators.itervalues() : + i.reinit(self.bg) + i.draw() + pg.display.toggle_fullscreen() + + + + def run(self): + + # setup connection + self.connect() + + # main loop + while True : + # pg.draw.circle(screen, pg.Color(255,0,0), (300,50),20,0) + # screen.blit(gradients.radial(99, scol, ecol), (401, 1)) + + self.read_keys() + + # Draw + self.draw() + + # update display + self.screen.blit(self.bg, (0, 0)) + pg.display.flip() + self.fpsClock.tick(FRAMERATE) + + if self.init_reconnect: + self.reconnect() + + return True + + def configure_server(self): + '''Send the server some configuration data.''' + # age = 10.0 + # dist = DIST + # ninp = 18 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + + + def update_server_config(self,age,dist,ninp,retry = 3): + '''Send the server some configuration data.''' + try : + self.conn.putrequest("GET","/moodconductor/config?age=%(age)s&dist=%(dist)s&ninp=%(ninp)s" %locals(), skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + if not res.status == 200 : + print "Server response:", res.status, res.reason + self.indicators["conn"].off() + time.sleep(0.5) + self.conn.putrequest("GET","/moodconductor/getconf", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + if not res.status == 200 : + print "Server response:", res.status, res.reason + self.indicators["conn"].off() + print "Server configuration:", res.read() + except: + time.sleep(2) + retry -= 1 + self.update_server_config(age,dist,ninp,retry) + + + def connect(self,retry = 5): + '''Connect to the server and test connection.''' + if not retry : + print "Server unreachable" + pg.quit() + raise SystemExit + if retry < 5 : + time.sleep(3) + try : + self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) + # self.start_update_thread() + self.indicators["conn"].on() + except : + self.indicators["conn"].off() + self.connect(retry = retry-1) + + try: + self.conn.putrequest("GET","/moodconductor/index.html", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + if res.status == 200 : + self.indicators["conn"].on() + else : + print "Server response:", res.status, res.reason + self.indicators["conn"].off() + if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : + return False + else : + self.noretry = None + self.connect(retry = retry-1) + except : + print "Exception while testing connection." + self.indicators["conn"].off() + # comment out in offline mode + if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : + return False + else : + self.noretry = None + self.connect(retry = retry-1) + self.configure_server() + self.start_update_thread() + return True + + + + def reconnect(self): + '''Called when c is pressed.''' + self.init_reconnect = False + # self.indicators["conn"].off().draw() + # self.screen.blit(self.bg, (0, 0)) + # pg.display.flip() + + self.stop_update_thread() + time.sleep(1) + try : + self.conn.close() + except : + self.indicators["conn"].off() + try : + self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) + self.conn.connect() + self.indicators["conn"].on() + print "Reconnected." + except : + self.indicators["conn"].off() + print "Error while reconnecting." + self.start_update_thread() + + + def quit(self): + print "Quitting.." + self.indicators["conn"].off() + self.stop_update_thread() + self.conn.close() + pg.quit() + sys.exit() + + + + + +def main(): + + v = VisualClient() + v.run() + + +if __name__ == '__main__': + pass + main() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/visualclient2/moods.csv Fri Apr 24 07:09:32 2015 +1000 @@ -0,0 +1,17 @@ +Label,ValMn,AroMn,DomMn,Cluster +power,0.69,0.71,0.79,1 +bright,0.81,0.55,0.67,2 +brutal,0.23,0.7,0.45,3 +confused,0.28,0.63,0.41,4 +rock,0.57,0.44,0.52,5 +serious,0.51,0.38,0.52,6 +relaxed,0.75,0.17,0.57,7 +calm,0.72,0.33,0.67,8 +dark,0.46,0.41,0.48,9 +dirty,0.26,0.49,0.46,10 +energy,0.78,0.74,0.74,11 +fun,0.92,0.78,0.73,12 +aggressive,0.51,0.6,0.57,13 +scary,0.28,0.71,0.33,14 +positive,0.88,0.57,0.65,15 +sad,0.08,0.39,0.31,16 \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/visualclient2/visclient.py Fri Apr 24 07:09:32 2015 +1000 @@ -0,0 +1,1061 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +visclient.py + +Created by George Fazekas on 2012-06-17. +Copyright (c) 2012 . All rights reserved. +""" + +import sys,os,math,time,copy +import pygame as pg +from pygame.locals import * +from pygame import gfxdraw as gd +import httplib as ht + +import gradients +from gradients import genericFxyGradient + +from threading import Thread +from random import random + +import colorsys as cs + +scol = (0,255,0,255) +ecol = (0,0,0,255) + +xavg=0 +yavg=0 +countavg=0 +moods = [] + + +# X,Y=1140,900 +# X,Y = 600,400 +X,Y = 800,600 +# X,Y = 1024,768 + +# Fullscreen resolution: +# XF,YF = 1280,900 +# XF,YF = 1440,900 +XF,YF = 1344,900 +# display calibrated + +# detect display resolution +import subprocess +screenres = subprocess.Popen('xrandr | grep "\*" | cut -d" " -f4',shell=True, stdout=subprocess.PIPE).communicate()[0] +screenres = map(lambda x: int(x.strip()), screenres.split('x')) +XF,YF = screenres +print "Screen resolution: ",XF,YF + + +# NBLOBS = 18 +# BLOBSIZE = 25 +# G=110 +# FADE = 15 +# DIST = 0.15 # blob equivalence tolerance +# FRAMERATE = 60 + +# new parameters +NBLOBS = 18 +BLOBSIZE = 25 +G=110 +FADE = 25 +DIST = 0.10 # blob equivalence tolerance +FRAMERATE = 120 + + +# Connection: +# IP = "127.0.0.1:8030" +# IP = "192.168.2.158:8030" +IP = "138.37.95.215" # this is the IP of kakapo<=>golden +HTTP_TIMEOUT = 3 +SERVER_UPDATE_INTERVAL = 0.8 + + +class Indicator(object): + + + def __init__(self,bg,pos,invisible=False): + + self.off_color = pg.Color(110,0,0) + self.on_color = pg.Color(0,120,0) + if invisible : + self.off_color = pg.Color(0,0,0) + self.on_color = pg.Color(0,0,120) + self.visible = True + self.ison = True + self.x,self.y = pos + self.xs = int(self.x * X) + self.ys = int(Y - (self.y * Y)) + self.c = self.off_color + self.size = 6 + self.bg = bg + + def reinit(self,bg): + self.bg = bg + self.xs = int(self.x * X) + self.ys = int(Y - (self.y * Y)) + + def draw(self): + if self.visible : + # pg.draw.circle(self.bg, self.c, (self.xs,self.ys),self.size,0) + # gd.aacircle(self.bg, self.xs, self.ys, self.size+1, self.c) + gd.filled_circle(self.bg, self.xs, self.ys, self.size, self.c) + gd.aacircle(self.bg, self.xs, self.ys, self.size, self.c) + + + def toggle(self): + if self.ison == True : + self.off() + else : + self.on() + return self + + def on(self): + self.c = self.on_color + self.ison = True + return self + + def off(self): + self.c = self.off_color + self.ison = False + return self + + def now(self,screen): + # for i in self.indicators.itervalues() : + # i.draw() + self.draw() + screen.blit(self.bg, (0, 0)) + pg.display.flip() + return self + +class MovingBlob(object): + + black_color = pg.Color(0,0,0) + + def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): + self.x = x + self.y = y + self.xs = x * X + self.ys = Y - (y * Y) + self.bg = bg + self.size = BLOBSIZE + self.time = time.time() + self.alpha = 255 + self.c = color + self.count = 1 + self.visible = True + self.FADE = fade + self.mood = mood + if mood and mood.color : + self.c = mood.color + self.title = mood.word + self.speed_factor = 80.0 + self.target_x = 0.0 + self.target_y = 0.0 + + def set_target(self,x,y): + self.target_x = x + self.target_y = y + + def draw(self): + if not self.visible : return + + global xavg,yavg,moods + # xspeed = (xavg - self.x) / self.speed_factor + # yspeed = (yavg - self.y) / self.speed_factor + # self.x = self.x + xspeed + # self.y = self.y + yspeed + self.x = self.x + (self.target_x - self.x) / self.speed_factor + self.y = self.y + (self.target_y - self.y) / self.speed_factor + + #self.size = countavg//TODO + self.xs = self.x * X + self.ys = Y - (self.y * Y) + #d=int(self.size) + + d = cd = sys.float_info.max + + for mood in moods : + d = mood.get_distance(self.x,self.y) + if d < cd : + cd = d + self.mood = mood + self.c = mood.color + self.title = mood.word + + # self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-BLOBSIZE,self.ys-BLOBSIZE)) + d=int(self.size) + # print d + self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d)) + gd.aacircle(self.bg, int(self.xs), int(self.ys), int(self.size), self.black_color) + gd.aacircle(self.bg, int(self.xs), int(self.ys), int(self.size-1), self.black_color) + + + font = pg.font.Font(None, 36) + text = font.render(self.title, 1, self.c) + textpos = text.get_rect() + #print textpos.width + if self.xs > X- textpos.width: + if self.ys > Y- textpos.height: + self.bg.blit(text, (self.xs - textpos.width,self.ys - textpos.height)) + else: + self.bg.blit(text, (self.xs - textpos.width,self.ys)) + + else : + if self.ys > Y- textpos.height: + self.bg.blit(text, (self.xs,self.ys - textpos.height)) + else: + self.bg.blit(text, (self.xs,self.ys)) + #self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d)) + #self.alpha = 255 - int(self.age()*self.FADE) + #if self.alpha < 5 : + # self.alpha = 1 + # self.visible = False + + def age(self): + return time.time() - self.time + + def resize(self,count): + if self.count == count : + return None + self.time = time.time() + self.count = count + # self.size = int(BLOBSIZE * int(self.count/1.5)) + self.to = int(BLOBSIZE * int(self.count/1.5)) + if self.to < BLOBSIZE : + self.to = BLOBSIZE + if self.to and self.size != self.to: + self.start_animate() + # print "resize to",count,self.to + + def get_distance(self,x,y): + return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) + + def fade(self,fade): + if not fade : self.alpha = 255 + self.FADE = fade + + def reset_time(self): + self.time = time.time() + + def reinit(self,bg): + self.bg = bg + self.xs = int(self.x * X) + self.ys = int(Y - (self.y * Y)) + + def start_animate(self): + self.thread = Thread(target = self.animate) + self.thread.daemon = True + self.thread.start() + + def animate(self): + '''Animate the way bubbles are grown.''' + tolerance = 5 + # while self.size > self.to-tolerance and self.size < self.to+tolerance : + while self.size != self.to : + self.size = int(self.size + int(self.to-self.size) * 0.1) + time_inc = 20.0 / (pow(1.2, abs(self.to-self.size)) * 200.0) + time.sleep(0.02+time_inc) + # print "sizing to ", self.to + + +class Blob(object): + + def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): + self.x = x + self.y = y + self.xs = x * X + self.ys = Y - (y * Y) + self.bg = bg + self.size = BLOBSIZE #pixels + self.time = time.time() #s + self.const_time = time.time() #s + self.alpha = 255 #8-bit alpha channel value + self.c = color #RGB colour 3-tuple + self.count = 1 + self.visible = True + self.FADE = fade + if mood and mood.color : + self.c = mood.color + self.title = mood.word + self.inactivity_delay = 75.0 #s + self.proximity_delay = 35.0 #s + self.proximity_tolerance = 0.13 + self.target_in_proximity = None + + def __cmp__(self,other): + if other is None : + return -1 + d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) + if d < DIST : + return 0 + else : + return -1 + + def object_in_proximity(self,other): + if other is None : + return False + d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) + if d < self.proximity_tolerance : + return True + else : + return False + + def check_target_proximity(self,target): + '''Check if the moving bubble is in proximity of this object. As soon as it is there, mark the time. + We do not want to reset this time just wait until this bubble dies due to inactivity in its region.''' + prox = self.object_in_proximity(target) + if self.target_in_proximity is None and prox is True : + self.target_in_proximity = time.time() + # if prox is False : + # self.target_in_proximity = None + + def draw(self): + if not self.visible : return + + d=int(self.size) + self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d)) + self.alpha = 255 - int(self.age()*self.FADE) + if self.alpha < 5 : + self.alpha = 1 + self.visible = False + + def age(self): + return time.time() - self.time + + def real_age(self): + return time.time() - self.const_time + + def age_weight(self): + age_s = time.time() - self.const_time + if age_s < self.inactivity_delay : + return 1.0 + return 1.0 / ((age_s-self.inactivity_delay+1)*10.0) + + def proximity_weight(self): + if self.target_in_proximity == None : + return 1.0 + age_s = time.time() - self.target_in_proximity + if age_s < self.proximity_delay : + return 1.0 + return 1.0 / ((age_s-self.proximity_delay+1)*10.0) + + def increment(self,count): + self.time = time.time() + self.count = count + # self.size = int(BLOBSIZE * int(self.count/1.5)) + self.to = int(BLOBSIZE * int(self.count/1.5)) + if self.to < 250 : + self.start_animate() + + def get_distance(self,x,y): + return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) + + def fade(self,fade): + if not fade : self.alpha = 255 + self.FADE = fade + + def reset_time(self): + self.time = time.time() + + def start_animate(self): + self.thread = Thread(target = self.animate) + self.thread.daemon = True + self.thread.start() + + def animate(self): + '''Animate the way bubbles are grown.''' + while self.size < self.to : + self.size += 1 + time_inc = 20.0 / (pow(1.2, self.to-self.size) * 200.0) + time.sleep(0.02+time_inc) + + def force(self,other): + '''Calculate the force between this object and another''' + if other is None : + return 0.0,0.0,False + captured = False + ds = math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) + if ds < 0.005 : + return 0.0,0.0,False + d = math.sqrt(ds) + if d < 0.07 : + captured = True + # return 0.0,0.0,False + m1,m2 = self.size,other.size + G = 6.674 * 0.000001 + f = - G * (m1*m2) / ds + x = f * (other.x-self.x) / d + y = f * (other.y-self.y) / d + return x,y,captured + + +class BlobTrail(object): + + def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): + self.x = x + self.y = y + self.xs = x * X + self.ys = Y - (y * Y) + self.bg = bg + self.size = BLOBSIZE + self.time = time.time() + self.alpha = 255 + self.c = color + self.count = 1 + self.visible = True + self.FADE = fade + if mood and mood.color : + self.c = mood.color + self.title = mood.word + + def __cmp__(self,other): + d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) + if d < DIST : + return 0 + else : + return -1 + + def draw(self): + if not self.visible : return + + d=int(self.size) + self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,0)), (self.xs-d,self.ys-d)) + self.alpha = 255 - int(self.age()*self.FADE) + if self.alpha < 5 : + self.alpha = 1 + self.visible = False + + def age(self): + return time.time() - self.time + + def increment(self,count): + self.time = time.time() + self.count = count + # self.size = int(BLOBSIZE * int(self.count/1.5)) + self.to = int(BLOBSIZE * int(self.count/1.5)) + self.start_animate() + + def get_distance(self,x,y): + return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) + + def fade(self,fade): + if not fade : self.alpha = 255 + self.FADE = fade + + def reset_time(self): + self.time = time.time() + + def start_animate(self): + self.thread = Thread(target = self.animate) + self.thread.daemon = True + self.thread.start() + + def animate(self): + '''Animate the way bubbles are grown.''' + while self.size < self.to : + self.size += 1 + time_inc = 20.0 / (pow(1.2, self.to-self.size) * 200.0) + time.sleep(0.02+time_inc) + + +class Mood(): + + def __init__(self,word,x,y): + self.word = word + self.x = (float(x)/ 2.0) + 0.5 + self.y = (float(y)/ 2.0) + 0.5 + self.color = [] + + def get_distance(self,x,y): + return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) + + def __repr__(self): + return "Mood(%s,%3.2f,%3.2f)" %(self.word,self.x,self.y) + + + +class VisualClient(object): + '''Main visualisation client.''' + global moods,blobTrail + + def __init__(self): + # self.conn = ht.HTTPConnection("192.168.2.184:8030") + # self.conn = ht.HTTPConnection("138.37.95.215") + self.s_age = 10 + self.s_dist = DIST + self.s_ninp = 18 + + pg.init() + pg.font.init() + + # fontObj = pg.font.Font("freesansbold.ttf",18) + white = ( 255, 255, 255) + black = ( 0,0,0) + + self.fpsClock = pg.time.Clock() + self.screen = pg.display.set_mode((X, Y)) + pg.display.set_caption('Mood Conductor') + self.bg = pg.Surface(self.screen.get_size()) + self.bg = self.bg.convert() + self.bg.fill((0,0,0)) + pg.display.set_gamma(100.0) + + + self.scol = (0,255,0,255) + self.ecol = (0,0,0,255) + coordstxt = "test" + + self.blobs = [] + #self.moods = [] + self.bt=[] + + self.movingBlob = None + + self.read_mood_data() + + self.FADE = FADE + + self.indicators = { + "conn":Indicator(self.bg,(0.98,0.02)), # connection active + "update":Indicator(self.bg,(0.96,0.02)), # update thread executing + "data":Indicator(self.bg,(0.94,0.02)), # data status changed + "receive":Indicator(self.bg,(0.92,0.02)), # data received + "grow":Indicator(self.bg,(0.90,0.02)), # blob growth active + "ignore":Indicator(self.bg,(0.88,0.02),True), # little AI: ignore some clusters in certain condition + "suspend":Indicator(self.bg,(0.86,0.02),True), # prevent adding new blobs (key: d) + "config":Indicator(self.bg,(0.84,0.02),True), # sending config data + "fade":Indicator(self.bg,(0.82,0.02),True)} # fade on/off (key: s) + + self.thread = None + self.running = False + self.fullscreen = False + self.init_reconnect = False + self.suspend = False + + pass + + + def read_mood_data(self): + '''Read the mood position and color information form csv file.''' + # file = 'moods.csv' + file = '../tags/mc_moodtags_lfm_curated2.csv' + with open(file) as mf: + data = mf.readlines()[1:] + for line in data : + l = line.split(',') + l = map(lambda x:x.replace("'","").strip(),l) + mood = Mood(l[0],l[1],l[2]) + moods.append(mood) + print moods + for mood in moods: + print"['%s',%2.2f,%2.2f,0,0]," %(mood.word,mood.x,mood.y) + + with open('colors.txt') as ff: + data = ff.readlines()[1:] + data = map(lambda x: x.split(','),data) + for mood in moods : + d = cd = sys.float_info.max + for colors in data : + d = mood.get_distance(float(colors[0]),float(colors[1])) + if d < cd : + cd = d + # mood.color = tuple(map(lambda x: int(pow(math.atan((float(x)/7.0)),12.5)),(colors[2],colors[3],colors[4]))) + mood.color = self.set_color(tuple(map(lambda x: int(x),(colors[2],colors[3],colors[4])))) + return True + + def set_color(self,color): + '''Move to HLS colour space and manipulate saturation there.''' + # TODO: ideally, we need a non-linear compressor of the lightness and saturation values + r,g,b = map(lambda x: (1.0*x/255.0), color) + h,l,s = cs.rgb_to_hls(r,g,b) + s = 1.0 #1.0 - (1.0 / pow(50.0,s)) + l = 1.0 - (1.0 / pow(20.0,l)) #0.6 + r,g,b = map(lambda x: int(x*255), cs.hls_to_rgb(h,l,s)) + return r,g,b + + def start_update_thread(self): + '''Start the thread that reads data from the server.''' + time.sleep(SERVER_UPDATE_INTERVAL) + self.thread = Thread(target = self.update_thread) + self.thread.daemon = True + self.running = True + self.thread.start() + print "OK. Update thread started." + + def stop_update_thread(self): + '''Stop the thread and allow some time fot the connections to close.''' + self.running = False + try : + self.thread.join(2) + self.indicators["conn"].off() + self.indicators["update"].off() + except : + print "No update thread to join." + + def update_thread(self): + '''The server update thread''' + print "Thread reporting..." + while self.running : + # self.update() + try : + self.update() + # self.indicators["update"].visible = True + except Exception, e: + if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip()) + self.indicators["conn"].off() + # self.indicators["update"].visible = False + try : + time.sleep(SERVER_UPDATE_INTERVAL) + except : + if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip()) + self.indicators["conn"].off() + + + def update(self): + '''Update the blob list from the server. This should be in a thread.''' + # global xavg, yavg, countavg + # indicate connection health by toggling an indictor + self.indicators["update"].toggle() + + # delete invisibles + for blob in self.blobs : + if not blob.visible : + self.blobs.remove(blob) + + # get new coordinates from the server + self.conn.putrequest("GET","/moodconductor/result", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + data = res.read() + data = eval(data) + if not data : + self.conn.close() + self.indicators["data"].toggle() + return False + + tempx = 0 + tempy = 0 + tempcount = 0 + + for d in data : + # coordstxt = "x:%s y:%s c:%s" %d + x,y,count = d + + tempx = tempx + x*count + tempy = tempy + y*count + tempcount = tempcount + count + + self.add_blob(x,y,count) + self.indicators["receive"].toggle() + + xavg = tempx/tempcount + yavg = tempy/tempcount + countavg = tempcount/len(data) + # print xavg, yavg, countavg + # if not self.blobs : + # self.add_blob(xavg,yavg,countavg) + # self.indicators["receive"].toggle() + + self.conn.close() + self.blobs = self.blobs[:NBLOBS] + return True + + + def add_blob(self,x,y,count=1): + '''Insert a blob to the list of blobs''' + # find mood correxponding to x,y + if self.suspend : + return None + cmood = None + d = cd = sys.float_info.max + for mood in moods : + d = mood.get_distance(x,y) + if d < cd : + cd = d + cmood = mood + # create new blob or increase click count on existing one + new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE) + if self.movingBlob == None : + self.movingBlob = MovingBlob(self.bg,x,y,mood=cmood,fade=self.FADE) + if not new in self.blobs : + self.blobs.insert(0,new) + elif count > self.blobs[self.blobs.index(new)].count: + self.blobs[self.blobs.index(new)].increment(count) + self.indicators["grow"].toggle() + pass + + + def draw(self): + '''Draw all objects''' + global xavg, yavg, countavg + + self.bg.fill((0,0,0,1)) + # self.bg.blit(gradients.radial(19, self.scol, self.ecol), (rect_x,rect_y)) + forces = [] + l = copy.copy(self.blobs) + l.reverse() + xt,yt = 0.0,0.0 + bs = 1 + c = 1 + # captured_by = None + + # calculate exponential weighted average of the visible blobs + ignore = False + for blob in l : + blob.draw() + c = c + blob.count + # aw = blob.age_weight() + aw = blob.proximity_weight() + if aw < 1.0 : ignore = True + w = math.pow(blob.size+(blob.alpha/2.0),7) * aw + xt = xt + blob.x * w + yt = yt + blob.y * w + bs = bs + w + if self.movingBlob != None : + blob.check_target_proximity(self.movingBlob) + xavg = xt / bs + yavg = yt / bs + # countavg = bs/(len(l)+1) + countavg = int(c/(len(l)+1)) + if ignore : + self.indicators["ignore"].on() + else : + self.indicators["ignore"].off() + + # compute gravity force + # if self.movingBlob != None : + # x,y,c = blob.force(self.movingBlob) + # forces.append((x,y)) + # if c : captured_by = blob + # tx,ty = reduce(lambda a,b:(a[0]+b[0],a[1]+b[1]), forces, (0.5,0.5)) + + # print tx,ty + # if tx>1.0 : tx = 1.0 + # if tx<0.0 : tx = 0.0 + # if ty>1.0 : ty = 1.0 + # if ty<0.0 : ty = 0.0 + + # xavg,yavg = tx,ty + + # if tx <= 1.0 and tx >= 0.0 and ty <= 1.0 and ty >= 0.0 : + # xavg,yavg = tx,ty + # countavg = 15 + # print tx,ty + # else : + # print "out of bounds:",tx,ty + # if captured_by != None : + # xavg,yavg = captured_by.x,captured_by.y + l = copy.copy(self.bt) + l.reverse() + for trail in l: + trail.draw() + if self.movingBlob != None : + # self.movingBlob.resize(countavg) + self.movingBlob.set_target(xavg,yavg) + self.movingBlob.draw() + new = BlobTrail(self.bg,self.movingBlob.x,self.movingBlob.y,mood=self.movingBlob.mood,fade=18) + self.bt.insert(0,new) + # axis + pg.draw.line(self.bg, (G,G,G), (int(X/2.0),0),(int(X/2.0),Y), 1) + pg.draw.line(self.bg, (G,G,G), (0,int(Y/2.0)),(X,int(Y/2.0)), 1) + + # indicators + for i in self.indicators.itervalues() : + i.draw() + + def read_keys(self): + '''Read keys''' + for event in pg.event.get() : + # Quit (event) + if event.type == QUIT: + self.quit() + elif event.type == KEYDOWN : + # Post Quit: Esc, q + if event.key == K_ESCAPE or event.key == K_q : + pg.event.post(pg.event.Event(QUIT)) + # Reset: Space + elif event.key == K_SPACE : + if not self.blobs : + self.bt = [] + self.blobs = [] + # Random : r + elif event.key == K_r : + self.add_blob(random(),random(),count=5) + # Connection : c + elif event.key == K_c : + self.init_reconnect = True + self.indicators["conn"].off() + # Fullscreen: f + elif event.key == K_f : + # pg.display.toggle_fullscreen() + self.toggle_screen_mode() + # Toggle suspend: d + elif event.key == K_d : + if self.suspend : + print "suspend off" + self.indicators["suspend"].off() + self.suspend = False + else: + print "suspend on" + self.indicators["suspend"].on() + self.suspend = True + # Toggle fade: s + elif event.key == K_s : + if self.FADE > 0: + print "fade off" + self.indicators["fade"].off() + self.FADE = 0 + for blob in self.blobs : + blob.fade(0) + else: + print "fade on" + self.indicators["fade"].on() + self.FADE = FADE + for blob in self.blobs : + blob.fade(FADE) + blob.reset_time() + # inc age + elif event.key == K_1 : + self.s_age += 1 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # dec age + elif event.key == K_2 : + self.s_age -= 1 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # inc dist + elif event.key == K_3 : + self.s_dist += 0.025 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # dec dist + elif event.key == K_4 : + self.s_dist -= 0.025 + if self.s_dist < 0.025 : self.s_dist = 0.025 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # inc ninp + elif event.key == K_5 : + self.s_ninp += 1 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # dec ninp + elif event.key == K_6 : + self.s_ninp -= 1 + if self.s_ninp < 2 : self.s_ninp = 2 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + # choose different app and restart server + elif event.key == K_9 : + self.choose_app() + + pass + pass + + def toggle_screen_mode(self): + '''Go back and forth between full screen mode.''' + if self.fullscreen == False: + globals()['_X'] = globals()['X'] + globals()['_Y'] = globals()['Y'] + globals()['X'] = XF + globals()['Y'] = YF + self.screen = pg.display.set_mode((X, Y)) + # self.screen = pg.display.set_mode((0,0),pg.FULLSCREEN) + self.fullscreen = True + self.bg = pg.Surface(self.screen.get_size()) + self.bg = self.bg.convert() + self.bg.fill((0,0,0)) + for i in self.indicators.itervalues() : + i.reinit(self.bg) + i.draw() + if self.movingBlob != None : + self.movingBlob.reinit(self.bg) + self.movingBlob.draw() + else : + globals()['X'] = globals()['_X'] + globals()['Y'] = globals()['_Y'] + self.screen = pg.display.set_mode((X, Y)) + self.fullscreen = False + self.bg = pg.Surface(self.screen.get_size()) + self.bg = self.bg.convert() + self.bg.fill((0,0,0)) + for i in self.indicators.itervalues() : + i.reinit(self.bg) + i.draw() + if self.movingBlob != None : + self.movingBlob.reinit(self.bg) + self.movingBlob.draw() + pg.display.toggle_fullscreen() + + + + def run(self): + + # setup connection + self.connect() + + # main loop + while True : + # pg.draw.circle(screen, pg.Color(255,0,0), (300,50),20,0) + # screen.blit(gradients.radial(99, scol, ecol), (401, 1)) + + self.read_keys() + + # Draw + self.draw() + + # update display + self.screen.blit(self.bg, (0, 0)) + pg.display.flip() + self.fpsClock.tick(FRAMERATE) + + if self.init_reconnect: + self.reconnect() + + return True + + def choose_app(self): + '''Experimental function for chaging served apps remotely. Disabled for now...''' + return False + try : + print "Changing app and restarting... the connection will be lost." + self.conn.putrequest("GET","/moodconductor/changeapp", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + res.read() + except : + pass + + def configure_server(self): + '''Send the server some configuration data.''' + # age = 10.0 + # dist = DIST + # ninp = 18 + self.update_server_config(self.s_age,self.s_dist,self.s_ninp) + + + def update_server_config(self,age,dist,ninp,retry = 3): + '''Send the server some configuration data.''' + self.indicators["config"].on().now(self.screen) + try : + print "Sending configuration data." + self.conn.putrequest("GET","/moodconductor/config?age=%(age)s&dist=%(dist)s&ninp=%(ninp)s" %locals(), skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + res.read() + if not res.status == 200 : + print "Server response:", res.status, res.reason + self.indicators["conn"].off() + time.sleep(0.5) + self.conn.putrequest("GET","/moodconductor/getconf", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + if not res.status == 200 : + print "Server response:", res.status, res.reason + self.indicators["conn"].off() + # self.indicators["config"].on() + print "Server configuration:", res.read() + self.indicators["config"].off() + self.indicators["conn"].on() + except: + print "Failed to send configuration data." + time.sleep(2) + retry -= 1 + self.update_server_config(age,dist,ninp,retry) + + + def connect(self,retry = 5): + '''Connect to the server and test connection.''' + if not retry : + print "Server unreachable" + pg.quit() + raise SystemExit + if retry < 5 : + time.sleep(3) + try : + print "connecting to server..." + self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) + self.indicators["conn"].on() + except : + self.indicators["conn"].off() + self.connect(retry = retry-1) + print "connection failed." + + try: + print "Testing connection." + # self.conn.putrequest("GET","/moodconductor/index.html", skip_host=True) + self.conn.putrequest("GET","/moodconductor/test", skip_host=True) + self.conn.putheader("Host", "www.isophonics.net") + self.conn.endheaders() + res = self.conn.getresponse() + print res.read() + if res.status == 200 : + self.indicators["conn"].on() + else : + print "Server response:", res.status, res.reason + self.indicators["conn"].off() + if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : + return False + else : + print "Failed. retrying." + self.noretry = None + self.connect(retry = retry-1) + except Exception as e: + print "Exception while testing connection.", e + self.indicators["conn"].off() + # comment out in offline mode + if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : + return False + else : + self.noretry = None + self.connect(retry = retry-1) + self.configure_server() + print "OK. Starting update thread..." + self.start_update_thread() + return True + + + + def reconnect(self): + '''Called when c is pressed.''' + self.indicators["config"].on().now(self.screen) + self.init_reconnect = False + + self.stop_update_thread() + time.sleep(1) + try : + self.conn.close() + except : + self.indicators["conn"].off() + try : + self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) + self.conn.connect() + self.indicators["conn"].on() + print "Reconnected." + except : + self.indicators["conn"].off() + print "Error while reconnecting." + self.start_update_thread() + self.indicators["config"].off().now(self.screen) + + + def quit(self): + print "Quitting.." + self.indicators["conn"].off() + self.stop_update_thread() + self.conn.close() + pg.quit() + sys.exit() + + +def main(): + + v = VisualClient() + v.run() + +if __name__ == '__main__': + pass + main() +