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
Binary file visualclient2/.DS_Store has changed
--- /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)
+
+
+
Binary file visualclient2/gradients.pyc has changed
--- /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()
+